/**
   * Write's the data to the sdcard, and updates the instances content provider. In theory we don't
   * have to write to disk, and this is where you'd add other methods.
   *
   * @param markCompleted
   * @return
   */
  private void exportData(boolean markCompleted) throws IOException, EncryptionException {
    FormController formController = Collect.getInstance().getFormController();

    publishProgress(Collect.getInstance().getString(R.string.survey_saving_collecting_message));

    ByteArrayPayload payload = formController.getFilledInFormXml();
    // write out xml
    String instancePath = formController.getInstancePath().getAbsolutePath();

    publishProgress(Collect.getInstance().getString(R.string.survey_saving_saving_message));

    exportXmlFile(payload, instancePath);

    // update the mUri. We have exported the reloadable instance, so update status...
    // Since we saved a reloadable instance, it is flagged as re-openable so that if any error
    // occurs during the packaging of the data for the server fails (e.g., encryption),
    // we can still reopen the filled-out form and re-save it at a later time.
    updateInstanceDatabase(true, true);

    if (markCompleted) {
      // now see if the packaging of the data for the server would make it
      // non-reopenable (e.g., encryption or send an SMS or other fraction of the form).
      boolean canEditAfterCompleted = formController.isSubmissionEntireForm();
      boolean isEncrypted = false;

      // build a submission.xml to hold the data being submitted
      // and (if appropriate) encrypt the files on the side

      // pay attention to the ref attribute of the submission profile...
      File instanceXml = formController.getInstancePath();
      File submissionXml = new File(instanceXml.getParentFile(), "submission.xml");

      payload = formController.getSubmissionXml();

      // write out submission.xml -- the data to actually submit to aggregate

      publishProgress(Collect.getInstance().getString(R.string.survey_saving_finalizing_message));

      exportXmlFile(payload, submissionXml.getAbsolutePath());

      // see if the form is encrypted and we can encrypt it...
      EncryptedFormInformation formInfo =
          EncryptionUtils.getEncryptedFormInformation(mUri, formController.getSubmissionMetadata());
      if (formInfo != null) {
        // if we are encrypting, the form cannot be reopened afterward
        canEditAfterCompleted = false;
        // and encrypt the submission (this is a one-way operation)...

        publishProgress(Collect.getInstance().getString(R.string.survey_saving_encrypting_message));

        EncryptionUtils.generateEncryptedSubmission(instanceXml, submissionXml, formInfo);
        isEncrypted = true;
      }

      // At this point, we have:
      // 1. the saved original instanceXml,
      // 2. all the plaintext attachments
      // 2. the submission.xml that is the completed xml (whether encrypting or not)
      // 3. all the encrypted attachments if encrypting (isEncrypted = true).
      //
      // NEXT:
      // 1. Update the instance database (with status complete).
      // 2. Overwrite the instanceXml with the submission.xml
      //    and remove the plaintext attachments if encrypting

      updateInstanceDatabase(false, canEditAfterCompleted);

      if (!canEditAfterCompleted) {
        // AT THIS POINT, there is no going back.  We are committed
        // to returning "success" (true) whether or not we can
        // rename "submission.xml" to instanceXml and whether or
        // not we can delete the plaintext media files.
        //
        // Handle the fall-out for a failed "submission.xml" rename
        // in the InstanceUploader task.  Leftover plaintext media
        // files are handled during form deletion.

        // delete the restore Xml file.
        if (!instanceXml.delete()) {
          String msg =
              "Error deleting "
                  + instanceXml.getAbsolutePath()
                  + " prior to renaming submission.xml";
          Log.e(t, msg);
          throw new IOException(msg);
        }

        // rename the submission.xml to be the instanceXml
        if (!submissionXml.renameTo(instanceXml)) {
          String msg = "Error renaming submission.xml to " + instanceXml.getAbsolutePath();
          Log.e(t, msg);
          throw new IOException(msg);
        }
      } else {
        // try to delete the submissionXml file, since it is
        // identical to the existing instanceXml file
        // (we don't need to delete and rename anything).
        if (!submissionXml.delete()) {
          String msg =
              "Error deleting " + submissionXml.getAbsolutePath() + " (instance is re-openable)";
          Log.w(t, msg);
        }
      }

      // if encrypted, delete all plaintext files
      // (anything not named instanceXml or anything not ending in .enc)
      if (isEncrypted) {
        if (!EncryptionUtils.deletePlaintextFiles(instanceXml)) {
          Log.e(t, "Error deleting plaintext files for " + instanceXml.getAbsolutePath());
        }
      }
    }
  }
  /**
   * Initialize {@link FormEntryController} with {@link FormDef} from binary or from XML. If given
   * an instance, it will be used to fill the {@link FormDef}.
   */
  @Override
  protected SaveResult doInBackground(Void... nothing) {
    SaveResult saveResult = new SaveResult();

    FormController formController = Collect.getInstance().getFormController();

    publishProgress(Collect.getInstance().getString(R.string.survey_saving_validating_message));

    try {
      int validateStatus = formController.validateAnswers(mMarkCompleted);
      if (validateStatus != FormEntryController.ANSWER_OK) {
        // validation failed, pass specific failure
        saveResult.setSaveResult(validateStatus);
        return saveResult;
      }
    } catch (Exception e) {
      Log.e(t, e.getMessage(), e);

      // SCTO-825
      // that means that we have a bad design
      // save the exception to be used in the error dialog.
      saveResult.setSaveErrorMessage(e.getMessage());
      saveResult.setSaveResult(SAVE_ERROR);
      return saveResult;
    }

    // check if the "Cancel" was hit and exit.
    if (isCancelled()) {
      return null;
    }

    if (mMarkCompleted) {
      formController.postProcessInstance();
    }

    Collect.getInstance()
        .getActivityLogger()
        .logInstanceAction(this, "save", Boolean.toString(mMarkCompleted));

    // close all open databases of external data.
    Collect.getInstance().getExternalDataManager().close();

    // if there is a meta/instanceName field, be sure we are using the latest value
    // just in case the validate somehow triggered an update.
    String updatedSaveName = formController.getSubmissionMetadata().instanceName;
    if (updatedSaveName != null) {
      mInstanceName = updatedSaveName;
    }

    try {
      exportData(mMarkCompleted);

      // attempt to remove any scratch file
      File shadowInstance = savepointFile(formController.getInstancePath());
      if (shadowInstance.exists()) {
        FileUtils.deleteAndReport(shadowInstance);
      }

      saveResult.setSaveResult(mSave ? SAVED_AND_EXIT : SAVED);
    } catch (Exception e) {
      Log.e(t, e.getMessage(), e);

      saveResult.setSaveErrorMessage(e.getMessage());
      saveResult.setSaveResult(SAVE_ERROR);
    }

    return saveResult;
  }
  private void updateInstanceDatabase(boolean incomplete, boolean canEditAfterCompleted) {

    FormController formController = Collect.getInstance().getFormController();

    // Update the instance database...
    ContentValues values = new ContentValues();
    if (mInstanceName != null) {
      values.put(InstanceColumns.DISPLAY_NAME, mInstanceName);
    }
    if (incomplete || !mMarkCompleted) {
      values.put(InstanceColumns.STATUS, InstanceProviderAPI.STATUS_INCOMPLETE);
    } else {
      values.put(InstanceColumns.STATUS, InstanceProviderAPI.STATUS_COMPLETE);
    }
    // update this whether or not the status is complete...
    values.put(InstanceColumns.CAN_EDIT_WHEN_COMPLETE, Boolean.toString(canEditAfterCompleted));

    // If FormEntryActivity was started with an Instance, just update that instance
    if (Collect.getInstance()
        .getContentResolver()
        .getType(mUri)
        .equals(InstanceColumns.CONTENT_ITEM_TYPE)) {
      int updated = Collect.getInstance().getContentResolver().update(mUri, values, null, null);
      if (updated > 1) {
        Log.w(t, "Updated more than one entry, that's not good: " + mUri.toString());
      } else if (updated == 1) {
        Log.i(t, "Instance successfully updated");
      } else {
        Log.e(t, "Instance doesn't exist but we have its Uri!! " + mUri.toString());
      }
    } else if (Collect.getInstance()
        .getContentResolver()
        .getType(mUri)
        .equals(FormsColumns.CONTENT_ITEM_TYPE)) {
      // If FormEntryActivity was started with a form, then it's likely the first time we're
      // saving.
      // However, it could be a not-first time saving if the user has been using the manual
      // 'save data' option from the menu. So try to update first, then make a new one if that
      // fails.
      String instancePath = formController.getInstancePath().getAbsolutePath();
      String where = InstanceColumns.INSTANCE_FILE_PATH + "=?";
      String[] whereArgs = {instancePath};
      int updated =
          Collect.getInstance()
              .getContentResolver()
              .update(InstanceColumns.CONTENT_URI, values, where, whereArgs);
      if (updated > 1) {
        Log.w(t, "Updated more than one entry, that's not good: " + instancePath);
      } else if (updated == 1) {
        Log.i(t, "Instance found and successfully updated: " + instancePath);
        // already existed and updated just fine
      } else {
        Log.i(t, "No instance found, creating");
        // Entry didn't exist, so create it.
        Cursor c = null;
        try {
          // retrieve the form definition...
          c = Collect.getInstance().getContentResolver().query(mUri, null, null, null, null);
          c.moveToFirst();
          String jrformid = c.getString(c.getColumnIndex(FormsColumns.JR_FORM_ID));
          String jrversion = c.getString(c.getColumnIndex(FormsColumns.JR_VERSION));
          String formname = c.getString(c.getColumnIndex(FormsColumns.DISPLAY_NAME));
          String submissionUri = null;
          if (!c.isNull(c.getColumnIndex(FormsColumns.SUBMISSION_URI))) {
            submissionUri = c.getString(c.getColumnIndex(FormsColumns.SUBMISSION_URI));
          }

          // add missing fields into values
          values.put(InstanceColumns.INSTANCE_FILE_PATH, instancePath);
          values.put(InstanceColumns.SUBMISSION_URI, submissionUri);
          if (mInstanceName != null) {
            values.put(InstanceColumns.DISPLAY_NAME, mInstanceName);
          } else {
            values.put(InstanceColumns.DISPLAY_NAME, formname);
          }
          values.put(InstanceColumns.JR_FORM_ID, jrformid);
          values.put(InstanceColumns.JR_VERSION, jrversion);
        } finally {
          if (c != null) {
            c.close();
          }
        }
        mUri =
            Collect.getInstance().getContentResolver().insert(InstanceColumns.CONTENT_URI, values);
      }
    }
  }