/*
 * Copyright (C) 2009 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package org.odk.collect.android.activities;

import android.app.Dialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;

import org.odk.collect.android.R;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.listeners.FormDownloaderListener;
import org.odk.collect.android.listeners.FormListDownloaderListener;
import org.odk.collect.android.logic.FormDetails;
import org.odk.collect.android.preferences.PreferencesActivity;
import org.odk.collect.android.tasks.DownloadFormListTask;
import org.odk.collect.android.tasks.DownloadFormsTask;
import org.odk.collect.android.utilities.CompatibilityUtils;
import org.odk.collect.android.utilities.WebUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

/**
 * Responsible for displaying, adding and deleting all the valid forms in the forms directory. One caveat. If the server
 * requires authentication, a dialog will pop up asking when you request the form list. If somehow you manage to wait
 * long enough and then try to download selected forms and your authorization has timed out, it won't again ask for
 * authentication, it will just throw a 401 and you'll have to hit 'refresh' where it will ask for credentials again.
 * Technically a server could point at other servers requiring authentication to download the forms, but the current
 * implementation in Collect doesn't allow for that. Mostly this is just because it's a pain in the butt to keep track
 * of which forms we've downloaded and where we're needing to authenticate. I think we do something similar in the
 * instanceuploader task/activity, so should change the implementation eventually.
 *
 * @author Carl Hartung (carlhartung@gmail.com)
 */
public class FormDownloadList extends ListActivity implements FormListDownloaderListener,
    FormDownloaderListener {
  private static final String t = "RemoveFileManageList";

  private static final int PROGRESS_DIALOG = 1;
  private static final int AUTH_DIALOG = 2;
  private static final int MENU_PREFERENCES = Menu.FIRST;

  private static final String BUNDLE_TOGGLED_KEY = "toggled";
  private static final String BUNDLE_SELECTED_COUNT = "selectedcount";
  private static final String BUNDLE_FORM_MAP = "formmap";
  private static final String DIALOG_TITLE = "dialogtitle";
  private static final String DIALOG_MSG = "dialogmsg";
  private static final String DIALOG_SHOWING = "dialogshowing";
  private static final String FORMLIST = "formlist";

  public static final String LIST_URL = "listurl";

  private static final String FORMNAME = "formname";
  private static final String FORMDETAIL_KEY = "formdetailkey";
  private static final String FORMID_DISPLAY = "formiddisplay";

  private String mAlertMsg;
  private boolean mAlertShowing = false;
  private String mAlertTitle;

  private AlertDialog mAlertDialog;
  private ProgressDialog mProgressDialog;
  private Button mDownloadButton;

  private DownloadFormListTask mDownloadFormListTask;
  private DownloadFormsTask mDownloadFormsTask;
  private Button mToggleButton;
  private Button mRefreshButton;

  private HashMap<String, FormDetails> mFormNamesAndURLs = new HashMap<String, FormDetails>();
  private SimpleAdapter mFormListAdapter;
  private ArrayList<HashMap<String, String>> mFormList;

  private boolean mToggled = false;
  private int mSelectedCount = 0;

  private static final boolean EXIT = true;
  private static final boolean DO_NOT_EXIT = false;
  private boolean mShouldExit;
  private static final String SHOULD_EXIT = "shouldexit";


  @SuppressWarnings("unchecked")
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.remote_file_manage_list);
    setTitle(getString(R.string.app_name) + " > " + getString(R.string.get_forms));
    mAlertMsg = getString(R.string.please_wait);

    // need white background before load
    getListView().setBackgroundColor(Color.WHITE);

    mDownloadButton = (Button) findViewById(R.id.add_button);
    mDownloadButton.setEnabled(selectedItemCount() > 0);
    mDownloadButton.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        // this is callled in downloadSelectedFiles():
        //    Collect.getInstance().getActivityLogger().logAction(this, "downloadSelectedFiles", ...);
        downloadSelectedFiles();
        mToggled = false;
        clearChoices();
      }
    });

    mToggleButton = (Button) findViewById(R.id.toggle_button);
    mToggleButton.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        // toggle selections of items to all or none
        ListView ls = getListView();
        mToggled = !mToggled;

        Collect.getInstance().getActivityLogger().logAction(this, "toggleFormCheckbox", Boolean.toString(mToggled));

        for (int pos = 0; pos < ls.getCount(); pos++) {
          ls.setItemChecked(pos, mToggled);
        }

        mDownloadButton.setEnabled(!(selectedItemCount() == 0));
      }
    });

    mRefreshButton = (Button) findViewById(R.id.refresh_button);
    mRefreshButton.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        Collect.getInstance().getActivityLogger().logAction(this, "refreshForms", "");

        mToggled = false;
        downloadFormList();
        FormDownloadList.this.getListView().clearChoices();
        clearChoices();
      }
    });

    if (savedInstanceState != null) {
      // If the screen has rotated, the hashmap with the form ids and urls is passed here.
      if (savedInstanceState.containsKey(BUNDLE_FORM_MAP)) {
        mFormNamesAndURLs =
            (HashMap<String, FormDetails>) savedInstanceState
                .getSerializable(BUNDLE_FORM_MAP);
      }

      // indicating whether or not select-all is on or off.
      if (savedInstanceState.containsKey(BUNDLE_TOGGLED_KEY)) {
        mToggled = savedInstanceState.getBoolean(BUNDLE_TOGGLED_KEY);
      }

      // how many items we've selected
      // Android should keep track of this, but broken on rotate...
      if (savedInstanceState.containsKey(BUNDLE_SELECTED_COUNT)) {
        mSelectedCount = savedInstanceState.getInt(BUNDLE_SELECTED_COUNT);
        mDownloadButton.setEnabled(!(mSelectedCount == 0));
      }

      // to restore alert dialog.
      if (savedInstanceState.containsKey(DIALOG_TITLE)) {
        mAlertTitle = savedInstanceState.getString(DIALOG_TITLE);
      }
      if (savedInstanceState.containsKey(DIALOG_MSG)) {
        mAlertMsg = savedInstanceState.getString(DIALOG_MSG);
      }
      if (savedInstanceState.containsKey(DIALOG_SHOWING)) {
        mAlertShowing = savedInstanceState.getBoolean(DIALOG_SHOWING);
      }
      if (savedInstanceState.containsKey(SHOULD_EXIT)) {
        mShouldExit = savedInstanceState.getBoolean(SHOULD_EXIT);
      }
    }

    if (savedInstanceState != null && savedInstanceState.containsKey(FORMLIST)) {
      mFormList =
          (ArrayList<HashMap<String, String>>) savedInstanceState.getSerializable(FORMLIST);
    } else {
      mFormList = new ArrayList<HashMap<String, String>>();
    }

    if (getLastNonConfigurationInstance() instanceof DownloadFormListTask) {
      mDownloadFormListTask = (DownloadFormListTask) getLastNonConfigurationInstance();
      if (mDownloadFormListTask.getStatus() == AsyncTask.Status.FINISHED) {
        try {
          dismissDialog(PROGRESS_DIALOG);
        } catch (IllegalArgumentException e) {
          Log.i(t, "Attempting to close a dialog that was not previously opened");
        }
        mDownloadFormsTask = null;
      }
    } else if (getLastNonConfigurationInstance() instanceof DownloadFormsTask) {
      mDownloadFormsTask = (DownloadFormsTask) getLastNonConfigurationInstance();
      if (mDownloadFormsTask.getStatus() == AsyncTask.Status.FINISHED) {
        try {
          dismissDialog(PROGRESS_DIALOG);
        } catch (IllegalArgumentException e) {
          Log.i(t, "Attempting to close a dialog that was not previously opened");
        }
        mDownloadFormsTask = null;
      }
    } else if (getLastNonConfigurationInstance() == null) {
      // first time, so get the formlist
      downloadFormList();
    }

    String[] data = new String[]{
        FORMNAME, FORMID_DISPLAY, FORMDETAIL_KEY
    };
    int[] view = new int[]{
        R.id.text1, R.id.text2
    };

    mFormListAdapter =
        new SimpleAdapter(this, mFormList, R.layout.two_item_multiple_choice, data, view);
    getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    getListView().setItemsCanFocus(false);
    setListAdapter(mFormListAdapter);
  }


  @Override
  protected void onStart() {
    super.onStart();
    Collect.getInstance().getActivityLogger().logOnStart(this);
  }

  @Override
  protected void onStop() {
    Collect.getInstance().getActivityLogger().logOnStop(this);
    super.onStop();
  }

  private void clearChoices() {
    FormDownloadList.this.getListView().clearChoices();
    mDownloadButton.setEnabled(false);
  }


  @Override
  protected void onListItemClick(ListView l, View v, int position, long id) {
    super.onListItemClick(l, v, position, id);
    mDownloadButton.setEnabled(!(selectedItemCount() == 0));

    Object o = getListAdapter().getItem(position);
    @SuppressWarnings("unchecked")
    HashMap<String, String> item = (HashMap<String, String>) o;
    FormDetails detail = mFormNamesAndURLs.get(item.get(FORMDETAIL_KEY));

    Collect.getInstance().getActivityLogger().logAction(this, "onListItemClick", detail.downloadUrl);
  }


  /**
   * Starts the download task and shows the progress dialog.
   */
  private void downloadFormList() {
    ConnectivityManager connectivityManager =
        (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo ni = connectivityManager.getActiveNetworkInfo();

    if (ni == null || !ni.isConnected()) {
      Toast.makeText(this, R.string.no_connection, Toast.LENGTH_SHORT).show();
    } else {

      mFormNamesAndURLs = new HashMap<String, FormDetails>();
      if (mProgressDialog != null) {
        // This is needed because onPrepareDialog() is broken in 1.6.
        mProgressDialog.setMessage(getString(R.string.please_wait));
      }
      showDialog(PROGRESS_DIALOG);

      if (mDownloadFormListTask != null &&
          mDownloadFormListTask.getStatus() != AsyncTask.Status.FINISHED) {
        return; // we are already doing the download!!!
      } else if (mDownloadFormListTask != null) {
        mDownloadFormListTask.setDownloaderListener(null);
        mDownloadFormListTask.cancel(true);
        mDownloadFormListTask = null;
      }

      mDownloadFormListTask = new DownloadFormListTask();
      mDownloadFormListTask.setDownloaderListener(this);
      mDownloadFormListTask.execute();
    }
  }


  @Override
  protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean(BUNDLE_TOGGLED_KEY, mToggled);
    outState.putInt(BUNDLE_SELECTED_COUNT, selectedItemCount());
    outState.putSerializable(BUNDLE_FORM_MAP, mFormNamesAndURLs);
    outState.putString(DIALOG_TITLE, mAlertTitle);
    outState.putString(DIALOG_MSG, mAlertMsg);
    outState.putBoolean(DIALOG_SHOWING, mAlertShowing);
    outState.putBoolean(SHOULD_EXIT, mShouldExit);
    outState.putSerializable(FORMLIST, mFormList);
  }


  /**
   * returns the number of items currently selected in the list.
   *
   * @return
   */
  private int selectedItemCount() {
    int count = 0;
    SparseBooleanArray sba = getListView().getCheckedItemPositions();
    for (int i = 0; i < getListView().getCount(); i++) {
      if (sba.get(i, false)) {
        count++;
      }
    }
    return count;
  }


  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    Collect.getInstance().getActivityLogger().logAction(this, "onCreateOptionsMenu", "show");
    super.onCreateOptionsMenu(menu);

    CompatibilityUtils.setShowAsAction(
        menu.add(0, MENU_PREFERENCES, 0, R.string.general_preferences)
            .setIcon(R.drawable.ic_menu_preferences),
        MenuItem.SHOW_AS_ACTION_NEVER);
    return true;
  }


  @Override
  public boolean onMenuItemSelected(int featureId, MenuItem item) {
    switch (item.getItemId()) {
      case MENU_PREFERENCES:
        Collect.getInstance().getActivityLogger().logAction(this, "onMenuItemSelected", "MENU_PREFERENCES");
        Intent i = new Intent(this, PreferencesActivity.class);
        startActivity(i);
        return true;
    }
    return super.onMenuItemSelected(featureId, item);
  }


  @Override
  protected Dialog onCreateDialog(int id) {
    switch (id) {
      case PROGRESS_DIALOG:
        Collect.getInstance().getActivityLogger().logAction(this, "onCreateDialog.PROGRESS_DIALOG", "show");
        mProgressDialog = new ProgressDialog(this);
        DialogInterface.OnClickListener loadingButtonListener =
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                Collect.getInstance().getActivityLogger().logAction(this, "onCreateDialog.PROGRESS_DIALOG", "OK");
                dialog.dismiss();
                // we use the same progress dialog for both
                // so whatever isn't null is running
                if (mDownloadFormListTask != null) {
                  mDownloadFormListTask.setDownloaderListener(null);
                  mDownloadFormListTask.cancel(true);
                  mDownloadFormListTask = null;
                }
                if (mDownloadFormsTask != null) {
                  mDownloadFormsTask.setDownloaderListener(null);
                  mDownloadFormsTask.cancel(true);
                  mDownloadFormsTask = null;
                }
              }
            };
        mProgressDialog.setTitle(getString(R.string.downloading_data));
        mProgressDialog.setMessage(mAlertMsg);
        mProgressDialog.setIndeterminate(true);
        mProgressDialog.setCancelable(false);
        mProgressDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.cancel), loadingButtonListener);
        return mProgressDialog;
      case AUTH_DIALOG:
        Collect.getInstance().getActivityLogger().logAction(this, "onCreateDialog.AUTH_DIALOG", "show");
        AlertDialog.Builder b = new AlertDialog.Builder(this);

        LayoutInflater factory = LayoutInflater.from(this);
        final View dialogView = factory.inflate(R.layout.server_auth_dialog, null);

        // Get the server, username, and password from the settings
        SharedPreferences settings =
            PreferenceManager.getDefaultSharedPreferences(getBaseContext());
        String server =
            settings.getString(PreferencesActivity.KEY_SERVER_URL,
                getString(R.string.default_server_url));

        String formListUrl = getString(R.string.default_odk_formlist);
        final String url =
            server + settings.getString(PreferencesActivity.KEY_FORMLIST_URL, formListUrl);
        Log.i(t, "Trying to get formList from: " + url);

        EditText username = (EditText) dialogView.findViewById(R.id.username_edit);
        String storedUsername = settings.getString(PreferencesActivity.KEY_USERNAME, null);
        username.setText(storedUsername);

        EditText password = (EditText) dialogView.findViewById(R.id.password_edit);
        String storedPassword = settings.getString(PreferencesActivity.KEY_PASSWORD, null);
        password.setText(storedPassword);

        b.setTitle(getString(R.string.server_requires_auth));
        b.setMessage(getString(R.string.server_auth_credentials, url));
        b.setView(dialogView);
        b.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
          @Override
          public void onClick(DialogInterface dialog, int which) {
            Collect.getInstance().getActivityLogger().logAction(this, "onCreateDialog.AUTH_DIALOG", "OK");

            EditText username = (EditText) dialogView.findViewById(R.id.username_edit);
            EditText password = (EditText) dialogView.findViewById(R.id.password_edit);

            Uri u = Uri.parse(url);

            WebUtils.addCredentials(username.getText().toString(), password.getText()
                .toString(), u.getHost());
            downloadFormList();
          }
        });
        b.setNegativeButton(getString(R.string.cancel),
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                Collect.getInstance().getActivityLogger().logAction(this, "onCreateDialog.AUTH_DIALOG", "Cancel");
                finish();
              }
            });

        b.setCancelable(false);
        mAlertShowing = false;
        return b.create();
    }
    return null;
  }


  /**
   * starts the task to download the selected forms, also shows progress dialog
   */
  @SuppressWarnings("unchecked")
  private void downloadSelectedFiles() {
    int totalCount = 0;
    ArrayList<FormDetails> filesToDownload = new ArrayList<FormDetails>();

    SparseBooleanArray sba = getListView().getCheckedItemPositions();
    for (int i = 0; i < getListView().getCount(); i++) {
      if (sba.get(i, false)) {
        HashMap<String, String> item =
            (HashMap<String, String>) getListAdapter().getItem(i);
        filesToDownload.add(mFormNamesAndURLs.get(item.get(FORMDETAIL_KEY)));
      }
    }
    totalCount = filesToDownload.size();

    Collect.getInstance().getActivityLogger().logAction(this, "downloadSelectedFiles", Integer.toString(totalCount));

    if (totalCount > 0) {
      // show dialog box
      showDialog(PROGRESS_DIALOG);

      mDownloadFormsTask = new DownloadFormsTask();
      mDownloadFormsTask.setDownloaderListener(this);
      mDownloadFormsTask.execute(filesToDownload);
    } else {
      Toast.makeText(getApplicationContext(), R.string.noselect_error, Toast.LENGTH_SHORT)
          .show();
    }
  }


  @Override
  public Object onRetainNonConfigurationInstance() {
    if (mDownloadFormsTask != null) {
      return mDownloadFormsTask;
    } else {
      return mDownloadFormListTask;
    }
  }


  @Override
  protected void onDestroy() {
    if (mDownloadFormListTask != null) {
      mDownloadFormListTask.setDownloaderListener(null);
    }
    if (mDownloadFormsTask != null) {
      mDownloadFormsTask.setDownloaderListener(null);
    }
    super.onDestroy();
  }


  @Override
  protected void onResume() {
    if (mDownloadFormListTask != null) {
      mDownloadFormListTask.setDownloaderListener(this);
    }
    if (mDownloadFormsTask != null) {
      mDownloadFormsTask.setDownloaderListener(this);
    }
    if (mAlertShowing) {
      createAlertDialog(mAlertTitle, mAlertMsg, mShouldExit);
    }
    super.onResume();
  }


  @Override
  protected void onPause() {
    if (mAlertDialog != null && mAlertDialog.isShowing()) {
      mAlertDialog.dismiss();
    }
    super.onPause();
  }


  /**
   * Called when the form list has finished downloading. results will either contain a set of <formname, formdetails>
   * tuples, or one tuple of DL.ERROR.MSG and the associated message.
   *
   * @param result
   */
  public void formListDownloadingComplete(HashMap<String, FormDetails> result) {
    dismissDialog(PROGRESS_DIALOG);
    mDownloadFormListTask.setDownloaderListener(null);
    mDownloadFormListTask = null;

    if (result == null) {
      Log.e(t, "Formlist Downloading returned null.  That shouldn't happen");
      // Just displayes "error occured" to the user, but this should never happen.
      createAlertDialog(getString(R.string.load_remote_form_error),
          getString(R.string.error_occured), EXIT);
      return;
    }

    if (result.containsKey(DownloadFormListTask.DL_AUTH_REQUIRED)) {
      // need authorization
      showDialog(AUTH_DIALOG);
    } else if (result.containsKey(DownloadFormListTask.DL_ERROR_MSG)) {
      // Download failed
      String dialogMessage =
          getString(R.string.list_failed_with_error,
              result.get(DownloadFormListTask.DL_ERROR_MSG).errorStr);
      String dialogTitle = getString(R.string.load_remote_form_error);
      createAlertDialog(dialogTitle, dialogMessage, DO_NOT_EXIT);
    } else {
      // Everything worked. Clear the list and add the results.
      mFormNamesAndURLs = result;

      mFormList.clear();

      ArrayList<String> ids = new ArrayList<String>(mFormNamesAndURLs.keySet());
      for (int i = 0; i < result.size(); i++) {
        String formDetailsKey = ids.get(i);
        FormDetails details = mFormNamesAndURLs.get(formDetailsKey);
        HashMap<String, String> item = new HashMap<String, String>();
        item.put(FORMNAME, details.formName);
        item.put(FORMID_DISPLAY,
            ((details.formVersion == null) ? "" : (getString(R.string.version) + " " + details.formVersion + " ")) +
                "ID: " + details.formID);
        item.put(FORMDETAIL_KEY, formDetailsKey);

        // Insert the new form in alphabetical order.
        if (mFormList.size() == 0) {
          mFormList.add(item);
        } else {
          int j;
          for (j = 0; j < mFormList.size(); j++) {
            HashMap<String, String> compareMe = mFormList.get(j);
            String name = compareMe.get(FORMNAME);
            if (name.compareTo(mFormNamesAndURLs.get(ids.get(i)).formName) > 0) {
              break;
            }
          }
          mFormList.add(j, item);
        }
      }
      mFormListAdapter.notifyDataSetChanged();
    }
  }


  /**
   * Creates an alert dialog with the given tite and message. If shouldExit is set to true, the activity will exit when
   * the user clicks "ok".
   *
   * @param title
   * @param message
   * @param shouldExit
   */
  private void createAlertDialog(String title, String message, final boolean shouldExit) {
    Collect.getInstance().getActivityLogger().logAction(this, "createAlertDialog", "show");
    mAlertDialog = new AlertDialog.Builder(this).create();
    mAlertDialog.setTitle(title);
    mAlertDialog.setMessage(message);
    DialogInterface.OnClickListener quitListener = new DialogInterface.OnClickListener() {
      @Override
      public void onClick(DialogInterface dialog, int i) {
        switch (i) {
          case DialogInterface.BUTTON_POSITIVE: // ok
            Collect.getInstance().getActivityLogger().logAction(this, "createAlertDialog", "OK");
            // just close the dialog
            mAlertShowing = false;
            // successful download, so quit
            if (shouldExit) {
              finish();
            }
            break;
        }
      }
    };
    mAlertDialog.setCancelable(false);
    mAlertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.ok), quitListener);
    mAlertMsg = message;
    mAlertTitle = title;
    mAlertShowing = true;
    mShouldExit = shouldExit;
    mAlertDialog.show();
  }


  @Override
  public void progressUpdate(String currentFile, int progress, int total) {
    mAlertMsg = getString(R.string.fetching_file, currentFile, progress, total);
    mProgressDialog.setMessage(mAlertMsg);
  }


  @Override
  public void formsDownloadingComplete(HashMap<FormDetails, String> result) {
    if (mDownloadFormsTask != null) {
      mDownloadFormsTask.setDownloaderListener(null);
    }

    if (mProgressDialog.isShowing()) {
      // should always be true here
      mProgressDialog.dismiss();
    }

    Set<FormDetails> keys = result.keySet();
    StringBuilder b = new StringBuilder();
    for (FormDetails k : keys) {
      b.append(k.formName +
          " (" +
          ((k.formVersion != null) ?
              (this.getString(R.string.version) + ": " + k.formVersion + " ")
              : "") +
          "ID: " + k.formID + ") - " +
          result.get(k));
      b.append("\n\n");
    }

    createAlertDialog(getString(R.string.download_forms_result), b.toString().trim(), EXIT);
  }

}
