/*
 * Copyright (C) 2013 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.path.episample.android.tasks;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.io.FileUtils;
import org.opendatakit.aggregate.odktables.rest.KeyValueStoreConstants;
import org.opendatakit.aggregate.odktables.rest.entity.Column;
import org.path.common.android.data.ColumnDefinition;
import org.path.common.android.data.KeyValueStoreEntry;
import org.path.common.android.database.CensusDatabaseHelper;
import org.path.common.android.database.DatabaseFactory;
import org.path.common.android.logic.FormInfo;
import org.path.common.android.provider.CensusColumns;
import org.path.common.android.provider.FormsColumns;
import org.path.common.android.utilities.ODKDatabaseUtils;
import org.path.common.android.utilities.ODKFileUtils;
import org.path.common.android.utilities.WebLogger;
import org.path.episample.android.R;
import org.path.episample.android.application.Survey;
import org.path.episample.android.listeners.InitializationListener;
import org.path.episample.android.provider.FormsProviderAPI;
import org.path.episample.android.utilities.AlarmUtil;

import android.app.Application;
import android.content.ContentValues;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.AsyncTask;

/**
 * Background task for exploding the built-in zipfile resource into the
 * framework directory of the application and doing forms discovery on this
 * appName.
 *
 * @author mitchellsundt@gmail.com
 */
public class InitializationTask extends AsyncTask<Void, String, ArrayList<String>> {

  private static final String t = "InitializationTask";

  private Application appContext;
  private InitializationListener mStateListener;
  private String appName;

  private boolean mSuccess = false;
  private ArrayList<String> mResult = new ArrayList<String>();

  private boolean mPendingSuccess = false;

  @Override
  protected ArrayList<String> doInBackground(Void... values) {
    mPendingSuccess = true;

    String message = null;
    ArrayList<String> result = new ArrayList<String>();
    
    boolean censusFormConfigRequired = false;
    
    ODKFileUtils.createFolder(ODKFileUtils.getEpiSampleBackupFolder());
    AlarmUtil.setAlarm(getApplication());
    
    // ///////////////////////////////////////////////
    // check that the framework zip has been exploded
    if (!ODKFileUtils.isConfiguredSurveyApp(appName, Survey.getInstance().getVersionCodeString())) {
      publishProgress(appContext.getString(R.string.expansion_unzipping_begins), null);

      extractFromRawZip(R.raw.frameworkzip, true, result);
      extractFromRawZip(R.raw.assetszip, false, result);
      extractFromRawZip(R.raw.tableszip, false, result);//included by belendia to extract census form in tables folder
      censusFormConfigRequired = true;
      
      ODKFileUtils.assertConfiguredSurveyApp(appName, Survey.getInstance().getVersionCodeString());
    }
    
    //included by belendia to extract census form in tables folder
    
    if(ODKFileUtils.isCensusFormExtracted(appName) == false) {
    	extractFromRawZip(R.raw.tableszip, false, result);
    	censusFormConfigRequired = true;
    }

    // /////////////////////////////////////////
    // /////////////////////////////////////////
    // /////////////////////////////////////////
    // register the framework form
    // TODO: make this go away!
    updateFormDir(new File(ODKFileUtils.getFrameworkFolder(appName)), false,
        ODKFileUtils.getStaleFrameworkFolder(appName) + File.separator);

    // and now scan for new forms...

    String completionString = appContext.getString(R.string.searching_for_form_defs);
    publishProgress(completionString, null);

    File tablesDir = new File(ODKFileUtils.getTablesFolder(appName));

    File[] tableIdDirs = tablesDir.listFiles(new FileFilter() {

      @Override
      public boolean accept(File pathname) {
        return pathname.isDirectory();
      }
    });

    List<File> formDirs = new ArrayList<File>();
    for (File tableIdDir : tableIdDirs) {
      String tableId = tableIdDir.getName();

      File formDir = new File(ODKFileUtils.getFormsFolder(appName, tableId));
      File[] formIdDirs = formDir.listFiles(new FileFilter() {

        @Override
        public boolean accept(File pathname) {
          File formDef = new File(pathname, ODKFileUtils.FORMDEF_JSON_FILENAME);
          return pathname.isDirectory() && formDef.exists() && formDef.isFile();
        }
      });

      if (formIdDirs != null) {
        formDirs.addAll(Arrays.asList(formIdDirs));
      }
    }

    // /////////////////////////////////////////
    // remove forms that no longer exist
    // remove the forms that haven't changed
    // from the discovered list
    removeStaleFormInfo(formDirs);

    // this is the complete list of forms we need to scan and possibly add
    // to the FormsProvider
    for (int i = 0; i < formDirs.size(); ++i) {
      File formDir = formDirs.get(i);
      // specifically target this form...
      WebLogger.getLogger(appName).i(t, "updateFormInfo: form: " + formDir.getAbsolutePath());

      String examString = appContext.getString(R.string.updating_form_information,
          formDir.getName(), i + 1, formDirs.size());
      publishProgress(examString, null);

      updateFormDir(formDir, true, ODKFileUtils.getStaleFormsFolder(appName) + File.separator);
    }

    //included by belendia to register census user defined columns and create census table
    if(censusFormConfigRequired == true || isCensusTableCreated()==false) {
    	registerCensusForm();
    }
    
    return result;
  }

  private boolean isCensusTableCreated() {
		SQLiteDatabase db = null;

		boolean censusTableCreated = false;
		try {
			// db = DatabaseFactory.getInstanceDatabase(context, appName);
			db = DatabaseFactory.get().getDatabase(getApplication(),
					getAppName());

			ArrayList<String> tableIds = ODKDatabaseUtils.get().getAllTableIds(db);

			if(tableIds != null && tableIds.contains(CensusDatabaseHelper.CENSUS_DATABASES_TABLE)) {
				censusTableCreated = true;
			}

		} finally {

			if (db != null) {
				db.close();
			}
		}
		
		return censusTableCreated;
	}

	private void registerCensusForm() {
		// create a census table in ODK Survey's database
		SQLiteDatabase db = null;

		try {
			// db = DatabaseFactory.getInstanceDatabase(context, appName);
			db = DatabaseFactory.get().getDatabase(getApplication(),
					getAppName());

			List<Column> columns = CensusColumns.USER_DEFINED_COLUMNS;

			ArrayList<ColumnDefinition> orderedDefs = ColumnDefinition
					.buildColumnDefinitions(appName,
							CensusDatabaseHelper.CENSUS_DATABASES_TABLE,
							columns);
			db.beginTransaction();
			ODKDatabaseUtils.get().createDBTableWithColumns(db, appName,
					CensusDatabaseHelper.CENSUS_DATABASES_TABLE, orderedDefs);
			db.setTransactionSuccessful();
			
			registerCensusFormInstanceName(db);
			
		} catch(Exception ex) {
			
		} finally {

			if (db != null) {
				db.endTransaction();
				db.close();
			}
		}
	}

	private void registerCensusFormInstanceName(SQLiteDatabase db) {
		String tableId = CensusDatabaseHelper.CENSUS_DATABASES_TABLE;

	      File formDir = new File(ODKFileUtils.getFormsFolder(appName, tableId));
	      File[] formIdDirs = formDir.listFiles(new FileFilter() {

	        @Override
	        public boolean accept(File pathname) {
	          File formDef = new File(pathname, ODKFileUtils.FORMDEF_JSON_FILENAME);
	          return pathname.isDirectory() && formDef.exists() && formDef.isFile();
	        }
	      });
	      if(formIdDirs.length > 0) {
	    	  File censusJsonFile = new File(formIdDirs[0], ODKFileUtils.FORMDEF_JSON_FILENAME);
	    	  FormInfo fi = new FormInfo(appContext, appName, censusJsonFile);
	    	  if(fi.instanceName != null && fi.instanceName.length()>0) {
	    		  
	    		  KeyValueStoreEntry entry = new KeyValueStoreEntry();
	    		  entry.tableId = CensusDatabaseHelper.CENSUS_DATABASES_TABLE;
	    		  entry.partition = KeyValueStoreConstants.PARTITION_TABLE;
	    		  entry.aspect = KeyValueStoreConstants.ASPECT_DEFAULT;
	    		  entry.key = KeyValueStoreConstants.XML_INSTANCE_NAME;
	    		  entry.type = "string";
	    		  entry.value = fi.instanceName;
	    		  
	    		  ODKDatabaseUtils.get().replaceDBTableMetadata(db, entry);
	    	  }
	      }
	}
	
  private final void extractFromRawZip(int resourceId, boolean overwrite, ArrayList<String> result) {
    String message = null;
    AssetFileDescriptor fd = null;
    try {
      fd = appContext.getResources().openRawResourceFd(resourceId);
      final long size = fd.getLength(); // apparently over-counts by 2x?
      InputStream rawInputStream = null;
      try {
        rawInputStream = fd.createInputStream();
        ZipInputStream zipInputStream = null;
        ZipEntry entry = null;
        try {

          // count the number of files in the zip
          zipInputStream = new ZipInputStream(rawInputStream);
          int totalFiles = 0;
          while ((entry = zipInputStream.getNextEntry()) != null) {
            message = null;
            if (isCancelled()) {
              message = "cancelled";
              result.add(entry.getName() + " " + message);
              break;
            }
            ++totalFiles;
          }
          zipInputStream.close();

          // and re-open the stream, reading it this time...
          fd = appContext.getResources().openRawResourceFd(resourceId);
          rawInputStream = fd.createInputStream();
          zipInputStream = new ZipInputStream(rawInputStream);

          long bytesProcessed = 0L;
          long lastBytesProcessedThousands = 0L;
          int nFiles = 0;
          while ((entry = zipInputStream.getNextEntry()) != null) {
            message = null;
            if (isCancelled()) {
              message = "cancelled";
              result.add(entry.getName() + " " + message);
              break;
            }
            ++nFiles;
            File tempFile = new File(ODKFileUtils.getAppFolder(appName), entry.getName());
            String formattedString = appContext.getString(
                R.string.expansion_unzipping_without_detail, entry.getName(), nFiles, totalFiles);
            String detail;
            if (entry.isDirectory()) {
              detail = appContext.getString(R.string.expansion_create_dir_detail);
              publishProgress(formattedString, detail);
              tempFile.mkdirs();
            } else if (overwrite || !tempFile.exists()) {
              int bufferSize = 8192;
              OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile, false),
                  bufferSize);
              byte buffer[] = new byte[bufferSize];
              int bread;
              while ((bread = zipInputStream.read(buffer)) != -1) {
                bytesProcessed += bread;
                long curThousands = (bytesProcessed / 1000L);
                if (curThousands != lastBytesProcessedThousands) {
                  detail = appContext.getString(R.string.expansion_unzipping_detail,
                      bytesProcessed, size);
                  publishProgress(formattedString, detail);
                  lastBytesProcessedThousands = curThousands;
                }
                out.write(buffer, 0, bread);
              }
              out.flush();
              out.close();

              detail = appContext.getString(R.string.expansion_unzipping_detail, bytesProcessed,
                  size);
              publishProgress(formattedString, detail);
            }
            WebLogger.getLogger(appName).i(t, "Extracted ZipEntry: " + entry.getName());
          }

          String completionString = appContext.getString(R.string.expansion_unzipping_complete,
              totalFiles);
          publishProgress(completionString, null);
        } catch (IOException e) {
          WebLogger.getLogger(appName).printStackTrace(e);
          mPendingSuccess = false;
          if (e.getCause() != null) {
            message = e.getCause().getMessage();
          } else {
            message = e.getMessage();
          }
          if (entry != null) {
            result.add(entry.getName() + " " + message);
          } else {
            result.add("Error accessing zipfile resource " + message);
          }
        } finally {
          if (zipInputStream != null) {
            try {
              zipInputStream.close();
            } catch (IOException e) {
              WebLogger.getLogger(appName).printStackTrace(e);
              WebLogger.getLogger(appName).e(t, "Closing of ZipFile failed: " + e.toString());
            }
          }
        }
      } catch (Exception e) {
        WebLogger.getLogger(appName).printStackTrace(e);
        mPendingSuccess = false;
        if (e.getCause() != null) {
          message = e.getCause().getMessage();
        } else {
          message = e.getMessage();
        }
        result.add("Error accessing zipfile resource " + message);
      } finally {
        if (rawInputStream != null) {
          try {
            rawInputStream.close();
          } catch (IOException e) {
            WebLogger.getLogger(appName).printStackTrace(e);
          }
        }
      }
    } finally {
      if (fd != null) {
        try {
          fd.close();
        } catch (IOException e) {
          WebLogger.getLogger(appName).printStackTrace(e);
        }
      } else {
        result.add("Error accessing zipfile resource.");
      }
    }
  }

  /**
   * Remove definitions from the Forms database that are no longer present on
   * disk.
   */
  private final void removeStaleFormInfo(List<File> discoveredFormDefDirs) {
    Uri formsProviderContentUri = Uri.parse("content://" + FormsProviderAPI.AUTHORITY);

    String completionString = appContext.getString(R.string.searching_for_deleted_forms);
    publishProgress(completionString, null);

    WebLogger.getLogger(appName).i(t, "removeStaleFormInfo " + appName + " begin");
    ArrayList<Uri> badEntries = new ArrayList<Uri>();
    Cursor c = null;
    try {
      c = appContext.getContentResolver().query(
          Uri.withAppendedPath(formsProviderContentUri, appName), null, null, null, null);

      if (c == null) {
        WebLogger.getLogger(appName).w(t,
            "removeStaleFormInfo " + appName + " null cursor returned from query.");
        return;
      }

      if (c.moveToFirst()) {
        do {
          String id = ODKDatabaseUtils.get().getIndexAsString(c,
              c.getColumnIndex(FormsColumns.FORM_ID));
          Uri otherUri = Uri.withAppendedPath(
              Uri.withAppendedPath(formsProviderContentUri, appName), id);

          String examString = appContext.getString(R.string.examining_form, id);
          publishProgress(examString, null);

          int appRelativeFormMediaPathIdx = c
              .getColumnIndex(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH);
          if (appRelativeFormMediaPathIdx == -1) {
            throw new IllegalStateException("Column " + FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH
                + " missing from database table. Incompatible versions?");
          }
          String appRelativeFormMediaPath = ODKDatabaseUtils.get().getIndexAsString(c,
              appRelativeFormMediaPathIdx);
          File f = ODKFileUtils.asAppFile(appName, appRelativeFormMediaPath);
          File formDefJson = new File(f, ODKFileUtils.FORMDEF_JSON_FILENAME);
          if (!f.exists() || !f.isDirectory() || !formDefJson.exists() || !formDefJson.isFile()) {
            // the form definition does not exist
            badEntries.add(otherUri);
            continue;
          } else {
            // ////////////////////////////////
            // formdef.json exists. See if it is
            // unchanged...
            String json_md5 = ODKDatabaseUtils.get().getIndexAsString(c,
                c.getColumnIndex(FormsColumns.JSON_MD5_HASH));
            String fileMd5 = ODKFileUtils.getMd5Hash(appName, formDefJson);
            if (json_md5.equals(fileMd5)) {
              // it is unchanged -- no need to rescan it
              discoveredFormDefDirs.remove(f);
            }
          }
        } while (c.moveToNext());
      }
    } catch (Exception e) {
      WebLogger.getLogger(appName).e(t,
          "removeStaleFormInfo " + appName + " exception: " + e.toString());
      WebLogger.getLogger(appName).printStackTrace(e);
    } finally {
      if (c != null && !c.isClosed()) {
        c.close();
      }
    }

    // delete the other entries (and directories)
    for (Uri badUri : badEntries) {
      WebLogger.getLogger(appName).i(t,
          "removeStaleFormInfo: " + appName + " deleting: " + badUri.toString());
      try {
        appContext.getContentResolver().delete(badUri, null, null);
      } catch (Exception e) {
        WebLogger.getLogger(appName).e(t,
            "removeStaleFormInfo " + appName + " exception: " + e.toString());
        WebLogger.getLogger(appName).printStackTrace(e);
        // and continue -- don't throw an error
      }
    }
    WebLogger.getLogger(appName).i(t, "removeStaleFormInfo " + appName + " end");
  }

  /**
   * Construct a directory name that is unused in the stale path and move
   * mediaPath there.
   *
   * @param mediaPath
   * @param baseStaleMediaPath
   *          -- the stale directory corresponding to the mediaPath container
   * @return the directory within the stale directory that the mediaPath was
   *         renamed to.
   * @throws IOException
   */
  private final File moveToStaleDirectory(File mediaPath, String baseStaleMediaPath)
      throws IOException {
    // we have a 'framework' form in the forms directory.
    // Move it to the stale directory.
    // Delete all records referring to this directory.
    int i = 0;
    File tempMediaPath = new File(baseStaleMediaPath + mediaPath.getName() + "_"
        + Integer.toString(i));
    while (tempMediaPath.exists()) {
      ++i;
      tempMediaPath = new File(baseStaleMediaPath + mediaPath.getName() + "_" + Integer.toString(i));
    }
    FileUtils.moveDirectory(mediaPath, tempMediaPath);
    return tempMediaPath;
  }

  /**
   * Scan the given formDir and update the Forms database. If it is the
   * formsFolder, then any 'framework' forms should be forbidden. If it is not
   * the formsFolder, only 'framework' forms should be allowed
   *
   * @param mediaPath
   *          -- full formDir
   * @param isFormsFolder
   * @param baseStaleMediaPath
   *          -- path prefix to the stale forms/framework directory.
   */
  private final void updateFormDir(File formDir, boolean isFormsFolder, String baseStaleMediaPath) {
    Uri formsProviderContentUri = Uri.parse("content://" + FormsProviderAPI.AUTHORITY);
    String formDirectoryPath = formDir.getAbsolutePath();
    WebLogger.getLogger(appName).i(t, "updateFormDir: " + formDirectoryPath);

    boolean needUpdate = true;
    FormInfo fi = null;
    Uri uri = null;
    Cursor c = null;
    try {
      File formDef = new File(formDir, ODKFileUtils.FORMDEF_JSON_FILENAME);

      String selection = FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH + "=?";
      String[] selectionArgs = { ODKFileUtils.asRelativePath(appName, formDir) };
      c = appContext.getContentResolver().query(
          Uri.withAppendedPath(formsProviderContentUri, appName), null, selection, selectionArgs,
          null);

      if (c == null) {
        WebLogger.getLogger(appName).w(t,
            "updateFormDir: " + formDirectoryPath + " null cursor -- cannot update!");
        return;
      }

      if (c.getCount() > 1) {
        c.close();
        WebLogger.getLogger(appName).w(t,
            "updateFormDir: " + formDirectoryPath
                + " multiple records from cursor -- delete all and restore!");
        // we have multiple records for this one directory.
        // Rename the directory. Delete the records, and move the
        // directory back.
        File tempMediaPath = moveToStaleDirectory(formDir, baseStaleMediaPath);
        appContext.getContentResolver().delete(
            Uri.withAppendedPath(formsProviderContentUri, appName), selection, selectionArgs);
        FileUtils.moveDirectory(tempMediaPath, formDir);
        // we don't know which of the above records was correct, so
        // reparse this to get ground truth...
        fi = new FormInfo(appContext, appName, formDef);
      } else if (c.getCount() == 1) {
        c.moveToFirst();
        String id = ODKDatabaseUtils.get().getIndexAsString(c,
            c.getColumnIndex(FormsColumns.FORM_ID));
        uri = Uri.withAppendedPath(Uri.withAppendedPath(formsProviderContentUri, appName), id);
        Long lastModificationDate = ODKDatabaseUtils.get().getIndexAsType(c, Long.class,
            c.getColumnIndex(FormsColumns.DATE));
        Long formDefModified = ODKFileUtils.getMostRecentlyModifiedDate(formDir);
        if (lastModificationDate.compareTo(formDefModified) == 0) {
          WebLogger.getLogger(appName).i(t,
              "updateFormDir: " + formDirectoryPath + " formDef unchanged");
          fi = new FormInfo(appName, c, false);
          needUpdate = false;
        } else {
          WebLogger.getLogger(appName).i(t,
              "updateFormDir: " + formDirectoryPath + " formDef revised");
          fi = new FormInfo(appContext, appName, formDef);
          needUpdate = true;
        }
      } else if (c.getCount() == 0) {
        // it should be new, try to parse it...
        fi = new FormInfo(appContext, appName, formDef);
      }

      // Enforce that a formId == FormsColumns.COMMON_BASE_FORM_ID can only be
      // in the Framework directory
      // and that no other formIds can be in that directory. If this is not the
      // case, ensure that
      // this record is moved to the stale directory.

      if (fi.formId.equals(FormsColumns.COMMON_BASE_FORM_ID)) {
        if (isFormsFolder) {
          // we have a 'framework' form in the forms directory.
          // Move it to the stale directory.
          // Delete all records referring to this directory.
          moveToStaleDirectory(formDir, baseStaleMediaPath);
          appContext.getContentResolver().delete(
              Uri.withAppendedPath(formsProviderContentUri, appName), selection, selectionArgs);
          return;
        }
      } else {
        if (!isFormsFolder) {
          // we have a non-'framework' form in the framework directory.
          // Move it to the stale directory.
          // Delete all records referring to this directory.
          moveToStaleDirectory(formDir, baseStaleMediaPath);
          appContext.getContentResolver().delete(
              Uri.withAppendedPath(formsProviderContentUri, appName), selection, selectionArgs);
          return;
        }
      }
    } catch (SQLiteException e) {
      WebLogger.getLogger(appName).printStackTrace(e);
      WebLogger.getLogger(appName).e(t,
          "updateFormDir: " + formDirectoryPath + " exception: " + e.toString());
      return;
    } catch (IOException e) {
      WebLogger.getLogger(appName).printStackTrace(e);
      WebLogger.getLogger(appName).e(t,
          "updateFormDir: " + formDirectoryPath + " exception: " + e.toString());
      return;
    } catch (IllegalArgumentException e) {
      WebLogger.getLogger(appName).printStackTrace(e);
      WebLogger.getLogger(appName).e(t,
          "updateFormDir: " + formDirectoryPath + " exception: " + e.toString());
      try {
        FileUtils.deleteDirectory(formDir);
        WebLogger.getLogger(appName).i(t,
            "updateFormDir: " + formDirectoryPath + " Removing -- unable to parse formDef file: "
                + e.toString());
      } catch (IOException e1) {
        WebLogger.getLogger(appName).printStackTrace(e1);
        WebLogger.getLogger(appName).i(t,
            "updateFormDir: " + formDirectoryPath
                + " Removing -- unable to delete form directory: " + formDir.getName() + " error: "
                + e.toString());
      }
      return;
    } finally {
      if (c != null && !c.isClosed()) {
        c.close();
      }
    }

    // Delete any entries matching this FORM_ID, but not the same directory and
    // which have a version that is equal to or older than this version.
    String selection;
    String[] selectionArgs;
    if (fi.formVersion == null) {
      selection = FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH + "!=? AND " + FormsColumns.FORM_ID
          + "=? AND " + FormsColumns.FORM_VERSION + " IS NULL";
      String[] temp = { ODKFileUtils.asRelativePath(appName, formDir), fi.formId };
      selectionArgs = temp;
    } else {
      selection = FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH + "!=? AND " + FormsColumns.FORM_ID
          + "=? AND " + "( " + FormsColumns.FORM_VERSION + " IS NULL" + " OR "
          + FormsColumns.FORM_VERSION + " <=?" + " )";
      String[] temp = { ODKFileUtils.asRelativePath(appName, formDir), fi.formId, fi.formVersion };
      selectionArgs = temp;
    }

    try {
      appContext.getContentResolver().delete(
          Uri.withAppendedPath(formsProviderContentUri, appName), selection, selectionArgs);
    } catch (SQLiteException e) {
      WebLogger.getLogger(appName).printStackTrace(e);
      WebLogger.getLogger(appName).e(t,
          "updateFormDir: " + formDirectoryPath + " exception: " + e.toString());
      return;
    } catch (Exception e) {
      WebLogger.getLogger(appName).printStackTrace(e);
      WebLogger.getLogger(appName).e(t,
          "updateFormDir: " + formDirectoryPath + " exception: " + e.toString());
      return;
    }

    // See if we have any newer versions already present...
    if (fi.formVersion == null) {
      selection = FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH + "!=? AND " + FormsColumns.FORM_ID
          + "=? AND " + FormsColumns.FORM_VERSION + " IS NOT NULL";
      String[] temp = { ODKFileUtils.asRelativePath(appName, formDir), fi.formId };
      selectionArgs = temp;
    } else {
      selection = FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH + "!=? AND " + FormsColumns.FORM_ID
          + "=? AND " + FormsColumns.FORM_VERSION + " >?";
      String[] temp = { ODKFileUtils.asRelativePath(appName, formDir), fi.formId, fi.formVersion };
      selectionArgs = temp;
    }

    try {
      Uri uriApp = Uri.withAppendedPath(formsProviderContentUri, appName);
      c = appContext.getContentResolver().query(uriApp, null, selection, selectionArgs, null);

      if (c == null) {
        WebLogger.getLogger(appName).w(t,
            "updateFormDir: " + uriApp.toString() + " null cursor -- cannot update!");
        return;
      }

      if (c.moveToFirst()) {
        // the directory we are processing is stale -- move it to stale
        // directory
        moveToStaleDirectory(formDir, baseStaleMediaPath);
        return;
      }
    } catch (SQLiteException e) {
      WebLogger.getLogger(appName).printStackTrace(e);
      WebLogger.getLogger(appName).e(t,
          "updateFormDir: " + formDirectoryPath + " exception: " + e.toString());
      return;
    } catch (IOException e) {
      WebLogger.getLogger(appName).printStackTrace(e);
      WebLogger.getLogger(appName).e(t,
          "updateFormDir: " + formDirectoryPath + " exception: " + e.toString());
      return;
    } finally {
      if (c != null && !c.isClosed()) {
        c.close();
      }
    }

    if (!needUpdate) {
      // no change...
      return;
    }

    try {
      // Now insert or update the record...
      ContentValues v = new ContentValues();
      String[] values = fi.asRowValues(FormsColumns.formsDataColumnNames);
      for (int i = 0; i < values.length; ++i) {
        v.put(FormsColumns.formsDataColumnNames[i], values[i]);
      }

      if (uri != null) {
        int count = appContext.getContentResolver().update(uri, v, null, null);
        WebLogger.getLogger(appName).i(t,
            "updateFormDir: " + formDirectoryPath + " " + count + " records successfully updated");
      } else {
        appContext.getContentResolver().insert(
            Uri.withAppendedPath(formsProviderContentUri, appName), v);
        WebLogger.getLogger(appName).i(t,
            "updateFormDir: " + formDirectoryPath + " one record successfully inserted");
      }

    } catch (SQLiteException ex) {
      WebLogger.getLogger(appName).printStackTrace(ex);
      WebLogger.getLogger(appName).e(t,
          "updateFormDir: " + formDirectoryPath + " exception: " + ex.toString());
      return;
    }
  }

  @Override
  protected void onPostExecute(ArrayList<String> result) {
    synchronized (this) {
      mResult = result;
      mSuccess = mPendingSuccess;
      if (mStateListener != null) {
        mStateListener.initializationComplete(mSuccess, mResult);
      }
    }
  }

  @Override
  protected void onCancelled(ArrayList<String> result) {
    synchronized (this) {
      // can be null if cancelled before task executes
      mResult = (result == null) ? new ArrayList<String>() : result;
      mSuccess = false;
      if (mStateListener != null) {
        mStateListener.initializationComplete(mSuccess, mResult);
      }
    }
  }

  @Override
  protected void onProgressUpdate(String... values) {
    synchronized (this) {
      if (mStateListener != null) {
        // update progress and total
        mStateListener.initializationProgressUpdate(values[0]
            + ((values[1] != null) ? "\n(" + values[1] + ")" : ""));
      }
    }

  }

  public boolean getOverallSuccess() {
    return mSuccess;
  }

  public ArrayList<String> getResult() {
    return mResult;
  }

  public void setInitializationListener(InitializationListener sl) {
    synchronized (this) {
      mStateListener = sl;
    }
  }

  public void setAppName(String appName) {
    synchronized (this) {
      this.appName = appName;
    }
  }

  public String getAppName() {
    return appName;
  }

  public void setApplication(Application appContext) {
    synchronized (this) {
      this.appContext = appContext;
    }
  }

  public Application getApplication() {
    return appContext;
  }

}
