package juloo.keyboard2;

import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.KeyEvent;
import java.util.HashSet;
import java.util.Set;

final class Config
{
  private final SharedPreferences _prefs;

  // From resources
  public final float marginTop;
  public final float keyPadding;

  public final float labelTextSize;
  public final float sublabelTextSize;

  // From preferences
  public KeyboardData layout; // Or 'null' for the system defaults
  public KeyboardData second_layout; // Or 'null' for none
  public KeyboardData custom_layout; // Might be 'null'
  public boolean show_numpad = false;
  // From the 'numpad_layout' option, also apply to the numeric pane.
  public boolean inverse_numpad = false;
  public float swipe_dist_px;
  public float slide_step_px;
  public boolean vibrateEnabled;
  public long longPressTimeout;
  public long longPressInterval;
  public float margin_bottom;
  public float keyHeight;
  public float horizontal_margin;
  public float keyVerticalInterval;
  public float keyHorizontalInterval;
  public int labelBrightness; // 0 - 255
  public int keyboardOpacity; // 0 - 255
  public int keyOpacity; // 0 - 255
  public int keyActivatedOpacity; // 0 - 255
  public boolean double_tap_lock_shift;
  public float characterSize; // Ratio
  public int accents; // Values are R.values.pref_accents_v_*
  public int theme; // Values are R.style.*
  public boolean autocapitalisation;

  // Dynamically set
  public boolean shouldOfferSwitchingToNextInputMethod;
  public boolean shouldOfferSwitchingToSecond;
  public String actionLabel; // Might be 'null'
  public int actionId; // Meaningful only when 'actionLabel' isn't 'null'
  public boolean swapEnterActionKey; // Swap the "enter" and "action" keys
  public Set<KeyValue> extra_keys_subtype;
  public Set<KeyValue> extra_keys_param;

  public final IKeyEventHandler handler;
  public boolean orientation_landscape = false;

  private Config(SharedPreferences prefs, Resources res, IKeyEventHandler h)
  {
    _prefs = prefs;
    // static values
    marginTop = res.getDimension(R.dimen.margin_top);
    keyPadding = res.getDimension(R.dimen.key_padding);
    labelTextSize = 0.33f;
    sublabelTextSize = 0.22f;
    // from prefs
    refresh(res);
    // initialized later
    shouldOfferSwitchingToNextInputMethod = false;
    shouldOfferSwitchingToSecond = false;
    actionLabel = null;
    actionId = 0;
    swapEnterActionKey = false;
    extra_keys_subtype = null;
    handler = h;
  }

  /*
   ** Reload prefs
   */
  public void refresh(Resources res)
  {
    DisplayMetrics dm = res.getDisplayMetrics();
    orientation_landscape = res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
    // The height of the keyboard is relative to the height of the screen.
    // This is the height of the keyboard if it have 4 rows.
    int keyboardHeightPercent;
    // Scale some dimensions depending on orientation
    float horizontalIntervalScale = 1.f;
    float characterSizeScale = 1.f;
    String show_numpad_s = _prefs.getString("show_numpad", "never");
    show_numpad = "always".equals(show_numpad_s);
    if (orientation_landscape)
    {
      if ("landscape".equals(show_numpad_s))
        show_numpad = true;
      keyboardHeightPercent = _prefs.getInt("keyboard_height_landscape", 50);
      horizontalIntervalScale = 2.f;
      characterSizeScale = 1.25f;
    }
    else
    {
      keyboardHeightPercent = _prefs.getInt("keyboard_height", 35);
    }
    layout = layout_of_string(res, _prefs.getString("layout", "none"));
    second_layout = tweak_secondary_layout(layout_of_string(res, _prefs.getString("second_layout", "none")));
    custom_layout = KeyboardData.load_string(_prefs.getString("custom_layout", ""));
    inverse_numpad = _prefs.getString("numpad_layout", "default").equals("low_first");
    // The baseline for the swipe distance correspond to approximately the
    // width of a key in portrait mode, as most layouts have 10 columns.
    // Multipled by the DPI ratio because most swipes are made in the diagonals.
    // The option value uses an unnamed scale where the baseline is around 25.
    float dpi_ratio = Math.max(dm.xdpi, dm.ydpi) / Math.min(dm.xdpi, dm.ydpi);
    float swipe_scaling = Math.min(dm.widthPixels, dm.heightPixels) / 10.f * dpi_ratio;
    float swipe_dist_value = Float.valueOf(_prefs.getString("swipe_dist", "15"));
    swipe_dist_px = swipe_dist_value / 25.f * swipe_scaling;
    slide_step_px = swipe_dist_px / 4.f;
    vibrateEnabled = _prefs.getBoolean("vibrate_enabled", true);
    longPressTimeout = _prefs.getInt("longpress_timeout", 600);
    longPressInterval = _prefs.getInt("longpress_interval", 65);
    margin_bottom = get_dip_pref(dm, oriented_pref("margin_bottom"),
        res.getDimension(R.dimen.margin_bottom));
    keyVerticalInterval = get_dip_pref(dm, "key_vertical_space",
        res.getDimension(R.dimen.key_vertical_interval));
    keyHorizontalInterval =
      get_dip_pref(dm, "key_horizontal_space",
          res.getDimension(R.dimen.key_horizontal_interval))
      * horizontalIntervalScale;
    // Label brightness is used as the alpha channel
    labelBrightness = _prefs.getInt("label_brightness", 100) * 255 / 100;
    // Keyboard opacity
    keyboardOpacity = _prefs.getInt("keyboard_opacity", 100) * 255 / 100;
    keyOpacity = _prefs.getInt("key_opacity", 100) * 255 / 100;
    keyActivatedOpacity = _prefs.getInt("key_activated_opacity", 100) * 255 / 100;
    // Do not substract keyVerticalInterval from keyHeight because this is done
    // during rendered.
    keyHeight = dm.heightPixels * keyboardHeightPercent / 100 / 4;
    horizontal_margin =
      get_dip_pref(dm, oriented_pref("horizontal_margin"),
          res.getDimension(R.dimen.horizontal_margin));
    double_tap_lock_shift = _prefs.getBoolean("lock_double_tap", false);
    characterSize =
      _prefs.getFloat("character_size", 1.f)
      * characterSizeScale;
    accents = Integer.valueOf(_prefs.getString("accents", "1"));
    theme = getThemeId(res, _prefs.getString("theme", ""));
    autocapitalisation = _prefs.getBoolean("autocapitalisation", true);
    extra_keys_param = ExtraKeyCheckBoxPreference.get_extra_keys(_prefs);
  }

  /** Update the layout according to the configuration.
   *  - Remove the switching key if it isn't needed
   *  - Remove "localized" keys from other locales (not in 'extra_keys')
   *  - Replace the action key to show the right label
   *  - Swap the enter and action keys
   */
  public KeyboardData modify_layout(KeyboardData kw)
  {
    // Update the name to avoid caching in KeyModifier
    final KeyValue action_key = (actionLabel == null) ? null :
      KeyValue.getKeyByName("action").withSymbol(actionLabel);
    // Extra keys are removed from the set as they are encountered during the
    // first iteration then automatically added.
    final Set<KeyValue> extra_keys = new HashSet<KeyValue>();
    if (extra_keys_subtype != null)
      extra_keys.addAll(extra_keys_subtype);
    extra_keys.addAll(extra_keys_param);
    if (kw.num_pad && show_numpad)
      kw = kw.addNumPad();
    kw = kw.mapKeys(new KeyboardData.MapKeyValues() {
      /** Apply to the center value only. Partial match, fallback to [apply]. */
      public KeyboardData.Corner apply_key0(KeyboardData.Corner corner)
      {
        if (corner == null)
          return null;
        KeyValue kv = corner.kv;
        switch (kv.getKind())
        {
          case Char:
            char c = kv.getChar();
            if (inverse_numpad)
              c = inverse_numpad_char(c);
            if (c != kv.getChar())
              return KeyboardData.Corner.of_kv(kv.withChar(c));
            break;
        }
        return super.apply(corner);
      }

      public KeyValue apply(KeyValue key, boolean localized)
      {
        boolean is_extra_key = extra_keys.contains(key);
        if (is_extra_key)
          extra_keys.remove(key);
        if (localized && !is_extra_key)
          return null;
        switch (key.getKind())
        {
          case Event:
            switch (key.getEvent())
            {
              case CHANGE_METHOD:
                return shouldOfferSwitchingToNextInputMethod ? key : null;
              case ACTION:
                return (swapEnterActionKey && action_key != null) ?
                  KeyValue.getKeyByName("enter") : action_key;
              case SWITCH_SECOND:
                return shouldOfferSwitchingToSecond ? key : null;
            }
            break;
          case Keyevent:
            switch (key.getKeyevent())
            {
              case KeyEvent.KEYCODE_ENTER:
                return (swapEnterActionKey && action_key != null) ? action_key : key;
            }
            break;
          case Modifier:
            switch (key.getModifier())
            {
              case SHIFT:
                if (double_tap_lock_shift)
                  return key.withFlags(key.getFlags() | KeyValue.FLAG_LOCK);
            }
            break;
        }
        return key;
      }
    });
    if (extra_keys.size() > 0)
      kw = kw.addExtraKeys(extra_keys.iterator());
    return kw;
  }

  /** Modify a layout to turn it into a secondary layout by changing the
      "switch_second" key. */
  KeyboardData tweak_secondary_layout(KeyboardData layout)
  {
    if (layout == null)
      return null;
    return layout.mapKeys(new KeyboardData.MapKeyValues() {
      public KeyValue apply(KeyValue key, boolean localized)
      {
        if (key.getKind() == KeyValue.Kind.Event
            && key.getEvent() == KeyValue.Event.SWITCH_SECOND)
          return KeyValue.getKeyByName("switch_second_back");
        return key;
      }
    });
  }

  private float get_dip_pref(DisplayMetrics dm, String pref_name, float def)
  {
    float value;
    try { value = _prefs.getInt(pref_name, -1); }
    catch (Exception e) { value = _prefs.getFloat(pref_name, -1f); }
    if (value < 0f)
      return (def);
    return (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, dm));
  }

  /** Returns preference name from a prefix depending on orientation. */
  private String oriented_pref(String base_name)
  {
    String suffix = orientation_landscape ? "_landscape" : "_portrait";
    return base_name + suffix;
  }

  private int getThemeId(Resources res, String theme_name)
  {
    switch (theme_name)
    {
      case "light": return R.style.Light;
      case "black": return R.style.Black;
      case "dark": return R.style.Dark;
      case "white": return R.style.White;
      default:
      case "system":
        if (Build.VERSION.SDK_INT >= 8)
        {
          int night_mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
          if ((night_mode & Configuration.UI_MODE_NIGHT_NO) != 0)
            return R.style.Light;
        }
        return R.style.Dark;
    }
  }

  public KeyboardData layout_of_string(Resources res, String name)
  {
    int id = R.xml.qwerty; // The config might store an invalid layout, don't crash
    switch (name)
    {
      case "system": case "none": return null;
      case "custom": if (custom_layout != null) return custom_layout; break;
      case "azerty": id = R.xml.azerty; break;
      case "bangla": id = R.xml.bangla; break;
      case "bgph1": id = R.xml.local_bgph1; break;
      case "bone": id = R.xml.bone; break;
      case "colemak": id = R.xml.colemak; break;
      case "dvorak": id = R.xml.dvorak; break;
      case "hindi": id = R.xml.hindi; break;
      case "jcuken_ua": id = R.xml.jcuken_ua; break;
      case "neo2": id = R.xml.neo2; break;
      case "qwerty": id = R.xml.qwerty; break;
      case "qwerty_el": id = R.xml.qwerty_el; break;
      case "qwerty_es": id = R.xml.qwerty_es; break;
      case "qwerty_hu": id = R.xml.qwerty_hu; break;
      case "qwerty_ko": id = R.xml.qwerty_ko; break;
      case "qwerty_lv": id = R.xml.qwerty_lv; break;
      case "qwerty_no": id = R.xml.qwerty_no; break;
      case "qwerty_pt": id = R.xml.qwerty_pt; break;
      case "qwerty_sv_se": id = R.xml.qwerty_sv_se; break;
      case "qwerty_tr": id = R.xml.qwerty_tr; break;
      case "qwerty_pl": id = R.xml.qwerty_pl; break;
      case "qwertz": id = R.xml.qwertz; break;
      case "qwertz_cs": id = R.xml.qwertz_cs; break;
      case "qwertz_de": id = R.xml.qwertz_de; break;
      case "qwertz_hu": id = R.xml.qwertz_hu; break;
      case "qwertz_sk": id = R.xml.qwertz_sk; break;
      case "ru_jcuken": id = R.xml.local_ru_jcuken; break;
    }
    return KeyboardData.load(res, id);
  }

  char inverse_numpad_char(char c)
  {
    switch (c)
    {
      case '7': return '1';
      case '8': return '2';
      case '9': return '3';
      case '1': return '7';
      case '2': return '8';
      case '3': return '9';
      default: return c;
    }
  }

  private static Config _globalConfig = null;

  public static void initGlobalConfig(SharedPreferences prefs, Resources res,
      IKeyEventHandler handler)
  {
    _globalConfig = new Config(prefs, res, handler);
  }

  public static Config globalConfig()
  {
    return _globalConfig;
  }

  public static interface IKeyEventHandler
  {
    public void key_up(KeyValue value, Pointers.Modifiers flags);
  }
}
