/*   This file is part of My Expenses.
 *   My Expenses is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   My Expenses is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with My Expenses.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.totschnig.myexpenses.activity;

import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.StrictMode;
import android.provider.Settings;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.NavUtils;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;

import org.totschnig.myexpenses.BuildConfig;
import org.totschnig.myexpenses.MyApplication;
import org.totschnig.myexpenses.dialog.TransactionDetailFragment;
import org.totschnig.myexpenses.model.Transaction;
import org.totschnig.myexpenses.preference.PrefKey;
import org.totschnig.myexpenses.R;
import org.totschnig.myexpenses.dialog.MessageDialogFragment.MessageDialogListener;
import org.totschnig.myexpenses.dialog.ProgressDialogFragment;
import org.totschnig.myexpenses.fragment.DbWriteFragment;
import org.totschnig.myexpenses.model.ContribFeature;
import org.totschnig.myexpenses.model.Model;
import org.totschnig.myexpenses.task.TaskExecutionFragment;
import org.totschnig.myexpenses.util.AcraHelper;
import org.totschnig.myexpenses.util.Utils;
import org.totschnig.myexpenses.widget.AbstractWidget;

import java.io.Serializable;

/**
 * @author Michael Totschnig
 */
public abstract class ProtectedFragmentActivity extends AppCompatActivity
    implements MessageDialogListener, OnSharedPreferenceChangeListener,
    TaskExecutionFragment.TaskCallbacks, DbWriteFragment.TaskCallbacks {
  public static final int CALCULATOR_REQUEST = 0;
  public static final int EDIT_TRANSACTION_REQUEST = 1;
  public static final int EDIT_ACCOUNT_REQUEST = 2;
  public static final int PREFERENCES_REQUEST = 3;
  public static final int CREATE_ACCOUNT_REQUEST = 4;
  public static final int FILTER_CATEGORY_REQUEST = 5;
  public static final int FILTER_COMMENT_REQUEST = 6;
  public static final int TEMPLATE_TITLE_REQUEST = 7;
  public static final int EDIT_SPLIT_REQUEST = 8;
  public static final int SELECT_CATEGORY_REQUEST = 9;
  public static final int PICK_COLOR_REQUEST = 11;
  public static final int PICTURE_REQUEST_CODE = 14;
  public static final int IMPORT_FILENAME_REQUESTCODE = 15;
  public static final String SAVE_TAG = "SAVE_TASK";
  public static final String SORT_ORDER_USAGES = "USAGES";
  public static final String SORT_ORDER_LAST_USED = "LAST_USED";
  public static final String SORT_ORDER_AMOUNT = "AMOUNT";
  public static final String SORT_ORDER_TITLE = "TITLE";
  public static final String SORT_ORDER_CUSTOM = "CUSTOM";
  public static final String SORT_ORDER_NEXT_INSTANCE = "NEXT_INSTANCE";
  private AlertDialog pwDialog;
  private ProtectionDelegate protection;
  private boolean scheduledRestart = false;
  public Enum<?> helpVariant = null;
  protected int colorExpense;
  protected FloatingActionButton floatingActionButton;

  public int getColorExpense() {
    return colorExpense;
  }

  protected int colorIncome;

  public int getColorIncome() {
    return colorIncome;
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    if (BuildConfig.DEBUG) {
      enableStrictMode();
    }
    super.onCreate(savedInstanceState);
    if (PrefKey.PERFORM_PROTECTION.getBoolean(false)) {
      getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
          WindowManager.LayoutParams.FLAG_SECURE);
    }
    MyApplication.getInstance().getSettings().registerOnSharedPreferenceChangeListener(this);
    setLanguage();
    Resources.Theme theme = getTheme();
    TypedValue color = new TypedValue();
    theme.resolveAttribute(R.attr.colorExpense, color, true);
    colorExpense = color.data;
    theme.resolveAttribute(R.attr.colorIncome, color, true);
    colorIncome = color.data;
  }

  @Override
  public void setContentView(int layoutResID) {
    super.setContentView(layoutResID);
  }

  protected void configureFloatingActionButton(int fabDescription) {
    if (!requireFloatingActionButtonWithContentDescription(getString(fabDescription))) return;
    TypedValue color = new TypedValue();
    getTheme().resolveAttribute(R.attr.colorAccent, color, true);
    Utils.setBackgroundTintListOnFab(floatingActionButton, color.data);
  }

  protected boolean requireFloatingActionButtonWithContentDescription(String fabDescription) {
    floatingActionButton = ((FloatingActionButton) findViewById(R.id.CREATE_COMMAND));
    if (floatingActionButton == null) return false;
    floatingActionButton.setContentDescription(fabDescription);
    return true;
  }

  protected Toolbar setupToolbar(boolean withHome) {
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    if (withHome) {
      final ActionBar actionBar = getSupportActionBar();
      actionBar.setDisplayHomeAsUpEnabled(true);
    }
    return toolbar;
  }

  @TargetApi(9)
  private void enableStrictMode() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
      StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
          .detectDiskReads()
          .detectDiskWrites()
          .detectNetwork()   // or .detectAll() for all detectable problems
          .penaltyLog()
          .build());
      StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
          .detectLeakedSqlLiteObjects()
              //.detectLeakedClosableObjects()
          .penaltyLog()
          .penaltyDeath()
          .build());
    }
  }

  private ProtectionDelegate getProtection() {
    if (protection == null) {
      protection = new ProtectionDelegate(this);
    }
    return protection;
  }

  @Override
  protected void onPause() {
    super.onPause();
    getProtection().handleOnPause(pwDialog);
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    MyApplication.getInstance().getSettings().unregisterOnSharedPreferenceChangeListener(this);
  }

  @Override
  @TargetApi(11)
  protected void onResume() {
    super.onResume();
    if (scheduledRestart) {
      scheduledRestart = false;
      if (android.os.Build.VERSION.SDK_INT >= 11)
        recreate();
      else {
        Intent intent = getIntent();
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(intent);
      }
    } else {
      pwDialog = getProtection().hanldeOnResume(pwDialog);
    }
  }

  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
                                        String key) {
    if (key.equals(PrefKey.UI_THEME_KEY.getKey()) ||
        key.equals(PrefKey.UI_LANGUAGE.getKey()) ||
        key.equals(PrefKey.UI_FONTSIZE.getKey()) ||
        key.equals((PrefKey.PERFORM_PROTECTION.getKey())) ||
        key.equals(PrefKey.GROUP_MONTH_STARTS.getKey()) ||
        key.equals(PrefKey.GROUP_WEEK_STARTS.getKey())) {
      scheduledRestart = true;
    }
  }

  public void onMessageDialogDismissOrCancel() {
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.help, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    if (dispatchCommand(item.getItemId(), null)) {
      return true;
    }
    return super.onOptionsItemSelected(item);
  }

  @Override
  public boolean dispatchCommand(int command, Object tag) {
    if (CommonCommands.dispatchCommand(this, command, tag)) {
      return true;
    }
    return false;
  }

  public void dispatchCommand(View v) {
    dispatchCommand(v.getId(), v.getTag());
  }

  @Override
  public void onPreExecute() {
  }

  @Override
  public void onProgressUpdate(Object progress) {
    getProtection().updateProgressDialog(progress);
  }

  @Override
  public void onCancelled() {
    getProtection().removeAsyncTaskFragment(false);
  }

  @Override
  public void onPostExecute(int taskId, Object o) {
    getProtection().removeAsyncTaskFragment(taskId);
    switch (taskId) {
      case TaskExecutionFragment.TASK_DELETE_TRANSACTION:
      case TaskExecutionFragment.TASK_DELETE_ACCOUNT:
      case TaskExecutionFragment.TASK_DELETE_PAYMENT_METHODS:
      case TaskExecutionFragment.TASK_DELETE_CATEGORY:
      case TaskExecutionFragment.TASK_DELETE_PAYEES:
      case TaskExecutionFragment.TASK_DELETE_TEMPLATES:
      case TaskExecutionFragment.TASK_UNDELETE_TRANSACTION:
        Boolean success = (Boolean) o;
        if (!success) {
          Toast.makeText(this,
              "There was an error deleting the object. Please contact support@myexenses.mobi !",
              Toast.LENGTH_LONG).show();
        }
        break;
      case TaskExecutionFragment.TASK_INSTANTIATE_TRANSACTION_2:
        TransactionDetailFragment tdf = (TransactionDetailFragment)
            getSupportFragmentManager().findFragmentByTag(TransactionDetailFragment.class.getName());
        if (tdf != null) {
          tdf.fillData((Transaction) o);
        }
        break;
    }
  }

  protected void setLanguage() {
    MyApplication.getInstance().setLanguage();
  }

  @Override
  public Model getObject() {
    return null;
  }

  @Override
  public void onPostExecute(Object result) {
    FragmentManager m = getSupportFragmentManager();
    FragmentTransaction t = m.beginTransaction();
    t.remove(m.findFragmentByTag(SAVE_TAG));
    t.remove(m.findFragmentByTag(ProtectionDelegate.PROGRESS_TAG));
    t.commitAllowingStateLoss();
  }

  /**
   * starts the given task, only if no task is currently executed,
   * informs user through toast in that case
   *
   * @param taskId
   * @param objectIds
   * @param extra
   * @param progressMessage if 0 no progress dialog will be shown
   */
  public <T> void startTaskExecution(int taskId, T[] objectIds, Serializable extra,
                                     int progressMessage) {
    getProtection().startTaskExecution(taskId, objectIds, extra, progressMessage);
  }

  public void startDbWriteTask(boolean returnSequenceCount) {
    getSupportFragmentManager().beginTransaction()
        .add(DbWriteFragment.newInstance(returnSequenceCount), SAVE_TAG)
        .add(ProgressDialogFragment.newInstance(R.string.progress_dialog_saving),
            ProtectionDelegate.PROGRESS_TAG)
        .commitAllowingStateLoss();
  }

  public void recordUsage(ContribFeature f) {
    f.recordUsage();
  }

  /**
   * Workaround for broken {@link NavUtils#shouldUpRecreateTask(android.app.Activity, Intent)}
   *
   * @param from
   * @return
   * @see <a href="http://stackoverflow.com/a/20643984/1199911">http://stackoverflow.com/a/20643984/1199911</a>
   */
  protected final boolean shouldUpRecreateTask(Activity from) {
    return from.getIntent().getBooleanExtra(AbstractWidget.EXTRA_START_FROM_WIDGET, false);
  }

  /*
   * @see android.support.v7.app.ActionBarActivity#onBackPressed()
   * https://code.google.com/p/android/issues/detail?id=25517
   */
  @Override
  public void onBackPressed() {
    try {
      super.onBackPressed();
    } catch (IllegalStateException e) {
      AcraHelper.report(e);
      finish();
    }
  }

  public void toggleCrStatus(View v) {
    Long id = (Long) v.getTag();
    if (id != -1) {
      startTaskExecution(
          TaskExecutionFragment.TASK_TOGGLE_CRSTATUS,
          new Long[]{id},
          null,
          0);
    }
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode,
                                  Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    if (requestCode == ProtectionDelegate.CONTRIB_REQUEST && intent != null) {
      if (resultCode == RESULT_OK) {
        ((ContribIFace) this).contribFeatureCalled(
            (ContribFeature) intent.getSerializableExtra(ContribInfoDialogActivity.KEY_FEATURE),
            intent.getSerializableExtra(ContribInfoDialogActivity.KEY_TAG));
      } else if (resultCode == RESULT_CANCELED) {
        ((ContribIFace) this).contribFeatureNotCalled(
            (ContribFeature) intent.getSerializableExtra(ContribInfoDialogActivity.KEY_FEATURE));
      }
    }
  }

  public void contribFeatureRequested(ContribFeature feature, Serializable tag) {
    if (feature.hasAccess()) {
      ((ContribIFace) this).contribFeatureCalled(feature, tag);
    } else {
      CommonCommands.showContribDialog(this, feature, tag);
    }
  }

  @Override
  public void setTitle(CharSequence title) {
    super.setTitle(title);
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
      getSupportActionBar().setTitle(title);
    }
  }

  @Override
  public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
      case ProtectionDelegate.PERMISSIONS_REQUEST_WRITE_CALENDAR:
        PrefKey.CALENDAR_PERMISSION_REQUESTED.putBoolean(true);
        if (grantResults.length > 0
            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
          MyApplication.getInstance().initPlanner();
        } else {
          if (ActivityCompat.shouldShowRequestPermissionRationale(
              this, Manifest.permission.WRITE_CALENDAR)) {
            Toast.makeText(this, getString(R.string.calendar_permission_required),Toast.LENGTH_LONG)
                .show();
          }
        }
        break;
    }
  }

  protected boolean calendarPermissionPermanentlyDeclined() {
    String permission = Manifest.permission.WRITE_CALENDAR;
    return PrefKey.CALENDAR_PERMISSION_REQUESTED.getBoolean(false) &&
        (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_DENIED) &&
        !ActivityCompat.shouldShowRequestPermissionRationale(this, permission);
  }

  public void requestCalendarPermission() {
    if (calendarPermissionPermanentlyDeclined()) {
      //noinspection InlinedApi
      Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
      Uri uri = Uri.fromParts("package", getPackageName(), null);
      intent.setData(uri);
      startActivity(intent);
    } else {
      ActivityCompat.requestPermissions(this,
          new String[]{Manifest.permission.WRITE_CALENDAR},
          ProtectionDelegate.PERMISSIONS_REQUEST_WRITE_CALENDAR);
    }
  }
}
