/*
 * Copyright (C) 2011 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.application;

import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Environment;
import android.preference.PreferenceManager;

import org.odk.collect.android.R;
import org.odk.collect.android.activities.DrawActivity;
import org.odk.collect.android.database.ActivityLogger;
import org.odk.collect.android.external.ExternalDataManager;
import org.odk.collect.android.logic.FormController;
import org.odk.collect.android.logic.PropertyManager;
import org.odk.collect.android.preferences.PreferencesActivity;
import org.odk.collect.android.utilities.AgingCredentialsProvider;
import org.odk.collect.android.widgets.ImageWidget;
import org.opendatakit.httpclientandroidlib.client.CookieStore;
import org.opendatakit.httpclientandroidlib.client.CredentialsProvider;
import org.opendatakit.httpclientandroidlib.client.protocol.ClientContext;
import org.opendatakit.httpclientandroidlib.impl.client.BasicCookieStore;
import org.opendatakit.httpclientandroidlib.protocol.BasicHttpContext;
import org.opendatakit.httpclientandroidlib.protocol.HttpContext;

import java.io.File;
import java.io.IOException;

/**
 * Extends the Application class to implement
 *
 * @author carlhartung
 */
public class Collect extends Application {
  // Storage paths
  public static String ODK_ROOT;
  public static String PUBLIC_ODK_ROOT;
  public static String FORMS_PATH;
  public static String INSTANCES_PATH;
  public static String CACHE_PATH;
  public static String PUBLIC_CACHE_PATH;
  public static String METADATA_PATH;
  public static String TMPFILE_PATH;
  public static String TMP_CAMERA_FILE_PATH;
  public static String TMPDRAWFILE_PATH;
  public static String TMPXML_PATH;
  public static String LOG_PATH;

  public static final String DEFAULT_FONTSIZE = "21";

  // share all session cookies across all sessions...
  private CookieStore cookieStore = new BasicCookieStore();
  // retain credentials for 7 minutes...
  private CredentialsProvider credsProvider = new AgingCredentialsProvider(7 * 60 * 1000);
  private ActivityLogger mActivityLogger;
  private FormController mFormController = null;
  private ExternalDataManager externalDataManager;

  private static Collect singleton = null;
  private DrawActivity.DrawActivityDelegate drawActivityDelegate;
  private ImageWidget.ViewImageActivityDelagate viewImageActivityDelagate;

  /**
   * @return the singleton instance of the class.
   */
  public static Collect getInstance() {
    return singleton;
  }

  /**
   * @return the current logger for the Activities.
   */
  public ActivityLogger getActivityLogger() {
    return mActivityLogger;
  }

  /**
   * @return the current controller for the loaded form.
   */
  public FormController getFormController() {
    return mFormController;
  }

  /**
   * @param controller
   *     the new controller for the current form.
   */
  public void setFormController(FormController controller) {
    mFormController = controller;
  }

  /**
   * @return allows access to external data sources.
   */
  public ExternalDataManager getExternalDataManager() {
    return externalDataManager;
  }

  /**
   * @param externalDataManager
   *     allows access to external data sources.
   */
  public void setExternalDataManager(ExternalDataManager externalDataManager) {
    this.externalDataManager = externalDataManager;
  }

  /**
   * @param drawActivityDelegate
   *     the delegate for project specific DrawActivities.
   */
  public void setDrawActivityDelegate(final DrawActivity.DrawActivityDelegate drawActivityDelegate) {
    this.drawActivityDelegate = drawActivityDelegate;
  }

  /**
   * @return the font size for the question text.
   */
  public static int getQuestionFontsize() {
    SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(Collect.getInstance());
    String question_font = settings.getString(PreferencesActivity.KEY_FONT_SIZE, Collect.DEFAULT_FONTSIZE);
    return Integer.valueOf(question_font);
  }

  /**
   * @return a String containing a concatenated version of the app name and version information.
   */
  public String getVersionedAppName() {
    String versionDetail = "";
    try {
      PackageInfo pinfo;
      pinfo = getPackageManager().getPackageInfo(getPackageName(), 0);
      int versionNumber = pinfo.versionCode;
      String versionName = pinfo.versionName;
      versionDetail = " " + versionName + " (" + versionNumber + ")";
    } catch (NameNotFoundException e) {
      // Nothing to be done, ignore the error.
    }
    return getString(R.string.app_name) + versionDetail;
  }

  /**
   * Creates required directories on the SDCard (or other external storage)
   *
   * @throws RuntimeException
   *     if there is no SDCard or the directory exists as a non directory
   */
  public static void createODKDirs() throws RuntimeException {
    String cardstatus = Environment.getExternalStorageState();
    if (!cardstatus.equals(Environment.MEDIA_MOUNTED)) {
      throw new RuntimeException(Collect.getInstance().getString(R.string.sdcard_unmounted, cardstatus));
    }

    String[] dirs = {PUBLIC_ODK_ROOT, PUBLIC_CACHE_PATH, ODK_ROOT, FORMS_PATH, INSTANCES_PATH, CACHE_PATH,
        METADATA_PATH};

    for (String dirName : dirs) {
      File dir = new File(dirName);
      if (!dir.exists()) {
        if (!dir.mkdirs()) {
          RuntimeException e = new RuntimeException("ODK reports :: Cannot create directory: " + dirName);
          throw e;
        }
      } else {
        if (!dir.isDirectory()) {
          RuntimeException e = new RuntimeException("ODK reports :: " + dirName + " exists, but is not a directory");
          throw e;
        }
      }

      try {
        createNoMediaFile(dir);
      } catch (final IOException e) {
        throw new RuntimeException("Could not create .nomedia file in ODK directory " + dirName, e);
      }
    }
  }

  private static void createNoMediaFile(final File directory) throws IOException {
    final File noMediaFile = new File(directory, ".nomedia");
    noMediaFile.createNewFile();
  }

  /**
   * Construct and return a session context with shared cookieStore and credsProvider so a user does not have to
   * re-enter login information.
   *
   * @return
   */
  public synchronized HttpContext getHttpContext() {

    // context holds authentication state machine, so it cannot be
    // shared across independent activities.
    HttpContext localContext = new BasicHttpContext();

    localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
    localContext.setAttribute(ClientContext.CREDS_PROVIDER, credsProvider);

    return localContext;
  }

  public CredentialsProvider getCredentialsProvider() {
    return credsProvider;
  }

  public CookieStore getCookieStore() {
    return cookieStore;
  }

  @Override
  public void onCreate() {
    singleton = this;

    PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
    super.onCreate();

    PropertyManager mgr = new PropertyManager(this);

    FormController.initializeJavaRosa(mgr);

    mActivityLogger = new ActivityLogger(mgr.getSingularProperty(PropertyManager.DEVICE_ID_PROPERTY));
  }

  public void init(final Context applicationContext) {

    // Initialise the storage location based on the app context, files will be stored in external private storage
    ODK_ROOT = applicationContext.getFilesDir() + File.separator + ".dmodk";
    PUBLIC_ODK_ROOT = applicationContext.getExternalFilesDir(null) + File.separator + "dmodk";
    FORMS_PATH = ODK_ROOT + File.separator + "forms";
    INSTANCES_PATH = ODK_ROOT + File.separator + "instances";
    CACHE_PATH = ODK_ROOT + File.separator + ".cache";
    METADATA_PATH = ODK_ROOT + File.separator + "metadata";
    TMPFILE_PATH = CACHE_PATH + File.separator + "tmp.jpg";
    PUBLIC_CACHE_PATH = PUBLIC_ODK_ROOT + File.separator + ".cache";
    TMP_CAMERA_FILE_PATH = PUBLIC_CACHE_PATH + File.separator + "cmra_tmp.jpg";
    TMPDRAWFILE_PATH = CACHE_PATH + File.separator + "tmpDraw.jpg";
    TMPXML_PATH = CACHE_PATH + File.separator + "tmp.xml";
    LOG_PATH = ODK_ROOT + File.separator + "log";

    singleton = this;
    this.attachBaseContext(applicationContext);
    onCreate();
    createODKDirs();
  }

  /**
   * @return the delegate for opening an alternative DrawActivity.
   */
  public DrawActivity.DrawActivityDelegate getDrawActivityDelegate() {
    return drawActivityDelegate;
  }

  /**
   * @return the delegate to launch an Activity for viewing an image.
   */
  public ImageWidget.ViewImageActivityDelagate getViewImageActivityDelagate() {
    return viewImageActivityDelagate;
  }

  /**
   * @param activityDelagate
   *     the delegate to launch an Activity for viewing an image.
   */
  public void setViewImageActivityDelegate(final ImageWidget.ViewImageActivityDelagate activityDelagate) {
    this.viewImageActivityDelagate = activityDelagate;
  }
}
