/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.weex.ui.component;

import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;

import com.alibaba.fastjson.JSONObject;
import org.apache.weex.WXSDKInstance;
import org.apache.weex.WXSDKManager;
import org.apache.weex.annotation.JSMethod;
import org.apache.weex.bridge.WXBridgeManager;
import org.apache.weex.common.Constants;
import org.apache.weex.common.WXThread;
import org.apache.weex.dom.CSSConstants;
import org.apache.weex.dom.WXStyle;
import org.apache.weex.layout.ContentBoxMeasurement;
import org.apache.weex.layout.MeasureMode;
import org.apache.weex.ui.action.BasicComponentData;
import org.apache.weex.ui.component.helper.SoftKeyboardDetector;
import org.apache.weex.ui.component.helper.WXTimeInputHelper;
import org.apache.weex.ui.view.WXEditText;
import org.apache.weex.utils.TypefaceUtil;
import org.apache.weex.utils.WXLogUtils;
import org.apache.weex.utils.WXResourceUtils;
import org.apache.weex.utils.WXUtils;
import org.apache.weex.utils.WXViewUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import static org.apache.weex.dom.WXStyle.UNSET;

/**
 * Created by sospartan on 7/11/16.
 */
public abstract class AbstractEditComponent extends WXComponent<WXEditText> {

  private final InputMethodManager mInputMethodManager;
  private String mBeforeText = "";
  private boolean mAutoFocus;
  private String mType = "text";
  private String mMax = null;
  private String mMin = null;
  private String mLastValue = "";
  private int mEditorAction = EditorInfo.IME_ACTION_DONE;
  private String mReturnKeyType = null;
  private List<TextView.OnEditorActionListener> mEditorActionListeners;
  private boolean mListeningKeyboard = false;
  private SoftKeyboardDetector.Unregister mUnregister;
  private boolean mIgnoreNextOnInputEvent = false;
  private boolean mKeepSelectionIndex = false;
  private TextFormatter mFormatter = null;
  private List<TextWatcher> mTextChangedListeners;
  private TextWatcher mTextChangedEventDispatcher;
  private int mFormatRepeatCount = 0;
  private static final int MAX_TEXT_FORMAT_REPEAT = 3;

  private TextPaint mPaint = new TextPaint();
  private int mLineHeight = UNSET;

  public AbstractEditComponent(WXSDKInstance instance, WXVContainer parent, boolean isLazy, BasicComponentData basicComponentData) {
    super(instance, parent, isLazy, basicComponentData);
    mInputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
    setContentBoxMeasurement(new ContentBoxMeasurement() {
      /** uiThread = false **/
      @Override
      public void measureInternal(float width, float height, int widthMeasureMode, int heightMeasureMode) {
        if (CSSConstants.isUndefined(width) || widthMeasureMode == MeasureMode.UNSPECIFIED) {
          width = 0;
        }
        mMeasureWidth = width;
        mMeasureHeight = getMeasureHeight();
      }

      /** uiThread = false **/
      @Override
      public void layoutBefore() {
        updateStyleAndAttrs();
      }

      /** uiThread = false **/
      @Override
      public void layoutAfter(float computedWidth, float computedHeight) {

      }
    });
  }

  @Override
  protected void layoutDirectionDidChanged(boolean isRTL) {
    String alignStr = (String) getStyles().get(Constants.Name.TEXT_ALIGN);
    int textAlign = getTextAlign(alignStr);
    if (textAlign <= 0) {
      textAlign = Gravity.START;
    }
    if (getHostView() instanceof WXEditText) {
      getHostView().setGravity(textAlign | getVerticalGravity());
    }
  }

  protected final float getMeasuredLineHeight() {
    return mLineHeight != UNSET && mLineHeight > 0 ? mLineHeight : mPaint.getFontMetrics(null);
  }

  protected float getMeasureHeight() {
    return getMeasuredLineHeight();
  }

  protected void updateStyleAndAttrs() {
    if (getStyles().size() > 0) {
      int fontSize = UNSET, fontStyle = UNSET, fontWeight = UNSET;
      String fontFamily = null;
      if (getStyles().containsKey(Constants.Name.FONT_SIZE)) {
        fontSize = WXStyle.getFontSize(getStyles(),getViewPortWidth());
      }

      if (getStyles().containsKey(Constants.Name.FONT_FAMILY)) {
        fontFamily = WXStyle.getFontFamily(getStyles());
      }

      if (getStyles().containsKey(Constants.Name.FONT_STYLE)) {
        fontStyle = WXStyle.getFontStyle(getStyles());
      }

      if (getStyles().containsKey(Constants.Name.FONT_WEIGHT)) {
        fontWeight = WXStyle.getFontWeight(getStyles());
      }

      int lineHeight = WXStyle.getLineHeight(getStyles(),getViewPortWidth());
      if (lineHeight != UNSET)
        mLineHeight = lineHeight;

      if (fontSize != UNSET)
        mPaint.setTextSize(fontSize);

      if (fontFamily != null) {
        TypefaceUtil.applyFontStyle(mPaint, fontStyle, fontWeight, fontFamily);
      }
    }
  }

  @Override
  protected WXEditText initComponentHostView(@NonNull Context context) {
    final WXEditText inputView = new WXEditText(context);
    appleStyleAfterCreated(inputView);
    return inputView;
  }

  @Override
  protected void onHostViewInitialized(WXEditText host) {
    super.onHostViewInitialized(host);
    addFocusChangeListener(new OnFocusChangeListener() {
      @Override
      public void onFocusChange(boolean hasFocus) {
        if (!hasFocus) {
          decideSoftKeyboard();
        }
        setPseudoClassStatus(Constants.PSEUDO.FOCUS,hasFocus);
      }
    });

    addKeyboardListener(host);
  }

  @Override
  protected boolean isConsumeTouch() {
    //EditText always consume touch event except disabled.
    return !isDisabled();
  }

  private OnClickListener mOnClickListener = new OnClickListener() {
    @Override
    public void onHostViewClick() {
      switch (mType) {
        case Constants.Value.DATE:
          hideSoftKeyboard();
          if (getParent() != null) {
            getParent().interceptFocus();
          }
          WXTimeInputHelper.pickDate(mMax, mMin, AbstractEditComponent.this);
          break;
        case Constants.Value.TIME:
          hideSoftKeyboard();
          if (getParent() != null) {
            getParent().interceptFocus();
          }
          WXTimeInputHelper.pickTime(AbstractEditComponent.this);
          break;
      }
    }
  };

  private void applyOnClickListener() {
    addClickListener(mOnClickListener);
  }


  protected int getVerticalGravity(){
    return Gravity.CENTER_VERTICAL;
  }

  /**
   * Process view after created.
   *
   * @param editText
   */
  protected void appleStyleAfterCreated(final WXEditText editText) {
    String alignStr = (String) getStyles().get(Constants.Name.TEXT_ALIGN);
    int textAlign = getTextAlign(alignStr);
    if (textAlign <= 0) {
      textAlign = Gravity.START;
    }
    editText.setGravity(textAlign | getVerticalGravity());
    final int colorInt = WXResourceUtils.getColor("#999999");
    if (colorInt != Integer.MIN_VALUE) {
      editText.setHintTextColor(colorInt);
    }

    mTextChangedEventDispatcher = new TextWatcher() {
      @Override
      public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        if (mTextChangedListeners != null) {
          for (TextWatcher watcher : mTextChangedListeners) {
            watcher.beforeTextChanged(s, start, count, after);
          }
        }
      }

      @Override
      public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (mFormatter != null) {
          String raw = mFormatter.recover(s.toString());
          String result = mFormatter.format(raw);
          // prevent infinite loop caused by bad format and recover regexp
          if (!result.equals(s.toString()) && mFormatRepeatCount < MAX_TEXT_FORMAT_REPEAT) {
            mFormatRepeatCount = mFormatRepeatCount + 1;
            int index = editText.getSelectionStart();
            int cursorIndex = mFormatter.format(mFormatter.recover(s.subSequence(0, index).toString())).length();
            editText.setText(result);
            editText.setSelection(cursorIndex);
            return;
          }

          mFormatRepeatCount = 0;
        }

        if (mTextChangedListeners != null) {
          for (TextWatcher watcher : mTextChangedListeners) {
            watcher.onTextChanged(s, start, before, count);
          }
        }
      }

      @Override
      public void afterTextChanged(Editable s) {
        if (mTextChangedListeners != null) {
          for (TextWatcher watcher : mTextChangedListeners) {
            watcher.afterTextChanged(s);
          }
        }
      }
    };
    editText.addTextChangedListener(mTextChangedEventDispatcher);

    editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, WXStyle.getFontSize(getStyles(), getInstance().getInstanceViewPortWidth()));
  }


  @Override
  public void addEvent(final String type) {
    super.addEvent(type);
    if (getHostView() == null || TextUtils.isEmpty(type)) {
      return;
    }
    final TextView text = getHostView();

    if (type.equals(Constants.Event.CHANGE)) {
      addFocusChangeListener(new OnFocusChangeListener() {
        @Override
        public void onFocusChange(boolean hasFocus) {
          if (hasFocus) {
            mLastValue = text.getText().toString();
          } else {
            CharSequence newValue = text.getText();
            newValue = newValue == null ? "" : newValue;
            if (!newValue.toString().equals(mLastValue)) {
              fireEvent(Constants.Event.CHANGE, newValue.toString());
              mLastValue = text.getText().toString();
            }
          }
        }
      });

      addEditorActionListener(new TextView.OnEditorActionListener() {
        @Override
        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
          if (actionId == mEditorAction) {
            CharSequence newValue = text.getText();
            newValue = newValue == null ? "" : newValue;
            if (!newValue.toString().equals(mLastValue)) {
              fireEvent(Constants.Event.CHANGE, newValue.toString());
              mLastValue = text.getText().toString();
            }
            if (getParent() != null) {
              getParent().interceptFocus();
            }
            hideSoftKeyboard();
            return true;
          }
          return false;
        }
      });
    } else if (type.equals(Constants.Event.INPUT)) {
      addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
          if (mIgnoreNextOnInputEvent) {
            mIgnoreNextOnInputEvent = false;
            mBeforeText = s.toString();
            return;
          }

          if (mBeforeText.equals(s.toString())) {
            return;
          }

          mBeforeText = s.toString();

          fireEvent(Constants.Event.INPUT, s.toString());
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
      });
    }

    if (Constants.Event.RETURN.equals(type)) {
      addEditorActionListener(new TextView.OnEditorActionListener() {
        @Override
        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
          if (actionId == mEditorAction) {
            Map<String, Object> ret = new HashMap<>(2);
            ret.put("returnKeyType", mReturnKeyType);
            ret.put("value", v.getText().toString());
            fireEvent(Constants.Event.RETURN, ret);
            return true;
          }
          return false;
        }
      });
    }

    if (Constants.Event.KEYBOARD.equals(type)) {
      mListeningKeyboard = true;
    }
  }

  private void fireEvent(String event, String value) {
    if (event != null) {
      Map<String, Object> params = new HashMap<>(2);
      params.put("value", value);
      params.put("timeStamp", System.currentTimeMillis());

      Map<String, Object> domChanges = new HashMap<>();
      Map<String, Object> attrsChanges = new HashMap<>();
      attrsChanges.put("value", value);
      domChanges.put("attrs", attrsChanges);

      WXSDKManager.getInstance().fireEvent(getInstanceId(), getRef(), event, params, domChanges);
    }
  }

  public void performOnChange(String value) {
    if (getEvents() != null) {
      String event = getEvents().contains(Constants.Event.CHANGE) ? Constants.Event.CHANGE : null;
      fireEvent(event, value);
    }
  }

  @Override
  protected boolean setProperty(String key, Object param) {
    switch (key) {
      case Constants.Name.DISABLED:
        Boolean disabled = WXUtils.getBoolean(param, null);
        if (disabled != null && mHost != null) {
          if (disabled) {
            mHost.setFocusable(false);
            mHost.setFocusableInTouchMode(false);
          } else {
            mHost.setFocusableInTouchMode(true);
            mHost.setFocusable(true);
          }
        }
        return true;
      case Constants.Name.PLACEHOLDER:
        String placeholder = WXUtils.getString(param, null);
        if (placeholder != null)
          setPlaceholder(placeholder);
        return true;
      case Constants.Name.PLACEHOLDER_COLOR:
        String placeholder_color = WXUtils.getString(param, null);
        if (placeholder_color != null)
          setPlaceholderColor(placeholder_color);
        return true;
      case Constants.Name.TYPE:
        String input_type = WXUtils.getString(param, null);
        if (input_type != null)
          setType(input_type);
        return true;
      case Constants.Name.AUTOFOCUS:
        Boolean result = WXUtils.getBoolean(param, null);
        if (result != null)
          setAutofocus(result);
        return true;
      case Constants.Name.COLOR:
        String color = WXUtils.getString(param, null);
        if (color != null)
          setColor(color);
        return true;
      case Constants.Name.FONT_SIZE:
        String fontsize = WXUtils.getString(param, null);
        if (fontsize != null)
          setFontSize(fontsize);
        return true;
      case Constants.Name.TEXT_ALIGN:
        String text_align = WXUtils.getString(param, null);
        if (text_align != null)
          setTextAlign(text_align);
        return true;
      case Constants.Name.SINGLELINE:
        Boolean singLineResult = WXUtils.getBoolean(param, null);
        if (singLineResult != null)
          setSingleLine(singLineResult);
        return true;
      case Constants.Name.LINES:
        Integer lines = WXUtils.getInteger(param, null);
        if (lines != null)
          setLines(lines);
        return true;
      case Constants.Name.MAX_LENGTH:
        Integer maxlength = WXUtils.getInteger(param, null);
        if (maxlength != null)
          setMaxLength(maxlength);
        return true;
      case Constants.Name.MAXLENGTH:
        Integer maxLength = WXUtils.getInteger(param, null);
        if (maxLength != null)
          setMaxLength(maxLength);
        return true;
      case Constants.Name.MAX:
        setMax(String.valueOf(param));
        return true;
      case Constants.Name.MIN:
        setMin(String.valueOf(param));
        return true;
      case Constants.Name.RETURN_KEY_TYPE:
        setReturnKeyType(String.valueOf(param));
        return true;
      case Constants.Name.KEEP_SELECTION_INDEX:
        boolean keepIndex = WXUtils.getBoolean(param, false);
        mKeepSelectionIndex = keepIndex;
        return true;
      case Constants.Name.ALLOW_COPY_PASTE:
        boolean allowCopyPaste = WXUtils.getBoolean(param, true);
        if (getHostView() != null) {
          getHostView().setAllowCopyPaste(allowCopyPaste);
        }
        return true;
    }
    return super.setProperty(key, param);
  }

  @WXComponentProp(name = Constants.Name.RETURN_KEY_TYPE)
  public void setReturnKeyType(String type) {
    if (getHostView() == null) {
      return;
    }
    mReturnKeyType = type;
    switch (type) {
      case ReturnTypes.DEFAULT:
        mEditorAction = EditorInfo.IME_ACTION_UNSPECIFIED;
        break;
      case ReturnTypes.GO:
        mEditorAction = EditorInfo.IME_ACTION_GO;
        break;
      case ReturnTypes.NEXT:
        mEditorAction = EditorInfo.IME_ACTION_NEXT;
        break;
      case ReturnTypes.SEARCH:
        mEditorAction = EditorInfo.IME_ACTION_SEARCH;
        break;
      case ReturnTypes.SEND:
        mEditorAction = EditorInfo.IME_ACTION_SEND;
        break;
      case ReturnTypes.DONE:
        mEditorAction = EditorInfo.IME_ACTION_DONE;
        break;
      default:
        break;
    }

    //remove focus and hide keyboard first, the ImeOptions will take effect when show keyboard next time
    blur();
    getHostView().setImeOptions(mEditorAction);
  }

  @WXComponentProp(name = Constants.Name.PLACEHOLDER)
  public void setPlaceholder(String placeholder) {
    if (placeholder == null || getHostView() == null) {
      return;
    }
    ((WXEditText) getHostView()).setHint(placeholder);
  }

  @WXComponentProp(name = Constants.Name.PLACEHOLDER_COLOR)
  public void setPlaceholderColor(String color) {
    if (getHostView() != null && !TextUtils.isEmpty(color)) {
      int colorInt = WXResourceUtils.getColor(color);
      if (colorInt != Integer.MIN_VALUE) {
        ((WXEditText) getHostView()).setHintTextColor(colorInt);
      }
    }
  }

  @WXComponentProp(name = Constants.Name.TYPE)
  public void setType(String type) {
    Log.e("weex", "setType=" + type);
    if (type == null || getHostView() == null) {
      return;
    }
    mType = type;
    ((EditText) getHostView()).setInputType(getInputType(mType));
    switch (mType) {
      case Constants.Value.DATE:
      case Constants.Value.TIME:
        applyOnClickListener();
        break;
    }
  }

  @WXComponentProp(name = Constants.Name.AUTOFOCUS)
  public void setAutofocus(boolean autofocus) {
    if (getHostView() == null) {
      return;
    }
    mAutoFocus = autofocus;
    EditText inputView = getHostView();
    if (mAutoFocus) {
      inputView.setFocusable(true);
      inputView.requestFocus();
      inputView.setFocusableInTouchMode(true);
      showSoftKeyboard();
    } else {
      hideSoftKeyboard();
    }
  }

  @WXComponentProp(name = Constants.Name.VALUE)
  public void setValue(String value) {
    WXEditText view;
    if ((view = getHostView()) == null) {
      return;
    }
    if (TextUtils.equals(view.getText(), value)) {
      return;
    }

    mIgnoreNextOnInputEvent = true;
    int oldIndex = view.getSelectionStart();
    view.setText(value);
    int index = mKeepSelectionIndex ? oldIndex : value.length();
    view.setSelection(value == null ? 0 : index);
  }

  @WXComponentProp(name = Constants.Name.COLOR)
  public void setColor(String color) {
    if (getHostView() != null && !TextUtils.isEmpty(color)) {
      int colorInt = WXResourceUtils.getColor(color);
      if (colorInt != Integer.MIN_VALUE) {
        getHostView().setTextColor(colorInt);
      }
    }
  }

  @WXComponentProp(name = Constants.Name.FONT_SIZE)
  public void setFontSize(String fontSize) {
    if (getHostView() != null && fontSize != null ) {
      Map<String, Object> map = new HashMap<>(1);
      map.put(Constants.Name.FONT_SIZE, fontSize);
      getHostView().setTextSize(TypedValue.COMPLEX_UNIT_PX, WXStyle.getFontSize(map, getInstance().getInstanceViewPortWidth()));
    }
  }

  @WXComponentProp(name = Constants.Name.TEXT_ALIGN)
  public void setTextAlign(String textAlign) {
    int align = getTextAlign(textAlign);
    if (align > 0) {
      getHostView().setGravity(align | getVerticalGravity());
    }
  }

  @WXComponentProp(name = Constants.Name.SINGLELINE)
  public void setSingleLine(boolean singleLine) {
    if (getHostView() == null) {
      return;
    }
    getHostView().setSingleLine(singleLine);
  }

  @WXComponentProp(name = Constants.Name.LINES)
  public void setLines(int lines) {
    if (getHostView() == null) {
      return;
    }
    getHostView().setLines(lines);
  }

  /**
   * Compatible with both 'max-length' and 'maxlength'
   * @param maxLength
   */
  @WXComponentProp(name = Constants.Name.MAX_LENGTH)
  public void setMaxLength(int maxLength) {
    if (getHostView() == null) {
      return;
    }
    getHostView().setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
  }

  /**
   * Compatible with both 'max-length' and 'maxlength'
   * @param maxLength
   */
  @WXComponentProp(name = Constants.Name.MAXLENGTH)
  @Deprecated
  public void setMaxlength(int maxLength) {
    setMaxLength(maxLength);
  }

  private int getInputType(String type) {
    int inputType;
    switch (type) {
      case Constants.Value.TEXT:
        inputType = InputType.TYPE_CLASS_TEXT;
        break;
      case Constants.Value.DATE:
        inputType = InputType.TYPE_NULL;
        getHostView().setFocusable(false);
        break;
      case Constants.Value.DATETIME:
        inputType = InputType.TYPE_CLASS_DATETIME;
        break;
      case Constants.Value.EMAIL:
        inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
        break;
      case Constants.Value.PASSWORD:
        inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD;
        if(getHostView() != null){
            getHostView().setTransformationMethod(PasswordTransformationMethod.getInstance());
        }
        break;
      case Constants.Value.TEL:
        inputType = InputType.TYPE_CLASS_PHONE;
        break;
      case Constants.Value.TIME:
        inputType = InputType.TYPE_NULL;
        if(getHostView() != null){
            getHostView().setFocusable(false);
        }
        break;
      case Constants.Value.URL:
        inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI;
        break;
      case Constants.Value.NUMBER:
        inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL;
        break;
      default:
        inputType = InputType.TYPE_CLASS_TEXT;
    }
    return inputType;
  }

  @WXComponentProp(name = Constants.Name.MAX)
  public void setMax(String max) {
    mMax = max;
  }

  @WXComponentProp(name = Constants.Name.MIN)
  public void setMin(String min) {
    mMin = min;
  }

  private boolean showSoftKeyboard() {
    if (getHostView() == null) {
      return false;
    } else {
      getHostView().postDelayed(WXThread.secure(new Runnable() {
        @Override
        public void run() {
          if (getInstance() != null && getInstance().getApmForInstance() != null){
            getInstance().getApmForInstance().forceStopRecordInteraction = true;
          }
          mInputMethodManager.showSoftInput(getHostView(), InputMethodManager.SHOW_IMPLICIT);
        }
      }), 100);
    }
    return true;
  }

  private void hideSoftKeyboard() {
    if (getHostView() != null) {
      getHostView().postDelayed(WXThread.secure(new Runnable() {
        @Override
        public void run() {
          mInputMethodManager.hideSoftInputFromWindow(getHostView().getWindowToken(), 0);
        }
      }), 16);
    }
  }

  private int getTextAlign(String textAlign) {
    boolean isRTL = isLayoutRTL();
    int align = isRTL ? Gravity.END : Gravity.START;
    if (TextUtils.isEmpty(textAlign)) {
      return align;
    }

    if (textAlign.equals(Constants.Value.LEFT)) {
      align = Gravity.START;
    } else if (textAlign.equals(Constants.Value.CENTER)) {
      align = Gravity.CENTER;
    } else if (textAlign.equals(Constants.Value.RIGHT)) {
      align = Gravity.END;
    }
    return align;
  }

  @JSMethod
  public void blur() {
    WXEditText host = getHostView();
    if (host != null && host.hasFocus()) {
      if (getParent() != null) {
        getParent().interceptFocus();
      }
      host.clearFocus();
      hideSoftKeyboard();
    }
  }

  @JSMethod
  public void focus() {
    WXEditText host = getHostView();
    if (host != null && !host.hasFocus()) {
      if (getParent() != null) {
        getParent().ignoreFocus();
      }
      host.requestFocus();
      host.setFocusable(true);
      host.setFocusableInTouchMode(true);
      showSoftKeyboard();
    }
  }

  @Override
  protected Object convertEmptyProperty(String propName, Object originalValue) {
    switch (propName) {
      case Constants.Name.FONT_SIZE:
        return WXText.sDEFAULT_SIZE;
      case Constants.Name.COLOR:
        return "black";
    }
    return super.convertEmptyProperty(propName, originalValue);
  }

  private void decideSoftKeyboard() {
    View hostView;
    if ((hostView = getHostView()) != null) {
      final Context context = getContext();
      if (context != null && context instanceof Activity) {
        hostView.postDelayed(WXThread.secure(new Runnable() {
          @Override
          public void run() {
            View currentFocus = ((Activity) context).getCurrentFocus();
            if (!(currentFocus instanceof EditText)) {
              mInputMethodManager.hideSoftInputFromWindow(getHostView().getWindowToken(), 0);
            }
          }
        }), 16);
      }
    }
  }

  @JSMethod
  public void setSelectionRange(int selectionStart, int selectionEnd) {
    EditText hostView;
    if ((hostView = getHostView()) != null) {
      int length = getHostView().length();
      if (selectionStart > length || selectionEnd > length) {
        return;
      }
      focus();
      hostView.setSelection(selectionStart, selectionEnd);
    }
  }

  @JSMethod
  public void getSelectionRange(String callbackId) {
    EditText hostView;
    Map<String, Object> result = new HashMap<>(2);
    if ((hostView = getHostView()) != null) {
      int start = hostView.getSelectionStart();
      int end = hostView.getSelectionEnd();

      if (!hostView.hasFocus()) {
        //The default behavior, same as iOS and web
        start = 0;
        end = 0;
      }

      result.put(Constants.Name.SELECTION_START, start);
      result.put(Constants.Name.SELECTION_END, end);
    }
    WXBridgeManager.getInstance().callback(getInstanceId(), callbackId, result, false);
  }

  @JSMethod
  public void setTextFormatter(JSONObject params) {
    try {
      String formatRule = params.getString("formatRule");
      String formatReplace = params.getString("formatReplace");
      String recoverRule = params.getString("recoverRule");
      String recoverReplace = params.getString("recoverReplace");

      PatternWrapper format = parseToPattern(formatRule, formatReplace);
      PatternWrapper recover = parseToPattern(recoverRule, recoverReplace);

      if (format != null && recover != null) {
        mFormatter = new TextFormatter(format, recover);
      }
    } catch (Throwable t) {
      t.printStackTrace();
    }
  }

  protected final void addEditorActionListener(TextView.OnEditorActionListener listener) {
    TextView view;
    if (listener != null && (view = getHostView()) != null) {
      if (mEditorActionListeners == null) {
        mEditorActionListeners = new ArrayList<>();
        view.setOnEditorActionListener(new TextView.OnEditorActionListener() {
          private boolean handled = true;

          @Override
          public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
            for (TextView.OnEditorActionListener l : mEditorActionListeners) {
              if (l != null) {
                handled = handled & l.onEditorAction(v, actionId, event);
              }
            }
            return handled;
          }
        });
      }
      mEditorActionListeners.add(listener);
    }
  }

  public final void addTextChangedListener(TextWatcher watcher) {
    if (mTextChangedListeners == null) {
      mTextChangedListeners = new ArrayList<>();
    }
    mTextChangedListeners.add(watcher);
  }

  private void addKeyboardListener(final WXEditText host) {
    if (host == null) {
      return;
    }
    final Context context = host.getContext();
    if (context != null && context instanceof Activity) {
      SoftKeyboardDetector.registerKeyboardEventListener((Activity) context, new SoftKeyboardDetector.OnKeyboardEventListener() {
        @Override
        public void onKeyboardEvent(boolean isShown) {
          if (mListeningKeyboard) {
            Map<String, Object> event = new HashMap<>(1);
            event.put("isShow", isShown);
            if (isShown) {
              Rect r = new Rect();
              ((Activity) context).getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
              float keyboardSize = WXViewUtils.getWebPxByWidth(WXViewUtils.getScreenHeight(context) - (r.bottom - r.top),
                      getInstance().getInstanceViewPortWidth());
              event.put("keyboardSize", keyboardSize);
            }
            fireEvent(Constants.Event.KEYBOARD, event);
          }
          if (!isShown) {
            blur();
          }
        }
      });
    }
  }

  @Override
  public void destroy() {
    super.destroy();
    if (mUnregister != null) {
      try {
        mUnregister.execute();
        mUnregister = null;
      } catch (Throwable throwable) {
        WXLogUtils.w("Unregister throw ", throwable);
      }
    }
  }

  private PatternWrapper parseToPattern(String jsPattern, String replace) {
    if (jsPattern == null || replace == null) {
      return null;
    }

    String checker = "/[\\S]+/[i]?[m]?[g]?";
    if (!Pattern.compile(checker).matcher(jsPattern).matches()) {
      WXLogUtils.w("WXInput", "Illegal js pattern syntax: " + jsPattern);
      return null;
    }

    int flags = 0;
    boolean global = false;
    String flagsStr = jsPattern.substring(jsPattern.lastIndexOf("/") + 1);
    String regExp = jsPattern.substring(jsPattern.indexOf("/") + 1, jsPattern.lastIndexOf("/"));

    if (flagsStr.contains("i")) {
      flags |= Pattern.CASE_INSENSITIVE;
    }

    if (flagsStr.contains("m")) {
      flags |= Pattern.DOTALL;
    }

    if (flagsStr.contains("g")) {
      global = true;
    }

    Pattern pattern = null;
    try {
      pattern = Pattern.compile(regExp, flags);
    } catch (PatternSyntaxException e) {
      WXLogUtils.w("WXInput", "Pattern syntax error: " + regExp);
    }
    if (pattern == null) {
      return null;
    }

    PatternWrapper wrapper = new PatternWrapper();
    wrapper.global = global;
    wrapper.matcher = pattern;
    wrapper.replace = replace;
    return wrapper;
  }

  private interface ReturnTypes {
    String DEFAULT = "default";
    String GO = "go";
    String NEXT = "next";
    String SEARCH = "search";
    String SEND = "send";
    String DONE = "done";
  }

  private static class PatternWrapper {
    private boolean global = false;
    private Pattern matcher;
    private String replace;
  }

  private static class TextFormatter {
    private PatternWrapper format;
    private PatternWrapper recover;

    private TextFormatter(PatternWrapper format, PatternWrapper recover) {
      this.format = format;
      this.recover = recover;
    }

    private String format(String src) {
      try {
        if (format != null) {
          if (format.global) {
            return format.matcher.matcher(src).replaceAll(format.replace);
          } else {
            return format.matcher.matcher(src).replaceFirst(format.replace);
          }
        }
      } catch (Throwable t) {
        //maybe IndexOutOfBoundsException caused by illegal replace
        WXLogUtils.w("WXInput", "[format] " + t.getMessage());
      }
      return src;
    }

    private String recover(String formatted) {
      try {
        if (recover != null) {
          if (recover.global) {
            return recover.matcher.matcher(formatted).replaceAll(recover.replace);
          } else {
            return recover.matcher.matcher(formatted).replaceFirst(recover.replace);
          }
        }
      } catch (Throwable t) {
        //same cause as format
        WXLogUtils.w("WXInput", "[formatted] " + t.getMessage());
      }
      return formatted;
    }
  }
}
