// Copyright 2010-2016, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package org.mozc.android.inputmethod.japanese.preference;

import org.mozc.android.inputmethod.japanese.ApplicationInitializerFactory;
import org.mozc.android.inputmethod.japanese.ApplicationInitializerFactory.ApplicationInitializer;
import org.mozc.android.inputmethod.japanese.DependencyFactory;
import org.mozc.android.inputmethod.japanese.MozcUtil;
import org.mozc.android.inputmethod.japanese.hardwarekeyboard.HardwareKeyboardSpecification;
import org.mozc.android.inputmethod.japanese.preference.KeyboardPreviewDrawable.BitmapCache;
import org.mozc.android.inputmethod.japanese.preference.KeyboardPreviewDrawable.CacheReferenceKey;
import org.mozc.android.inputmethod.japanese.resources.R;
import org.mozc.android.inputmethod.japanese.util.LauncherIconManagerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.provider.Settings;

/**
 * This class handles the preferences of mozc.
 *
 * The same information might be stored in both SharedPreference and Config.
 * In this case SharedPreference is the master and Config is just a copy.
 *
 */
public class MozcBasePreferenceActivity extends PreferenceActivity {

  private static class ImeEnableDialogClickListener implements OnClickListener {
    private final Context context;

    ImeEnableDialogClickListener(Context context) {
      if (context == null) {
        throw new NullPointerException("context is null.");
      }
      this.context = context;
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
      if (which == DialogInterface.BUTTON_POSITIVE) {
        // Open the keyboard & language setting page.
        Intent intent = new Intent();
        intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        context.startActivity(intent);
      }
    }
  }

  private static class ImeSwitchDialogListener
      implements OnClickListener, OnDismissListener, OnCancelListener {
    private final Context context;

    // This variable should be reset every dialog shown timing, but it is not supported
    // on API level 7. So, instead, we check all possible finishing pass.
    // TODO(hidehiko): use OnShownListener after we upgrade the API level.
    private boolean showInputMethodPicker = false;

    ImeSwitchDialogListener(Context context) {
      if (context == null) {
        throw new NullPointerException("context is null.");
      }
      this.context = context;
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
      showInputMethodPicker = (which == DialogInterface.BUTTON_POSITIVE);
    }

    @Override
    public void onCancel(DialogInterface arg0) {
      showInputMethodPicker = false;
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
      if (showInputMethodPicker) {
        // Send an input method picker shown event after the dismissing of this dialog is
        // completed. Otherwise, the new dialog will be dismissed, too.
        showInputMethodPicker = false;
        MozcUtil.requestShowInputMethodPicker(context);
      }
    }
  }

  @VisibleForTesting AlertDialog imeEnableDialog;
  @VisibleForTesting AlertDialog imeSwitchDialog;

  // Cache the SharedPreferences instance, otherwise PreferenceManager.getDefaultSharedPreferences
  // would try to read it from the storage every time.
  private SharedPreferences sharedPreferences;

  private final CacheReferenceKey cacheKey = new CacheReferenceKey();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
    initializeAlertDialog();
  }

  @Override
  protected void onResume() {
    super.onResume();

    HardwareKeyboardSpecification.maybeSetDetectedHardwareKeyMap(
        sharedPreferences, getResources().getConfiguration(), false);
  }

  @Override
  protected void onPostResume() {
    super.onPostResume();
    onPostResumeInternal(ApplicationInitializerFactory.createInstance(this));
  }

  @VisibleForTesting
  void onPostResumeInternal(ApplicationInitializer initializer) {
    Context context = getApplicationContext();
    Optional<Intent> forwardIntent = initializer.initialize(
        MozcUtil.isSystemApplication(context),
        MozcUtil.isDevChannel(context),
        DependencyFactory.getDependency(getApplicationContext()).isWelcomeActivityPreferrable(),
        MozcUtil.getAbiIndependentVersionCode(context),
        LauncherIconManagerFactory.getDefaultInstance(),
        PreferenceUtil.getDefaultPreferenceManagerStatic());
    if (forwardIntent.isPresent()) {
      startActivity(forwardIntent.get());
    } else {
      // After all resuming processes, we check the default IME status.
      // Note that we need to do this only when first launch activity is *not* launched.
      maybeShowAlertDialogIme();
    }
  }

  // Note: when an activity A starts another activity B, the order of their onStart/onStop
  // invocation sequence is:
  //   B's onStart -> A's onStop.
  // (of course, some other methods, such as onPause or onResume etc, are also invoked accordingly).
  // Thus, we can keep and pass the cached keyboard preview bitmaps by reference counting
  // in onStart/onStop methods from the single-pane preference top page to
  // software keyboard advanced settings, and vice versa.
  // Note that if a user tries to move an activity other than Mozc's preference even from
  // software keyboard advanced settings preference page, the counter will simply get 0, and all the
  // cached bitmap resources will be released.
  @Override
  protected void onStart() {
    super.onStart();
    // Tell bitmap cache that this Activity uses it.
    BitmapCache.getInstance().addReference(cacheKey);
  }

  @Override
  protected void onStop() {
    // Release all the bitmap cache (if necessary) to utilize the used memory.
    BitmapCache.getInstance().removeReference(cacheKey);
    super.onStop();
  }

  private void initializeAlertDialog() {
    Resources resource = getResources();
    imeEnableDialog = createAlertDialog(
        this,
        R.string.pref_ime_enable_alert_title,
        resource.getString(R.string.pref_ime_enable_alert_message,
                           resource.getString(R.string.app_name)),
        new ImeEnableDialogClickListener(this));
    ImeSwitchDialogListener listener = new ImeSwitchDialogListener(this);
    imeSwitchDialog = createAlertDialog(
        this,
        R.string.pref_ime_switch_alert_title,
        resource.getString(R.string.pref_ime_switch_alert_message,
                           resource.getString(R.string.app_name)),
        listener);
    imeSwitchDialog.setOnCancelListener(listener);
    imeSwitchDialog.setOnDismissListener(listener);
  }

  private static AlertDialog createAlertDialog(
      Context context, int titleResourceId, String message, OnClickListener clickListener) {
    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    builder.setTitle(titleResourceId);
    builder.setMessage(message);
    builder.setPositiveButton(R.string.pref_ime_alert_next, clickListener);
    builder.setNegativeButton(R.string.pref_ime_alert_cancel, clickListener);
    return builder.create();
  }

  /**
   * Check the current default IME status, and show alerting dialog if necessary.
   */
  private void maybeShowAlertDialogIme() {
    if (imeEnableDialog.isShowing() || imeSwitchDialog.isShowing()) {
      // Either dialog is already shown, right now. So just skip the check.
      return;
    }

    Context context = getApplicationContext();
    if (!MozcUtil.isMozcEnabled(context)) {
      imeEnableDialog.show();
      return;
    }
    // If this is a system (=preinstalled) application (including updated one),
    // Notification dialogs won't be shown to improve usability.
    // If this is not a system one, imeSwitchDialog is shown because users might misunderstand
    // that their system IME is Google Japanese Input.
    if (!MozcUtil.isMozcDefaultIme(context) && !MozcUtil.isSystemApplication(context)) {
      imeSwitchDialog.show();
      return;
    }
  }

  /**
   * For the time being, pretend default behavior of apps for the framework older than KITKAT.
   */
  @Override
  protected boolean isValidFragment(@SuppressWarnings("unused") String fragmentName) {
    return true;
  }
}
