/*
 * Copyright (C) 2008,2009  OMRON SOFTWARE Co., Ltd.
 *
 * 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 com.hiroshica.android.input.nicownn2;


import android.content.SharedPreferences;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.BackgroundColorSpan;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.MotionEvent;
import android.view.View;
import android.view.KeyCharacterMap;
import android.text.method.MetaKeyKeyListener;

import java.util.regex.Pattern;
import java.util.regex.Matcher;

import com.hiroshica.android.input.nicownn2.EN.NicoWnnEngineEN;
import com.hiroshica.android.input.nicownn2.JAJP.*;

/**
 * The OpenWnn Japanese IME class
 *
 * @author Copyright (C) 2009 OMRON SOFTWARE CO., LTD.  All Rights Reserved.
 */
public class NicoWnnJAJP extends NicoWnn {
	/**
	 * Mode of the convert engine (Full-width KATAKANA).
	 * Use with {@code OpenWnn.CHANGE_MODE} event.
	 */
	public static final int ENGINE_MODE_FULL_KATAKANA = 101;

	/**
	 * Mode of the convert engine (Half-width KATAKANA).
	 * Use with {@code OpenWnn.CHANGE_MODE} event.
	 */
	public static final int ENGINE_MODE_HALF_KATAKANA = 102;

	/**
	 * Mode of the convert engine (EISU-KANA conversion).
	 * Use with {@code OpenWnn.CHANGE_MODE} event.
	 */
	public static final int ENGINE_MODE_EISU_KANA = 103;

	/**
	 * Mode of the convert engine (Symbol list).
	 * Use with {@code OpenWnn.CHANGE_MODE} event.
	 */
	public static final int ENGINE_MODE_SYMBOL = 104;
	public static final int ENGINE_MODE_DOCOMOSYMBOL = 107;

	/**
	 * Mode of the convert engine (Keyboard type is QWERTY).
	 * Use with {@code OpenWnn.CHANGE_MODE} event to change ambiguous searching pattern.
	 */
	public static final int ENGINE_MODE_OPT_TYPE_QWERTY = 105;

	/**
	 * Mode of the convert engine (Keyboard type is 12-keys).
	 * Use with {@code OpenWnn.CHANGE_MODE} event to change ambiguous searching pattern.
	 */
	public static final int ENGINE_MODE_OPT_TYPE_12KEY = 106;

	/** Never move cursor in to the composing text (adapting to IMF's specification change) */
	private static final boolean FIX_CURSOR_TEXT_END = true;

	/** Highlight color style for the converted clause */
	private static final CharacterStyle SPAN_CONVERT_BGCOLOR_HL   = new BackgroundColorSpan(0xFF8888FF);
	/** Highlight color style for the selected string  */
	private static final CharacterStyle SPAN_EXACT_BGCOLOR_HL     = new BackgroundColorSpan(0xFF66CDAA);
	/** Highlight color style for EISU-KANA conversion */
	private static final CharacterStyle SPAN_EISUKANA_BGCOLOR_HL  = new BackgroundColorSpan(0xFF9FB6CD);
	/** Highlight color style for the composing text */
	private static final CharacterStyle SPAN_REMAIN_BGCOLOR_HL    = new BackgroundColorSpan(0xFFF0FFFF);
	/** Highlight text color */
	private static final CharacterStyle SPAN_TEXTCOLOR  = new ForegroundColorSpan(0xFF000000);
	/** Underline style for the composing text */
	private static final CharacterStyle SPAN_UNDERLINE            = new UnderlineSpan();

	/** IME's status for {@code mStatus} input/no candidates). */
	private static final int STATUS_INIT            = 0x0000;
	/** IME's status for {@code mStatus}(input characters). */
	private static final int STATUS_INPUT           = 0x0001;
	/** IME's status for {@code mStatus}(input functional keys). */
	private static final int STATUS_INPUT_EDIT      = 0x0003;
	/** IME's status for {@code mStatus}(all candidates are displayed). */
	private static final int STATUS_CANDIDATE_FULL  = 0x0010;

	/** Alphabet-last pattern */
	private static final Pattern ENGLISH_CHARACTER_LAST = Pattern.compile(".*[a-zA-Z]$");

	/**
	 *  Private area character code got by {@link KeyEvent#getUnicodeChar()}.
	 *   (SHIFT+ALT+X G1 specific)
	 */
	private static final int PRIVATE_AREA_CODE = 61184;

	/** Maximum length of input string */
	private static final int LIMIT_INPUT_NUMBER = 30;

	/** Bit flag for English auto commit mode (ON) */
	private static final int AUTO_COMMIT_ENGLISH_ON      = 0x0000;
	/** Bit flag for English auto commit mode (OFF) */
	private static final int AUTO_COMMIT_ENGLISH_OFF     = 0x0001;
	/** Bit flag for English auto commit mode (symbol list) */
	private static final int AUTO_COMMIT_ENGLISH_SYMBOL  = 0x0010;

	/** Message for {@code mHandler} (execute prediction) */
	private static final int MSG_PREDICTION = 0;

	/** Message for {@code mHandler} (execute tutorial) */
	private static final int MSG_START_TUTORIAL = 1;

	/** Message for {@code mHandler} (close) */
	private static final int MSG_CLOSE = 2;

	/** Delay time(msec.) to start prediction after key input when the candidates view is not shown. */
	private static final int PREDICTION_DELAY_MS_1ST = 200;

	/** Delay time(msec.) to start prediction after key input when the candidates view is shown. */
	private static final int PREDICTION_DELAY_MS_SHOWING_CANDIDATE = 200;
    

	/** Convert engine's state */
	private class EngineState {
		/** Definition for {@code EngineState.*} (invalid) */
		public static final int INVALID = -1;

		/** Definition for {@code EngineState.dictionarySet} (Japanese) */
		public static final int DICTIONARYSET_JP = 0;

		/** Definition for {@code EngineState.dictionarySet} (English) */
		public static final int DICTIONARYSET_EN = 1;

		/** Definition for {@code EngineState.convertType} (prediction/no conversion) */
		public static final int CONVERT_TYPE_NONE = 0;

		/** Definition for {@code EngineState.convertType} (consecutive clause conversion) */
		public static final int CONVERT_TYPE_RENBUN = 1;

		/** Definition for {@code EngineState.convertType} (EISU-KANA conversion) */
		public static final int CONVERT_TYPE_EISU_KANA = 2;

		/** Definition for {@code EngineState.temporaryMode} (change back to the normal dictionary) */
		public static final int TEMPORARY_DICTIONARY_MODE_NONE = 0;

		/** Definition for {@code EngineState.temporaryMode} (change to the symbol dictionary) */
		public static final int TEMPORARY_DICTIONARY_MODE_SYMBOL = 1;
		public static final int TEMPORARY_DICTIONARY_MODE_DOCOMOSYMBOL = 3;

		/** Definition for {@code EngineState.temporaryMode} (change to the user dictionary) */
		public static final int TEMPORARY_DICTIONARY_MODE_USER = 2;

		/** Definition for {@code EngineState.preferenceDictionary} (no preference dictionary) */
		public static final int PREFERENCE_DICTIONARY_NONE = 0;

		/** Definition for {@code EngineState.preferenceDictionary} (person's name) */
		public static final int PREFERENCE_DICTIONARY_PERSON_NAME = 1;

		/** Definition for {@code EngineState.preferenceDictionary} (place name) */
		public static final int PREFERENCE_DICTIONARY_POSTAL_ADDRESS = 2;

		/** Definition for {@code EngineState.preferenceDictionary} (email/URI) */
		public static final int PREFERENCE_DICTIONARY_EMAIL_ADDRESS_URI = 3;

		/** Definition for {@code EngineState.keyboard} (undefined) */
		public static final int KEYBOARD_UNDEF = 0;

		/** Definition for {@code EngineState.keyboard} (QWERTY) */
		public static final int KEYBOARD_QWERTY = 1;

		/** Definition for {@code EngineState.keyboard} (12-keys) */
		public static final int KEYBOARD_12KEY  = 2;

		/** Set of dictionaries */
		public int dictionarySet = INVALID;

		/** Type of conversion */
		public int convertType = INVALID;

		/** Temporary mode */
		public int temporaryMode = INVALID;

		/** Preference dictionary setting */
		public int preferenceDictionary = INVALID;

		/** keyboard */
		public int keyboard = INVALID;

		/**
		 * Returns whether current type of conversion is consecutive clause(RENBUNSETSU) conversion.
		 * 
		 * @return {@code true} if current type of conversion is consecutive clause conversion.
		 */
		public boolean isRenbun() {
			return convertType == CONVERT_TYPE_RENBUN;
		}

		/**
		 * Returns whether current type of conversion is EISU-KANA conversion.
		 * 
		 * @return {@code true} if current type of conversion is EISU-KANA conversion.
		 */
		public boolean isEisuKana() {
			return convertType == CONVERT_TYPE_EISU_KANA;
		}

		/**
		 * Returns whether current type of conversion is no conversion.
		 * 
		 * @return {@code true} if no conversion is executed currently.
		 */
		public boolean isConvertState() {
			return convertType != CONVERT_TYPE_NONE;
		}

		/**
		 * Check whether or not the mode is "symbol list".
		 * 
		 * @return {@code true} if the mode is "symbol list".
		 */
		public boolean isSymbolList() {
			return ((temporaryMode == TEMPORARY_DICTIONARY_MODE_SYMBOL) || (temporaryMode == TEMPORARY_DICTIONARY_MODE_DOCOMOSYMBOL));
		}
		public boolean isDocomoSymbolList() {
			return temporaryMode == TEMPORARY_DICTIONARY_MODE_DOCOMOSYMBOL;
		}

		/**
		 * Check whether or not the current language is English.
		 * 
		 * @return {@code true} if the current language is English.
		 */
		public boolean isEnglish() {
			return dictionarySet == DICTIONARYSET_EN;
		}
	}

	/** IME's status */
	protected int mStatus = STATUS_INIT;

	/** Whether exact match searching or not */
	protected boolean mExactMatchMode = false;

	/** Spannable string builder for displaying the composing text */
	protected SpannableStringBuilder mDisplayText;

	/** Instance of this service */
	private static NicoWnnJAJP mSelf = null;

	/** Backup for switching the converter */
	private WnnEngine mConverterBack;

	/** Backup for switching the pre-converter */
	private LetterConverter mPreConverterBack;

	/** OpenWnn conversion engine for Japanese */
	private NicoWnnEngineJAJP mConverterJAJP;

	/** OpenWnn conversion engine for English */
	private NicoWnnEngineEN mConverterEN;

	/** Conversion engine for listing symbols */
	private SymbolList mConverterSymbolEngineBack;

	/** Symbol lists to display when the symbol key is pressed */
	private static final String[] SYMBOL_LISTS = {
		//SymbolList.SYMBOL_DOCOMO_EMOJI, SymbolList.SYMBOL_JAPANESE, SymbolList.SYMBOL_ENGLISH, SymbolList.SYMBOL_JAPANESE_FACE
		SymbolList.SYMBOL_JAPANESE, SymbolList.SYMBOL_ENGLISH, SymbolList.SYMBOL_JAPANESE_FACE
	};

	/** Current symbol list */
	private int mCurrentSymbol = 0;

	/** Romaji-to-Kana converter (HIRAGANA) */
	private Romkan mPreConverterHiragana;

	/** Romaji-to-Kana converter (full-width KATAKANA) */
	private RomkanFullKatakana mPreConverterFullKatakana;

	/** Romaji-to-Kana converter (half-width KATAKANA) */
	private RomkanHalfKatakana mPreConverterHalfKatakana;

	/** Conversion Engine's state */
	private EngineState mEngineState = new EngineState();

	/** Whether learning function is active of not. */
	private boolean mEnableLearning = true;

	/** Whether prediction is active or not. */
	private boolean mEnablePrediction = true;

	/** Whether using the converter */
	private boolean mEnableConverter = true;

	/** Whether displaying the symbol list */
	private boolean mEnableSymbolList = true;

	/** Whether non ASCII code is enabled */
	private boolean mEnableSymbolListNonHalf = true;

	/** Enable mistyping spell correction or not */
	private boolean mEnableSpellCorrection = true;

	/** Auto commit state (in English mode) */
	private int mDisableAutoCommitEnglishMask = AUTO_COMMIT_ENGLISH_ON;

	/** Whether removing a space before a separator or not. (in English mode) */
	private boolean mEnableAutoDeleteSpace = false;

	/** Whether auto-spacing is enabled or not. */
	private boolean mEnableAutoInsertSpace = true;

	/** Whether dismissing the keyboard when the enter key is pressed */
	private boolean mEnableAutoHideKeyboard = true;

	/** Number of committed clauses on consecutive clause conversion */
	private int mCommitCount = 0;

	/** Target layer of the {@link ComposingText} */
	private int mTargetLayer = 1;

	/** Current orientation of the display */
	private int mOrientation = Configuration.ORIENTATION_UNDEFINED;

	/** Current normal dictionary set */
	private int mPrevDictionarySet = NicoWnnEngineJAJP.DIC_LANG_INIT;

	/** Regular expression pattern for English separators */
	private  Pattern mEnglishAutoCommitDelimiter = null;

	/** Cursor position in the composing text */
	private int mComposingStartCursor = 0;

	/** Cursor position before committing text */
	private int mCommitStartCursor = 0;

	/** Previous committed text */
	private StringBuffer mPrevCommitText = null;

	/** Call count of {@code commitText} */
	private int mPrevCommitCount = 0;

	/** Shift lock status of the Hardware keyboard */
	private int mHardShift;

	/** SHIFT key state (pressing) */
	private boolean mShiftPressing;

	/** ALT lock status of the Hardware keyboard */
	private int mHardAlt;

	/** ALT key state (pressing) */
	private boolean mAltPressing;

	/** Shift lock toggle definition */
	private static final int[] mShiftKeyToggle = {0, MetaKeyKeyListener.META_SHIFT_ON, MetaKeyKeyListener.META_CAP_LOCKED};

	/** ALT lock toggle definition */
	private static final int[] mAltKeyToggle = {0, MetaKeyKeyListener.META_ALT_ON, MetaKeyKeyListener.META_ALT_LOCKED};

	/** Auto caps mode */
	private boolean mAutoCaps = false;

	/** List of words in the user dictionary */
	private WnnWord[] mUserDictionaryWords = null;
    
	/** Tutorial */
	private TutorialJAJP mTutorial;

	/** Whether tutorial mode or not */
	private boolean mEnableTutorial;

	/** Whether there is a continued predicted candidate */
	private boolean mHasContinuedPrediction = false;

	/** {@code Handler} for drawing candidates/displaying tutorial */
	Handler mHandler = new Handler() {
		@Override
                public void handleMessage(Message msg) {
			switch (msg.what) {
			case MSG_PREDICTION:
				updatePrediction();
				break;
			case MSG_START_TUTORIAL:
				if (mTutorial == null) {
					if (isInputViewShown()) {
						DefaultSoftKeyboardJAJP inputManager = ((DefaultSoftKeyboardJAJP) mInputViewManager);
						View v = inputManager.getKeyboardView();
						mTutorial = new TutorialJAJP(NicoWnnJAJP.this, v, inputManager);
                                                         
						mTutorial.start();
					} else {
						/* Try again soon if the view is not yet showing */
						sendMessageDelayed(obtainMessage(MSG_START_TUTORIAL), 100);
					}
				}
				break;
			case MSG_CLOSE:
				if (mConverterJAJP != null) mConverterJAJP.close();
				if (mConverterEN != null) mConverterEN.close();
				if (mConverterSymbolEngineBack != null) mConverterSymbolEngineBack.close();
				break;
			}
		}
        };

	/** The candidate filter */
	private CandidateFilter mFilter;

	/**
	 * Constructor
	 */
	public NicoWnnJAJP() {
		super();
		mSelf = this;
		mComposingText = new ComposingText();
		mCandidatesViewManager = new TextCandidatesViewManager(-1);
		mInputViewManager  = new DefaultSoftKeyboardJAJP();
		mConverter = mConverterJAJP = new NicoWnnEngineJAJP(NicoWnn.writableJAJPDic);
		mConverterEN = new NicoWnnEngineEN(NicoWnn.writableENDic);
		mPreConverter = mPreConverterHiragana = new Romkan();
		mPreConverterFullKatakana = new RomkanFullKatakana();
		mPreConverterHalfKatakana = new RomkanHalfKatakana();
		mFilter = new CandidateFilter();

		mDisplayText = new SpannableStringBuilder();
		mAutoHideMode = false;

		mPrevCommitText = new StringBuffer();
	}

	/**
	 * Constructor
	 *
	 * @param context       The context
	 */
	public NicoWnnJAJP(Context context) {
		this();
		attachBaseContext(context);
	}

	/** @see com.hiroshica.android.input.nicownn2.NicoWnn#onCreate */
	@Override public void onCreate() {
		super.onCreate();

		String delimiter = Pattern.quote(getResources().getString(R.string.en_word_separators));
		mEnglishAutoCommitDelimiter = Pattern.compile(".*[" + delimiter + "]$");
		if (mConverterSymbolEngineBack == null) {
			mConverterSymbolEngineBack = new SymbolList(this, SymbolList.LANG_JA);
		}
	}

	/** @see com.hiroshica.android.input.nicownn2.NicoWnn#onCreateInputView */
	@Override public View onCreateInputView() {
		int hiddenState = getResources().getConfiguration().hardKeyboardHidden;
		boolean hidden = (hiddenState == Configuration.HARDKEYBOARDHIDDEN_YES);
		((DefaultSoftKeyboardJAJP) mInputViewManager).setHardKeyboardHidden(hidden);
		mEnableTutorial = hidden;
		return super.onCreateInputView();
	}

	/** @see com.hiroshica.android.input.nicownn2.NicoWnn#onStartInputView */
	@Override public void onStartInputView(EditorInfo attribute, boolean restarting) {

		EngineState state = new EngineState();
		state.temporaryMode = EngineState.TEMPORARY_DICTIONARY_MODE_NONE;
		updateEngineState(state);

		mPrevCommitCount = 0;
		clearCommitInfo();

		/* load preferences */
		SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);

		/* start inputview */
		((DefaultSoftKeyboard) mInputViewManager).resetCurrentKeyboard();

		super.onStartInputView(attribute, restarting);

		/* initialize views */
		mCandidatesViewManager.clearCandidates();
		/* initialize status */
		mStatus = STATUS_INIT;
		mExactMatchMode = false;       

		/* hardware keyboard support */
		mHardShift = 0;
		mHardAlt   = 0;
		updateMetaKeyStateDisplay();

		/* initialize the engine's state */
		fitInputType(pref, attribute);

		((TextCandidatesViewManager)mCandidatesViewManager).setAutoHide(true);

		if (isEnableL2Converter()) {
			breakSequence();
		}
	}

	/** @see com.hiroshica.android.input.nicownn2.NicoWnn#hideWindow */
	@Override public void hideWindow() {
		mComposingText.clear();
		mInputViewManager.onUpdateState(this);
		clearCommitInfo();
		mHandler.removeMessages(MSG_START_TUTORIAL);
		mInputViewManager.closing();
		if (mTutorial != null) {
			mTutorial.close();
			mTutorial = null;
		}

		super.hideWindow();
	}

	/** @see com.hiroshica.android.input.nicownn2.NicoWnn#onUpdateSelection */
	@Override public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {

		mComposingStartCursor = (candidatesStart < 0) ? newSelEnd : candidatesStart;

		if (newSelStart != newSelEnd) {
			clearCommitInfo();
		}

		if (mHasContinuedPrediction) {
			mHasContinuedPrediction = false;
			if (0 < mPrevCommitCount) {
				mPrevCommitCount--;
			}
			return;
		}

		boolean isNotComposing = ((candidatesStart < 0) && (candidatesEnd < 0));
		if ((mComposingText.size(ComposingText.LAYER1) != 0)
		    && !isNotComposing) {
			updateViewStatus(mTargetLayer, false, true);
		} else {
			if (0 < mPrevCommitCount) {
				mPrevCommitCount--;
			} else {
				int commitEnd = mCommitStartCursor + mPrevCommitText.length();
				if ((((newSelEnd < oldSelEnd) || (commitEnd < newSelEnd)) && clearCommitInfo())
				    || isNotComposing) {
					if (isEnableL2Converter()) {
						breakSequence();
					}

					if (mInputConnection != null) {
						if (isNotComposing && (mComposingText.size(ComposingText.LAYER1) != 0)) {
							mInputConnection.finishComposingText();
						}
					}
					initializeScreen();
				}
			}
		}
	}

	/** @see com.hiroshica.android.input.nicownn2.NicoWnn#onConfigurationChanged */
	@Override public void onConfigurationChanged(Configuration newConfig) {
		try {
			super.onConfigurationChanged(newConfig);
            
			if (mInputConnection != null) {
				if (super.isInputViewShown()) {
					updateViewStatus(mTargetLayer, true, true);
				}

				/* display orientation */
				if (mOrientation != newConfig.orientation) {
					mOrientation = newConfig.orientation;
					commitConvertingText();
					initializeScreen();
				}

				/* Hardware keyboard */
				int hiddenState = newConfig.hardKeyboardHidden;
				boolean hidden = (hiddenState == Configuration.HARDKEYBOARDHIDDEN_YES);
				((DefaultSoftKeyboardJAJP) mInputViewManager).setHardKeyboardHidden(hidden);
				mEnableTutorial = hidden;
			}
		} catch (Exception ex) {
			/* do nothing if an error occurs. */
		}
	}

	/** @see com.hiroshica.android.input.nicownn2.NicoWnn#onEvent */
	@Override synchronized public boolean onEvent(NicoWnnEvent ev) {

		EngineState state;

		/* handling events which are valid when InputConnection is not active. */
		switch (ev.code) {
        
		case NicoWnnEvent.KEYUP:
			onKeyUpEvent(ev.keyEvent);
			return true;
            
		case NicoWnnEvent.INITIALIZE_LEARNING_DICTIONARY:
			mConverterEN.initializeDictionary(WnnEngine.DICTIONARY_TYPE_LEARN);
			mConverterJAJP.initializeDictionary(WnnEngine.DICTIONARY_TYPE_LEARN);
			return true;

		case NicoWnnEvent.INITIALIZE_USER_DICTIONARY:
			return mConverterJAJP.initializeDictionary( WnnEngine.DICTIONARY_TYPE_USER );

		case NicoWnnEvent.LIST_WORDS_IN_USER_DICTIONARY:
			mUserDictionaryWords = mConverterJAJP.getUserDictionaryWords( );
			return true;

		case NicoWnnEvent.GET_WORD:
			if (mUserDictionaryWords != null) {
				ev.word = mUserDictionaryWords[0];
				for (int i = 0 ; i < mUserDictionaryWords.length - 1 ; i++) {
					mUserDictionaryWords[i] = mUserDictionaryWords[i + 1];
				}
				mUserDictionaryWords[mUserDictionaryWords.length - 1] = null;
				if (mUserDictionaryWords[0] == null) {
					mUserDictionaryWords = null;
				}
				return true;
			}
			break;

		case NicoWnnEvent.ADD_WORD:
			mConverterJAJP.addWord(ev.word);
			return true;

		case NicoWnnEvent.DELETE_WORD:
			mConverterJAJP.deleteWord(ev.word);
			return true;

		case NicoWnnEvent.CHANGE_MODE:
			changeEngineMode(ev.mode);
			if (!(ev.mode == ENGINE_MODE_SYMBOL || ev.mode == ENGINE_MODE_DOCOMOSYMBOL || ev.mode == ENGINE_MODE_EISU_KANA)) {
				initializeScreen();
			}
			return true;

		case NicoWnnEvent.UPDATE_CANDIDATE:
			if (mEngineState.isRenbun()) {
				mComposingText.setCursor(ComposingText.LAYER1,
							 mComposingText.toString(ComposingText.LAYER1).length());
				mExactMatchMode = false;
				updateViewStatusForPrediction(true, true);
			} else {
				updateViewStatus(mTargetLayer, true, true);
			}
			return true;

		case NicoWnnEvent.CHANGE_INPUT_VIEW:
			setInputView(onCreateInputView());
			return true;

		case NicoWnnEvent.CANDIDATE_VIEW_TOUCH:
			boolean ret;
			ret = ((TextCandidatesViewManager)mCandidatesViewManager).onTouchSync();
			return ret;

		case NicoWnnEvent.TOUCH_OTHER_KEY:
			mStatus |= STATUS_INPUT_EDIT;
			return true;

		default:
			break;
		}

		KeyEvent keyEvent = ev.keyEvent;
		int keyCode = 0;
		if (keyEvent != null) {
			keyCode = keyEvent.getKeyCode();
		}

		if (mDirectInputMode) {
			if (ev.code == NicoWnnEvent.INPUT_SOFT_KEY && mInputConnection != null) {
				mInputConnection.sendKeyEvent(keyEvent);
				mInputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
									   keyEvent.getKeyCode()));
			}

			/* return if InputConnection is not active */
			return false;
		}
        
		if (!((ev.code == NicoWnnEvent.COMMIT_COMPOSING_TEXT)
		      || ((keyEvent != null)
			  && ((keyCode == KeyEvent.KEYCODE_SHIFT_LEFT)
			      || (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)
			      || (keyCode == KeyEvent.KEYCODE_ALT_LEFT)
			      || (keyCode == KeyEvent.KEYCODE_ALT_RIGHT)
			      || (keyEvent.isAltPressed() && (keyCode == KeyEvent.KEYCODE_SPACE)))))) {

			clearCommitInfo();
		}       
                
		/* change back the dictionary if necessary */
		if (!((ev.code == NicoWnnEvent.SELECT_CANDIDATE)
		      || (ev.code == NicoWnnEvent.LIST_CANDIDATES_NORMAL)
		      || (ev.code == NicoWnnEvent.LIST_CANDIDATES_FULL)
		      || ((keyEvent != null)
			  && ((keyCode == KeyEvent.KEYCODE_SHIFT_LEFT)
			      ||(keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)
			      ||(keyCode == KeyEvent.KEYCODE_ALT_LEFT)
			      ||(keyCode == KeyEvent.KEYCODE_ALT_RIGHT)
			      ||(keyCode == KeyEvent.KEYCODE_BACK && mCandidatesViewManager.getViewType() == CandidatesViewManager.VIEW_TYPE_FULL)
			      ||(keyEvent.isAltPressed() && (keyCode == KeyEvent.KEYCODE_SPACE)))))) {

			state = new EngineState();
			state.temporaryMode = EngineState.TEMPORARY_DICTIONARY_MODE_NONE;
			updateEngineState(state);
		}

		if (ev.code == NicoWnnEvent.LIST_CANDIDATES_FULL) {
			mStatus |= STATUS_CANDIDATE_FULL;
			mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_FULL);
			return true;
		} else if (ev.code == NicoWnnEvent.LIST_CANDIDATES_NORMAL) {
			mStatus &= ~STATUS_CANDIDATE_FULL;
			mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_NORMAL);
			return true;
		}

		boolean ret = false;
		switch (ev.code) {
		case NicoWnnEvent.INPUT_CHAR:
			if ((mPreConverter == null) && !isEnableL2Converter()) {
				/* direct input (= full-width alphabet/number input) */
				commitText(false);
				commitText(new String(ev.chars));
				mCandidatesViewManager.clearCandidates();
			} else if (!isEnableL2Converter()) {
				processSoftKeyboardCodeWithoutConversion(ev.chars);
			} else {
				processSoftKeyboardCode(ev.chars);
			}
			ret = true;
			break;

		case NicoWnnEvent.TOGGLE_CHAR:
			processSoftKeyboardToggleChar(ev.toggleTable);
			ret = true;
			break;

		case NicoWnnEvent.TOGGLE_REVERSE_CHAR:
			if (((mStatus & ~STATUS_CANDIDATE_FULL) == STATUS_INPUT)
			    && !(mEngineState.isConvertState())) {

				int cursor = mComposingText.getCursor(ComposingText.LAYER1);
				if (cursor > 0) {
					String prevChar = mComposingText.getStrSegment(ComposingText.LAYER1, cursor - 1).string;
					String c = searchToggleCharacter(prevChar, ev.toggleTable, true);
					if (c != null) {
						mComposingText.delete(ComposingText.LAYER1, false);
						appendStrSegment(new StrSegment(c));
						updateViewStatusForPrediction(true, true);
						ret = true;
						break;
					}
				}
			}
			break;

		case NicoWnnEvent.REPLACE_CHAR:
			int cursor = mComposingText.getCursor(ComposingText.LAYER1);
			if ((cursor > 0)
			    && !(mEngineState.isConvertState())) {

				String search = mComposingText.getStrSegment(ComposingText.LAYER1, cursor - 1).string;
				String c = (String)ev.replaceTable.get(search);
				if (c != null) {
					mComposingText.delete(1, false);
					appendStrSegment(new StrSegment(c));
					updateViewStatusForPrediction(true, true);
					ret = true;
					mStatus = STATUS_INPUT_EDIT;
					break;
				}
			}
			break;

		case NicoWnnEvent.INPUT_KEY:
			/* update shift/alt state */
			switch (keyCode) {
			case KeyEvent.KEYCODE_DPAD_DOWN:
			case KeyEvent.KEYCODE_DPAD_LEFT:
			case KeyEvent.KEYCODE_DPAD_RIGHT:
			case KeyEvent.KEYCODE_DPAD_UP:
				if (mTutorial != null) {
					return true;
				}
				break;
            
			case KeyEvent.KEYCODE_ALT_LEFT:
			case KeyEvent.KEYCODE_ALT_RIGHT:
				if (keyEvent.getRepeatCount() == 0) {
					if (++mHardAlt > 2) { mHardAlt = 0; }
				}
				mAltPressing   = true;
				updateMetaKeyStateDisplay();
				return true;

			case KeyEvent.KEYCODE_SHIFT_LEFT:
			case KeyEvent.KEYCODE_SHIFT_RIGHT:
				if (keyEvent.getRepeatCount() == 0) {
					if (++mHardShift > 2) { mHardShift = 0; }
				}
				mShiftPressing = true;
				updateMetaKeyStateDisplay();
				return true;
			}

			/* handle other key event */
			ret = processKeyEvent(keyEvent);
			break;

		case NicoWnnEvent.INPUT_SOFT_KEY:
			ret = processKeyEvent(keyEvent);
			if (!ret) {
				mInputConnection.sendKeyEvent(keyEvent);
				mInputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEvent.getKeyCode()));
				ret = true;
			}
			break;

		case NicoWnnEvent.SELECT_CANDIDATE:
			initCommitInfoForWatchCursor();
			if (isEnglishPrediction()) {
				mComposingText.clear();
			}
			mStatus = commitText(ev.word);
			if (isEnglishPrediction() && !mEngineState.isSymbolList() && mEnableAutoInsertSpace) {
				commitSpaceJustOne();
			}
			checkCommitInfo();

			if (mEngineState.isSymbolList()) {
				mEnableAutoDeleteSpace = false;
			}
			break;

		case NicoWnnEvent.CONVERT:           
			startConvert(EngineState.CONVERT_TYPE_RENBUN);
			break;

		case NicoWnnEvent.COMMIT_COMPOSING_TEXT:
			commitAllText();
			break;
		}

		return ret;
	}

	/** @see com.hiroshica.android.input.nicownn2.NicoWnn#onEvaluateFullscreenMode */
	@Override public boolean onEvaluateFullscreenMode() {
		/* never use full-screen mode */
		return false;
	}

	/** @see com.hiroshica.android.input.nicownn2.NicoWnn#onEvaluateInputViewShown */
	@Override public boolean onEvaluateInputViewShown() {
		return true;
	}

	/**
	 * Get the instance of this service.
	 * <br>
	 * Before using this method, the constructor of this service must be invoked.
	 *
	 * @return      The instance of this service
	 */
	public static NicoWnnJAJP getInstance() {
		return mSelf;
	}

	/**
	 * Create a {@link StrSegment} from a character code.
	 * <br>
	 * @param charCode           A character code
	 * @return                  {@link StrSegment} created; {@code null} if an error occurs.
	 */
	private StrSegment createStrSegment(int charCode) {
		if (charCode == 0) {
			return null;
		}
		return new StrSegment(Character.toChars(charCode));
	}

	/**
	 * Key event handler.
	 *
	 * @param ev        A key event
	 * @return  {@code true} if the event is handled in this method.
	 */
	private boolean processKeyEvent(KeyEvent ev) {
		int key = ev.getKeyCode();

		/* keys which produce a glyph */
		if (ev.isPrintingKey()) {
			/* do nothing if the character is not able to display or the character is dead key */
			if ((mHardShift > 0 && mHardAlt > 0) ||
			    (ev.isAltPressed() == true && ev.isShiftPressed() == true)) {
				int charCode = ev.getUnicodeChar(MetaKeyKeyListener.META_SHIFT_ON | MetaKeyKeyListener.META_ALT_ON);
				if (charCode == 0 || (charCode & KeyCharacterMap.COMBINING_ACCENT) != 0 || charCode == PRIVATE_AREA_CODE) {
					if(mHardShift == 1){
						mShiftPressing = false;
					}
					if(mHardAlt == 1){
						mAltPressing   = false;
					}
					if(!ev.isAltPressed()){
						if (mHardAlt == 1) {
							mHardAlt = 0;
						}
					}
					if(!ev.isShiftPressed()){
						if (mHardShift == 1) {
							mHardShift = 0;
						}
					}
					if(!ev.isShiftPressed() && !ev.isAltPressed()){
						updateMetaKeyStateDisplay();
					}
					return true;
				}
			}

			commitConvertingText();

			EditorInfo edit = getCurrentInputEditorInfo();
			StrSegment str;

			/* get the key character */
			if (mHardShift== 0 && mHardAlt == 0) {
				/* no meta key is locked */
				int shift = (mAutoCaps)? getShiftKeyState(edit) : 0;
				if (shift != mHardShift && (key >= KeyEvent.KEYCODE_A && key <= KeyEvent.KEYCODE_Z)) {
					/* handling auto caps for a alphabet character */
					str = createStrSegment(ev.getUnicodeChar(MetaKeyKeyListener.META_SHIFT_ON));
				} else {
					str = createStrSegment(ev.getUnicodeChar());
				}
			} else {
				str = createStrSegment(ev.getUnicodeChar(mShiftKeyToggle[mHardShift]
									 | mAltKeyToggle[mHardAlt]));
				if(mHardShift == 1){
					mShiftPressing = false;
				}
				if(mHardAlt == 1){
					mAltPressing   = false;
				}
				/* back to 0 (off) if 1 (on/not locked) */
				if (!ev.isAltPressed()) {
					if (mHardAlt == 1) {
						mHardAlt = 0;
					}
				}
				if (!ev.isShiftPressed()) {
					if (mHardShift == 1) {
						mHardShift = 0;
					}
				}
				if (!ev.isShiftPressed() && !ev.isShiftPressed()) {
					updateMetaKeyStateDisplay();
				}
			}
            
			if (str == null) {
				return true;
			}

			/* append the character to the composing text if the character is not TAB */
			if (str.string.charAt(0) != '\u0009') {
				processHardwareKeyboardInputChar(str);
				return true;
			} else {
				commitText(true);
				commitText(str.string);
				initializeScreen();
				return true;
			}

		} else if (key == KeyEvent.KEYCODE_SPACE) {
			/* H/W space key */
			processHardwareKeyboardSpaceKey(ev);
			return true;

		} else if (key == KeyEvent.KEYCODE_SYM) {
			/* display the symbol list */
			initCommitInfoForWatchCursor();
			mStatus = commitText(true);
			checkCommitInfo();
			changeEngineMode(ENGINE_MODE_SYMBOL);
			mHardAlt = 0;
			updateMetaKeyStateDisplay();
			return true;
		}

		/* Functional key */
		if (mComposingText.size(ComposingText.LAYER1) > 0) {
			switch (key) {
			case KeyEvent.KEYCODE_DEL:
				mStatus = STATUS_INPUT_EDIT;
				if (mEngineState.isConvertState()) {
					mComposingText.setCursor(ComposingText.LAYER1,
								 mComposingText.toString(ComposingText.LAYER1).length());
					mExactMatchMode = false;
				} else {
					if (mComposingText.size(ComposingText.LAYER1) == 1) {
						initializeScreen();
						return true;
					} else {
						mComposingText.delete(ComposingText.LAYER1, false);
					}
				}
				updateViewStatusForPrediction(true, true);
				return true;

			case KeyEvent.KEYCODE_BACK:
				if (mCandidatesViewManager.getViewType() == CandidatesViewManager.VIEW_TYPE_FULL) {
					mStatus &= ~STATUS_CANDIDATE_FULL;
					mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_NORMAL);
				} else {
					if (!mEngineState.isConvertState()) {
						initializeScreen();
						if (mConverter != null) {
							mConverter.init();
						}
					} else {
						mCandidatesViewManager.clearCandidates();
						mStatus = STATUS_INPUT_EDIT;
						mExactMatchMode = false;
						mComposingText.setCursor(ComposingText.LAYER1,
									 mComposingText.toString(ComposingText.LAYER1).length());
						updateViewStatusForPrediction(true, true);
					}
				}
				return true;

			case KeyEvent.KEYCODE_DPAD_LEFT:
				if (!isEnableL2Converter()) {
					commitText(false);
					return false;
				} else {
					processLeftKeyEvent();
					return true;
				}

			case KeyEvent.KEYCODE_DPAD_RIGHT:
				if (!isEnableL2Converter()) {
					if (mEngineState.keyboard == EngineState.KEYBOARD_12KEY) {
						commitText(false);
					}
				} else {
					processRightKeyEvent();
				}
				return true;

			case KeyEvent.KEYCODE_DPAD_CENTER:
			case KeyEvent.KEYCODE_ENTER:
				if (!isEnglishPrediction()) {
					int cursor = mComposingText.getCursor(ComposingText.LAYER1);
					if (cursor < 1) {
						return true;
					}
				}
				initCommitInfoForWatchCursor();
				mStatus = commitText(true);
				checkCommitInfo();

				if (isEnglishPrediction()) {
					initializeScreen();
				}

				if (mEnableAutoHideKeyboard) {
					mInputViewManager.closing();
					requestHideSelf(0);
				}
				return true;

			case KeyEvent.KEYCODE_CALL:
				return false;

			default:
				return true;
			}
		} else {
			/* if there is no composing string. */
			if (mCandidatesViewManager.getCurrentView().isShown()) {
				/* displaying relational prediction candidates */
				switch (key) {
				case KeyEvent.KEYCODE_DPAD_LEFT:
					if (isEnableL2Converter()) {
						/* initialize the converter */
						mConverter.init();
					}
					mStatus = STATUS_INPUT_EDIT;
					updateViewStatusForPrediction(true, true);
					return false;

				case KeyEvent.KEYCODE_DPAD_RIGHT:
					if (isEnableL2Converter()) {
						/* initialize the converter */
						mConverter.init();
					}
					mStatus = STATUS_INPUT_EDIT;
					updateViewStatusForPrediction(true, true);
					return false;

				default:
					return processKeyEventNoInputCandidateShown(ev);
				}
			} else {
				switch (key) {
				case KeyEvent.KEYCODE_DPAD_CENTER:
				case KeyEvent.KEYCODE_ENTER:
					if (mEnableAutoHideKeyboard) {
						mInputViewManager.closing();
						requestHideSelf(0);
						return true;
					}
					break;
				case KeyEvent.KEYCODE_BACK:
					/*
					 * If 'BACK' key is pressed when the SW-keyboard is shown
					 * and the candidates view is not shown, dismiss the SW-keyboard.
					 */
					if (isInputViewShown()) {
						mInputViewManager.closing();
						requestHideSelf(0);
						return true;
					}
					break;
				default:
					break;
				}
			}
		}

		return false;
	}

	/**
	 * Handle the space key event from the Hardware keyboard.
	 * 
	 * @param ev  The space key event
	 */
	private void processHardwareKeyboardSpaceKey(KeyEvent ev) {
		/* H/W space key */
		if (ev.isShiftPressed()) {
			/* change Japanese <-> English mode */
			mHardAlt = 0;
			mHardShift = 0;
			updateMetaKeyStateDisplay();
			if (mEngineState.isEnglish()) {
				/* English mode to Japanese mode */
				((DefaultSoftKeyboardJAJP) mInputViewManager).changeKeyMode(DefaultSoftKeyboard.KEYMODE_JA_FULL_NIKO);
				mConverter = mConverterJAJP;
			} else {
				/* Japanese mode to English mode */
				((DefaultSoftKeyboardJAJP) mInputViewManager).changeKeyMode(DefaultSoftKeyboard.KEYMODE_JA_HALF_ALPHABET);
				mConverter = mConverterEN;
			}
			mCandidatesViewManager.clearCandidates();

		} else if(ev.isAltPressed()){
			/* display the symbol list (G1 specific. same as KEYCODE_SYM) */
			if (!mEngineState.isSymbolList()) {
				commitAllText();
			}
			changeEngineMode(ENGINE_MODE_SYMBOL);
			mHardAlt = 0;
			updateMetaKeyStateDisplay();

		} else if (isEnglishPrediction()) {
			/* Auto commit if English mode */
			if (mComposingText.size(0) == 0) {
				commitText(" ");
				mCandidatesViewManager.clearCandidates();
				breakSequence();
			} else {
				initCommitInfoForWatchCursor();
				commitText(true);
				commitSpaceJustOne();
				checkCommitInfo();
			}
			mEnableAutoDeleteSpace = false;

		} else {
			/* start consecutive clause conversion if Japanese mode */
			if (mComposingText.size(0) == 0) {
				commitText(" ");
				mCandidatesViewManager.clearCandidates();
				breakSequence();
			} else {
				startConvert(EngineState.CONVERT_TYPE_RENBUN);
			}
		}
	}

	/**
	 * Handle the character code from the hardware keyboard except the space key.
	 *
	 * @param str  The input character
	 */
	private void processHardwareKeyboardInputChar(StrSegment str) {
		if (isEnableL2Converter()) {
			boolean commit = false;
			if (mPreConverter == null) {
				Matcher m = mEnglishAutoCommitDelimiter.matcher(str.string);
				if (m.matches()) {
					commitText(true);
                    
					commit = true;
				}
				appendStrSegment(str);
			} else {
				appendStrSegment(str);
				mPreConverter.convert(mComposingText);
			}
            
			if (commit) {
				commitText(true);
			} else {
				mStatus = STATUS_INPUT;
				updateViewStatusForPrediction(true, true);
			}
		} else {
			appendStrSegment(str);
			boolean completed = true;
			if (mPreConverter != null) {
				completed = mPreConverter.convert(mComposingText);
			}

			if (completed) {
				commitText(false);
			} else {
				updateViewStatus(ComposingText.LAYER1, false, true);
			}
		}
	}

	/** Thread for updating the candidates view */
	private void updatePrediction() {
		int candidates = 0;
		int cursor = mComposingText.getCursor(ComposingText.LAYER1);
		if (isEnableL2Converter() || mEngineState.isSymbolList()) {
			if (mExactMatchMode) {
				/* exact matching */
				candidates = mConverter.predict(mComposingText, 0, cursor);
			} else {
				/* normal prediction */
				candidates = mConverter.predict(mComposingText, 0, -1);
			}
		}

		/* update the candidates view */
		if (candidates > 0) {
			mHasContinuedPrediction = ((mComposingText.size(ComposingText.LAYER1) == 0)
						   && !mEngineState.isSymbolList());
			mCandidatesViewManager.displayCandidates(mConverter, mEngineState.isDocomoSymbolList());
		} else {
			mCandidatesViewManager.clearCandidates();
		}
	}

	/**
	 * Handle a left key event.
	 */
	private void processLeftKeyEvent() {
		if (mEngineState.isConvertState()) {
			if (mEngineState.isEisuKana()) {
				mExactMatchMode = true;
			}

			if (1 < mComposingText.getCursor(ComposingText.LAYER1)) {
				mComposingText.moveCursor(ComposingText.LAYER1, -1);
			}
		} else if (mExactMatchMode) {
			mComposingText.moveCursor(ComposingText.LAYER1, -1);
		} else {
			if (isEnglishPrediction()) {
				mComposingText.moveCursor(ComposingText.LAYER1, -1);
			} else {
				mExactMatchMode = true;
			}
		}

		mCommitCount = 0; /* retry consecutive clause conversion if necessary. */
		mStatus = STATUS_INPUT_EDIT;
		updateViewStatus(mTargetLayer, true, true);
	}

	/**
	 * Handle a right key event.
	 */
	private void processRightKeyEvent() {
		int layer = mTargetLayer;
		ComposingText composingText = mComposingText;
		if (mExactMatchMode || (mEngineState.isConvertState())) {
			int textSize = composingText.size(ComposingText.LAYER1);
			if (composingText.getCursor(ComposingText.LAYER1) == textSize) {
				mExactMatchMode = false;
				layer = ComposingText.LAYER1; /* convert -> prediction */
				EngineState state = new EngineState();
				state.convertType = EngineState.CONVERT_TYPE_NONE;
				updateEngineState(state);
			} else {
				if (mEngineState.isEisuKana()) {
					mExactMatchMode = true;
				}
				composingText.moveCursor(ComposingText.LAYER1, 1);
			}
		} else {
			if (composingText.getCursor(ComposingText.LAYER1)
			    < composingText.size(ComposingText.LAYER1)) {
				composingText.moveCursor(ComposingText.LAYER1, 1);
			}
		}

		mCommitCount = 0; /* retry consecutive clause conversion if necessary. */
		mStatus = STATUS_INPUT_EDIT;

		updateViewStatus(layer, true, true);
	}

	/**
	 * Handle a key event which is not right or left key when the
	 * composing text is empty and some candidates are shown.
	 *
	 * @param ev        A key event
	 * @return          {@code true} if this consumes the event; {@code false} if not.
	 */
	boolean processKeyEventNoInputCandidateShown(KeyEvent ev) {
		boolean ret = true;

		switch (ev.getKeyCode()) {
		case KeyEvent.KEYCODE_DEL:
			ret = true;
			break;
		case KeyEvent.KEYCODE_ENTER:
		case KeyEvent.KEYCODE_DPAD_UP:
		case KeyEvent.KEYCODE_DPAD_DOWN:
		case KeyEvent.KEYCODE_MENU:
			ret = false;
			break;
            
		case KeyEvent.KEYCODE_CALL:
			return false;
            
		case KeyEvent.KEYCODE_DPAD_CENTER:
			ret = true;
			break;

		case KeyEvent.KEYCODE_BACK:
			if (mCandidatesViewManager.getViewType() == CandidatesViewManager.VIEW_TYPE_FULL) {
				mStatus &= ~STATUS_CANDIDATE_FULL;
				mCandidatesViewManager.setViewType(CandidatesViewManager.VIEW_TYPE_NORMAL);
				return true;
			} else {
				ret = true;
			}
			break;
        
		default:
			return true;
		}

		if (mConverter != null) {
			/* initialize the converter */
			mConverter.init();
		}
		updateViewStatusForPrediction(true, true);
		return ret;
	}

	/**
	 * Update views and the display of the composing text for predict mode.
	 *
	 * @param updateCandidates  {@code true} to update the candidates view
	 * @param updateEmptyText   {@code false} to update the composing text if it is not empty; {@code true} to update always.
	 */
	private void updateViewStatusForPrediction(boolean updateCandidates, boolean updateEmptyText) {
		EngineState state = new EngineState();
		state.convertType = EngineState.CONVERT_TYPE_NONE;
		updateEngineState(state);

		updateViewStatus(ComposingText.LAYER1, updateCandidates, updateEmptyText);
	}

	/**
	 * Update views and the display of the composing text.
	 *
	 * @param layer                      Display layer of the composing text
	 * @param updateCandidates  {@code true} to update the candidates view
	 * @param updateEmptyText   {@code false} to update the composing text if it is not empty; {@code true} to update always.
	 */
	private void updateViewStatus(int layer, boolean updateCandidates, boolean updateEmptyText) {
		mTargetLayer = layer;

		if (updateCandidates) {
			updateCandidateView();
		}
		/* notice to the input view */
		mInputViewManager.onUpdateState(this);

		/* set the text for displaying as the composing text */
		mDisplayText.clear();
		mDisplayText.insert(0, mComposingText.toString(layer));

		/* add decoration to the text */
		int cursor = mComposingText.getCursor(layer);
		if ((mInputConnection != null) && (mDisplayText.length() != 0 || updateEmptyText)) {
			if (cursor != 0) {
				int highlightEnd = 0;

				if ((mExactMatchMode && (!mEngineState.isEisuKana()))
				    || (FIX_CURSOR_TEXT_END && isEnglishPrediction()
					&& (cursor < mComposingText.size(ComposingText.LAYER1)))){

					mDisplayText.setSpan(SPAN_EXACT_BGCOLOR_HL, 0, cursor,
							     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
					highlightEnd = cursor;

				} else if (FIX_CURSOR_TEXT_END && mEngineState.isEisuKana()) {
					mDisplayText.setSpan(SPAN_EISUKANA_BGCOLOR_HL, 0, cursor,
							     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
					highlightEnd = cursor;

				} else if (layer == ComposingText.LAYER2) {
					highlightEnd = mComposingText.toString(layer, 0, 0).length();

					/* highlights the first segment */
					mDisplayText.setSpan(SPAN_CONVERT_BGCOLOR_HL, 0,
							     highlightEnd,
							     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
				}

				if (FIX_CURSOR_TEXT_END && (highlightEnd != 0)) {
					/* highlights remaining text */
					mDisplayText.setSpan(SPAN_REMAIN_BGCOLOR_HL, highlightEnd,
							     mComposingText.toString(layer).length(),
							     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

					/* text color in the highlight */
					mDisplayText.setSpan(SPAN_TEXTCOLOR, 0,
							     mComposingText.toString(layer).length(),
							     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 
				}
			}

			mDisplayText.setSpan(SPAN_UNDERLINE, 0, mDisplayText.length(),
					     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

			int displayCursor = mComposingText.toString(layer, 0, cursor - 1).length();
			if (FIX_CURSOR_TEXT_END) {
				displayCursor = (cursor == 0) ?  0 : 1;
			} 
			/* update the composing text on the EditView */
			mInputConnection.setComposingText(mDisplayText, displayCursor);
		}
	}

	/**
	 * Update the candidates view.
	 */
	private void updateCandidateView() {
		switch (mTargetLayer) {
		case ComposingText.LAYER0:
		case ComposingText.LAYER1: /* prediction */
			if (mEnablePrediction || mEngineState.isSymbolList() || mEngineState.isEisuKana()) {
				/* update the candidates view */
				if ((mComposingText.size(ComposingText.LAYER1) != 0)
				    && !mEngineState.isConvertState()) {
                    
					mHandler.removeMessages(MSG_PREDICTION);
					if (mCandidatesViewManager.getCurrentView().isShown()) {
						mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PREDICTION),
									    PREDICTION_DELAY_MS_SHOWING_CANDIDATE);
					} else {
						mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PREDICTION),
									    PREDICTION_DELAY_MS_1ST);
					}
				} else {
					mHandler.removeMessages(MSG_PREDICTION);
					updatePrediction();
				}
			} else {
				mHandler.removeMessages(MSG_PREDICTION);
				mCandidatesViewManager.clearCandidates();
			}
			break;
		case ComposingText.LAYER2: /* convert */
			if (mCommitCount == 0) {
				mHandler.removeMessages(MSG_PREDICTION);
				mConverter.convert(mComposingText);
			}

			int candidates = mConverter.makeCandidateListOf(mCommitCount);

			if (candidates != 0) {
				mComposingText.setCursor(ComposingText.LAYER2, 1);
				mCandidatesViewManager.displayCandidates(mConverter, false);
			} else {
				mComposingText.setCursor(ComposingText.LAYER1,
							 mComposingText.toString(ComposingText.LAYER1).length());
				mCandidatesViewManager.clearCandidates();
			}
			break;
		default:
			break;
		}
	}

	/**
	 * Commit the displaying composing text.
	 *
	 * @param learn  {@code true} to register the committed string to the learning dictionary.
	 * @return          IME's status after commit
	 */
	private int commitText(boolean learn) {
		if (isEnglishPrediction()) {
			mComposingText.setCursor(ComposingText.LAYER1,
						 mComposingText.size(ComposingText.LAYER1));
		}

		int layer = mTargetLayer;
		int cursor = mComposingText.getCursor(layer);
		if (cursor == 0) {
			return mStatus;
		}
		String tmp = mComposingText.toString(layer, 0, cursor - 1);

		if (mConverter != null) {
			if (learn) {
				if (mEngineState.isRenbun()) {
					learnWord(0); /* select the top of the clauses */
				} else {
					if (mComposingText.size(ComposingText.LAYER1) != 0) {
						String stroke = mComposingText.toString(ComposingText.LAYER1, 0, mComposingText.getCursor(layer) - 1);
						WnnWord word = new WnnWord(tmp, stroke);
                                                   
						learnWord(word);
					}
				}
			} else {
				breakSequence();
			}
		}
		return commitTextThroughInputConnection(tmp);
	}

	/**
	 * Commit all uncommitted words.
	 */
	private void commitAllText() {
		initCommitInfoForWatchCursor();
		if (mEngineState.isConvertState()) {
			commitConvertingText();
		} else {
			mComposingText.setCursor(ComposingText.LAYER1,
						 mComposingText.size(ComposingText.LAYER1));
			mStatus = commitText(true);
		}
		checkCommitInfo();
	}

	/**
	 * Commit a word.
	 *
	 * @param word              A word to commit
	 * @return                  IME's status after commit
	 */
	private int commitText(WnnWord word) {
		if (mConverter != null) {
			learnWord(word);
		}
		return commitTextThroughInputConnection(word.candidate);
	}

	/**
	 * Commit a string.
	 *
	 * @param str  A string to commit
	 */
	private void commitText(String str) {
		mInputConnection.commitText(str, (FIX_CURSOR_TEXT_END ? 1 : str.length()));
		mPrevCommitText.append(str);
		mPrevCommitCount++;
		mEnableAutoDeleteSpace = true;
		updateViewStatusForPrediction(false, false);
	}

	/**
	 * Commit a string through {@link InputConnection}.
	 *
	 * @param string  A string to commit
	 * @return                  IME's status after commit
	 */
	private int commitTextThroughInputConnection(String string) {
		int layer = mTargetLayer;

		mInputConnection.commitText(string, (FIX_CURSOR_TEXT_END ? 1 : string.length()));
		mPrevCommitText.append(string);
		mPrevCommitCount++;
        
		int cursor = mComposingText.getCursor(layer);
		if (cursor > 0) {
			mComposingText.deleteStrSegment(layer, 0, mComposingText.getCursor(layer) - 1);
			mComposingText.setCursor(layer, mComposingText.size(layer));
		}
		mExactMatchMode = false;
		mCommitCount++;

		if ((layer == ComposingText.LAYER2) && (mComposingText.size(layer) == 0)) {
			layer = 1; /* for connected prediction */
		}

		boolean commited = autoCommitEnglish();
		mEnableAutoDeleteSpace = true;

		if (layer == ComposingText.LAYER2) {
			EngineState state = new EngineState();
			state.convertType = EngineState.CONVERT_TYPE_RENBUN;
			updateEngineState(state);
			updateViewStatus(layer, !commited, false);
		} else {
			updateViewStatusForPrediction(!commited, false);
		}

		if (mComposingText.size(ComposingText.LAYER0) == 0) {
			return STATUS_INIT;
		} else {
			return STATUS_INPUT_EDIT;
		}
	}

	/**
	 * Returns whether it is English prediction mode or not.
	 *
	 * @return  {@code true} if it is English prediction mode; otherwise, {@code false}.
	 */
	private boolean isEnglishPrediction() {
		return (mEngineState.isEnglish() && isEnableL2Converter());
	}

	/**
	 * Change the conversion engine and the letter converter(Romaji-to-Kana converter).
	 *
	 * @param mode  Engine's mode to be changed
	 * @see com.hiroshica.android.input.nicownn2.NicoWnnEvent.Mode
	 * @see com.hiroshica.android.input.nicownn2.JAJP.DefaultSoftKeyboardJAJP
	 */
	private void changeEngineMode(int mode) {
		EngineState state = new EngineState();

		switch (mode) {
		case ENGINE_MODE_OPT_TYPE_QWERTY:
			state.keyboard = EngineState.KEYBOARD_QWERTY;
			updateEngineState(state);
			clearCommitInfo();
			return;

		case ENGINE_MODE_OPT_TYPE_12KEY:
			state.keyboard = EngineState.KEYBOARD_12KEY;
			updateEngineState(state);
			clearCommitInfo();
			return;

		case ENGINE_MODE_EISU_KANA:
			if (mEngineState.isEisuKana()) {
				state.temporaryMode = EngineState.TEMPORARY_DICTIONARY_MODE_NONE;
				updateEngineState(state);
				updateViewStatusForPrediction(true, true); /* prediction only */
			} else {
				startConvert(EngineState.CONVERT_TYPE_EISU_KANA);
			}
			return;

		case ENGINE_MODE_SYMBOL:
			if (mEnableSymbolList && !mDirectInputMode) {
				state.temporaryMode = EngineState.TEMPORARY_DICTIONARY_MODE_SYMBOL;
				updateEngineState(state);
				updateViewStatusForPrediction(true, true);
			}
			return;

		case ENGINE_MODE_DOCOMOSYMBOL:
			if (mEnableSymbolList && !mDirectInputMode) {
				state.temporaryMode = EngineState.TEMPORARY_DICTIONARY_MODE_DOCOMOSYMBOL;
				updateEngineState(state);
				updateViewStatusForPrediction(true, true);
			}
			return;


		default:
			break;
		}

		state = new EngineState();
		state.temporaryMode = EngineState.TEMPORARY_DICTIONARY_MODE_NONE;
		updateEngineState(state);

		state = new EngineState();
		switch (mode) {
		case NicoWnnEvent.Mode.DIRECT:
			/* Full/Half-width number or Full-width alphabet */
			mConverter = null;
			mPreConverter = null;
			break;

		case NicoWnnEvent.Mode.NO_LV1_CONV:
			/* no Romaji-to-Kana conversion (=English prediction mode) */
			state.dictionarySet = EngineState.DICTIONARYSET_EN;
			updateEngineState(state);
			mConverter = mConverterEN;
			mPreConverter = null;
			break;

		case NicoWnnEvent.Mode.NO_LV2_CONV:
			mConverter = null;
			mPreConverter = mPreConverterHiragana;
			break;

		case ENGINE_MODE_FULL_KATAKANA:
			mConverter = null;
			mPreConverter = mPreConverterFullKatakana;
			break;

		case ENGINE_MODE_HALF_KATAKANA:
			mConverter = null;
			mPreConverter = mPreConverterHalfKatakana;
			break;

		default:
			/* HIRAGANA input mode */
			state.dictionarySet = EngineState.DICTIONARYSET_JP;
			updateEngineState(state);
			mConverter = mConverterJAJP;
			mPreConverter = mPreConverterHiragana;
			break;
		}

		mPreConverterBack = mPreConverter;
		mConverterBack = mConverter;
	}

	/**
	 * Update the conversion engine's state.
	 *
	 * @param state  Engine's state to be updated
	 */
	private void updateEngineState(EngineState state) {
		EngineState myState = mEngineState;

		/* language */
		if ((state.dictionarySet != EngineState.INVALID) 
		    && (myState.dictionarySet != state.dictionarySet)) {

			switch (state.dictionarySet) {
			case EngineState.DICTIONARYSET_EN:
				setDictionary(NicoWnnEngineJAJP.DIC_LANG_EN);
				break;

			case EngineState.DICTIONARYSET_JP:
			default:
				setDictionary(NicoWnnEngineJAJP.DIC_LANG_JP);
				break;
			}
			myState.dictionarySet = state.dictionarySet;
			breakSequence();

			/* update keyboard setting */
			if (state.keyboard == EngineState.INVALID) {
				state.keyboard = myState.keyboard;
			}
		}

		/* type of conversion */
		if ((state.convertType != EngineState.INVALID)
		    && (myState.convertType != state.convertType)) {

			switch (state.convertType) {
			case EngineState.CONVERT_TYPE_NONE:
				setDictionary(mPrevDictionarySet);
				break;

			case EngineState.CONVERT_TYPE_EISU_KANA:
				setDictionary(NicoWnnEngineJAJP.DIC_LANG_JP_EISUKANA);
				break;

			case EngineState.CONVERT_TYPE_RENBUN:
			default:
				setDictionary(NicoWnnEngineJAJP.DIC_LANG_JP);
				break;
			}
			myState.convertType = state.convertType;
		}

		/* temporary dictionary */
		if (state.temporaryMode != EngineState.INVALID) {

			switch (state.temporaryMode) {
			case EngineState.TEMPORARY_DICTIONARY_MODE_NONE:
				if (myState.temporaryMode != EngineState.TEMPORARY_DICTIONARY_MODE_NONE) {
					setDictionary(mPrevDictionarySet);
					mCurrentSymbol = 0;
					mPreConverter = mPreConverterBack;
					mConverter = mConverterBack;
					mDisableAutoCommitEnglishMask &= ~AUTO_COMMIT_ENGLISH_SYMBOL;
				}
				break;

			case EngineState.TEMPORARY_DICTIONARY_MODE_SYMBOL:
				if (++mCurrentSymbol >= SYMBOL_LISTS.length) {
					mCurrentSymbol = 0;
				}
				if (mEnableSymbolListNonHalf) {
					mConverterSymbolEngineBack.setDictionary(SYMBOL_LISTS[mCurrentSymbol]);
				} else {
					mConverterSymbolEngineBack.setDictionary(SymbolList.SYMBOL_ENGLISH);
				}
				mConverter = mConverterSymbolEngineBack;
				mDisableAutoCommitEnglishMask |= AUTO_COMMIT_ENGLISH_SYMBOL;
				breakSequence();
				break;

			case EngineState.TEMPORARY_DICTIONARY_MODE_DOCOMOSYMBOL:
				mConverterSymbolEngineBack.setDictionary(SymbolList.SYMBOL_DOCOMO_EMOJI);
				mConverter = mConverterSymbolEngineBack;
				mDisableAutoCommitEnglishMask |= AUTO_COMMIT_ENGLISH_SYMBOL;
				breakSequence();
				break;

			default:
				break;
			}
			myState.temporaryMode = state.temporaryMode;
		}

		/* preference dictionary */
		if ((state.preferenceDictionary != EngineState.INVALID) 
		    && (myState.preferenceDictionary != state.preferenceDictionary)) {

			myState.preferenceDictionary = state.preferenceDictionary;
			setDictionary(mPrevDictionarySet);
		}

		/* keyboard type */
		if (state.keyboard != EngineState.INVALID) {
			switch (state.keyboard) {
			case EngineState.KEYBOARD_12KEY:
				mConverterJAJP.setKeyboardType(NicoWnnEngineJAJP.KEYBOARD_KEYPAD12);
				mConverterEN.setDictionary(NicoWnnEngineEN.DICT_DEFAULT);
				break;
                
			case EngineState.KEYBOARD_QWERTY:
			default:
				mConverterJAJP.setKeyboardType(NicoWnnEngineJAJP.KEYBOARD_QWERTY);
				if (mEnableSpellCorrection) {
					mConverterEN.setDictionary(NicoWnnEngineEN.DICT_FOR_CORRECT_MISTYPE);
				} else {
					mConverterEN.setDictionary(NicoWnnEngineEN.DICT_DEFAULT);
				}
				break;
			}
			myState.keyboard = state.keyboard;
		}
	}

	/**
	 * Set dictionaries to be used.
	 * 
	 * @param mode  Definition of dictionaries
	 */
	private void setDictionary(int mode) {
		int target = mode;
		switch (target) {

		case NicoWnnEngineJAJP.DIC_LANG_JP:

			switch (mEngineState.preferenceDictionary) {
			case EngineState.PREFERENCE_DICTIONARY_PERSON_NAME:
				target = NicoWnnEngineJAJP.DIC_LANG_JP_PERSON_NAME;
				break;
			case EngineState.PREFERENCE_DICTIONARY_POSTAL_ADDRESS:
				target = NicoWnnEngineJAJP.DIC_LANG_JP_POSTAL_ADDRESS;
				break;
			default:
				break;
			}

			break;

		case NicoWnnEngineJAJP.DIC_LANG_EN:

			switch (mEngineState.preferenceDictionary) {
			case EngineState.PREFERENCE_DICTIONARY_EMAIL_ADDRESS_URI:
				target = NicoWnnEngineJAJP.DIC_LANG_EN_EMAIL_ADDRESS;
				break;
			default:
				break;
			}

			break;

		default:
			break;
		}
 
		switch (mode) {
		case NicoWnnEngineJAJP.DIC_LANG_JP:
		case NicoWnnEngineJAJP.DIC_LANG_EN:
			mPrevDictionarySet = mode;
			break;
		default:
			break;
		}

		mConverterJAJP.setDictionary(target);
	}

	/**
	 * Handle a toggle key input event.
	 *
	 * @param table  Table of toggle characters
	 */
	private void processSoftKeyboardToggleChar(String[] table) {
		if (table == null) {
			return;
		}

		commitConvertingText();

		boolean toggled = false;
		if ((mStatus & ~STATUS_CANDIDATE_FULL) == STATUS_INPUT) {
			int cursor = mComposingText.getCursor(ComposingText.LAYER1);
			if (cursor > 0) {
				String prevChar = mComposingText.getStrSegment(ComposingText.LAYER1,
									       cursor - 1).string;
				String c = searchToggleCharacter(prevChar, table, false);
				if (c != null) {
					mComposingText.delete(ComposingText.LAYER1, false);
					appendStrSegment(new StrSegment(c));
					toggled = true;
				}
			}
		}

		if (!toggled) {
			if (!isEnableL2Converter()) {
				commitText(false);
			}

			String str = table[0];
			/* shift on */
			if (mAutoCaps
			    && isEnglishPrediction()
			    && (getShiftKeyState(getCurrentInputEditorInfo()) == 1)) {

				char top = table[0].charAt(0);
				if (Character.isLowerCase(top)) {
					str = Character.toString(Character.toUpperCase(top));
				}
			} 
			appendStrSegment(new StrSegment(str));
		}

		mStatus = STATUS_INPUT;

		updateViewStatusForPrediction(true, true);
	}

	/**
	 * Handle character input from the software keyboard without listing candidates.
	 *
	 * @param chars  The input character(s)
	 */
	private void processSoftKeyboardCodeWithoutConversion(char[] chars) {
		if (chars == null) {
			return;
		}

		ComposingText text = mComposingText;
		appendStrSegment(new StrSegment(chars));

		if (!isAlphabetLast(text.toString(ComposingText.LAYER1))) {
			/* commit if the input character is not alphabet */
			commitText(false);
		} else {
			boolean completed = mPreConverter.convert(text);
			if (completed) {
				commitText(false);
			} else {
				mStatus = STATUS_INPUT;
				updateViewStatusForPrediction(true, true);
			}
		}
	}

	/**
	 * Handle character input from the software keyboard.
	 *
	 * @param chars   The input character(s)
	 */
	private void processSoftKeyboardCode(char[] chars) {
		if (chars == null) {
			return;
		}

		if ((chars[0] == ' ') || (chars[0] == '\u3000' /* Full-width space */)) {
			if (mComposingText.size(0) == 0) {
				mCandidatesViewManager.clearCandidates();
				commitText(new String(chars));
				breakSequence();
			} else {
				if (isEnglishPrediction()) {
					initCommitInfoForWatchCursor();
					commitText(true);
					commitSpaceJustOne();
					checkCommitInfo();
				} else {
					startConvert(EngineState.CONVERT_TYPE_RENBUN);
				}
			}
			mEnableAutoDeleteSpace = false;
		} else {
			commitConvertingText();

			/* Auto-commit a word if it is English and Qwerty mode */
			boolean commit = false;
			if (isEnglishPrediction()
			    && (mEngineState.keyboard == EngineState.KEYBOARD_QWERTY)) {

				Matcher m = mEnglishAutoCommitDelimiter.matcher(new String(chars));
				if (m.matches()) {
					commit = true;
				}
			}
        
			if (commit) {
				commitText(true);

				appendStrSegment(new StrSegment(chars));
				commitText(true);
			} else {
				appendStrSegment(new StrSegment(chars));
				if (mPreConverter != null) {
					mPreConverter.convert(mComposingText);
					mStatus = STATUS_INPUT;
				}
				updateViewStatusForPrediction(true, true);
			}
		}
	}

	/**
	 * Start consecutive clause conversion or EISU-KANA conversion mode.
	 *
	 * @param convertType               The conversion type({@code EngineState.CONVERT_TYPE_*})
	 */
	private void startConvert(int convertType) {
		if (!isEnableL2Converter()) {
			return;
		}

		if (mEngineState.convertType != convertType) {
			/* adjust the cursor position */
			if (!mExactMatchMode) {
				if (convertType == EngineState.CONVERT_TYPE_RENBUN) {
					/* not specify */
					mComposingText.setCursor(ComposingText.LAYER1, 0);
				} else {
					if (mEngineState.isRenbun()) {
						/* EISU-KANA conversion specifying the position of the segment if previous mode is conversion mode */
						mExactMatchMode = true;
					} else {
						/* specify all range */
						mComposingText.setCursor(ComposingText.LAYER1,
									 mComposingText.size(ComposingText.LAYER1));
					}
				}
			} 

			if (convertType == EngineState.CONVERT_TYPE_RENBUN) {
				/* clears variables for the prediction */
				mExactMatchMode = false;
			}
			/* clears variables for the convert */
			mCommitCount = 0;

			int layer;
			if (convertType == EngineState.CONVERT_TYPE_EISU_KANA) {
				layer = ComposingText.LAYER1;
			} else {
				layer = ComposingText.LAYER2;
			}

			EngineState state = new EngineState();
			state.convertType = convertType;
			updateEngineState(state);

			updateViewStatus(layer, true, true);
		}
	}

	/**
	 * Auto commit a word in English (on half-width alphabet mode).
	 *
	 * @return  {@code true} if auto-committed; otherwise, {@code false}.
	 */
	private boolean autoCommitEnglish() {
		if (isEnglishPrediction() && (mDisableAutoCommitEnglishMask == AUTO_COMMIT_ENGLISH_ON)) {
			CharSequence seq = mInputConnection.getTextBeforeCursor(2, 0);
			Matcher m = mEnglishAutoCommitDelimiter.matcher(seq);
			if (m.matches()) {
				if ((seq.charAt(0) == ' ') && mEnableAutoDeleteSpace) {
					mInputConnection.deleteSurroundingText(2, 0);
					CharSequence str = seq.subSequence(1, 2);
					mInputConnection.commitText(str, 1);
					mPrevCommitText.append(str);
					mPrevCommitCount++;
				}

				mHandler.removeMessages(MSG_PREDICTION);
				mCandidatesViewManager.clearCandidates();
				return true;
			}
		}
		return false;
	}

	/**
	 * Insert a white space if the previous character is not a white space.
	 */
	private void commitSpaceJustOne() {
		CharSequence seq = mInputConnection.getTextBeforeCursor(1, 0);
		if (seq.charAt(0) != ' ') {
			commitText(" ");
		}
	}

	/**
	 * Get the shift key state from the editor.
	 *
	 * @param editor    The editor
	 * @return          State ID of the shift key (0:off, 1:on)
	 */
	protected int getShiftKeyState(EditorInfo editor) {
		return (getCurrentInputConnection().getCursorCapsMode(editor.inputType) == 0) ? 0 : 1;
	}

	/**
	 * Display current meta-key state.
	 */
	private void updateMetaKeyStateDisplay() {
		int mode = 0;
		if(mHardShift == 0 && mHardAlt == 0){
			mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_OFF;
		}else if(mHardShift == 1 && mHardAlt == 0){
			mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_ON_ALT_OFF;
		}else if(mHardShift == 2  && mHardAlt == 0){
			mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_LOCK_ALT_OFF;
		}else if(mHardShift == 0 && mHardAlt == 1){
			mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_ON;
		}else if(mHardShift == 0 && mHardAlt == 2){
			mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_LOCK;
		}else if(mHardShift == 1 && mHardAlt == 1){
			mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_ON_ALT_ON;
		}else if(mHardShift == 1 && mHardAlt == 2){
			mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_ON_ALT_LOCK;
		}else if(mHardShift == 2 && mHardAlt == 1){
			mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_LOCK_ALT_ON;
		}else if(mHardShift == 2 && mHardAlt == 2){
			mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_LOCK_ALT_LOCK;
		}else{
			mode = DefaultSoftKeyboard.HARD_KEYMODE_SHIFT_OFF_ALT_OFF;
		}
		((DefaultSoftKeyboard) mInputViewManager).updateIndicator(mode);
	}

	/**
	 * Memory a selected word. 
	 * 
	 * @param word  A selected word
	 */
	private void learnWord(WnnWord word) {
		if (mEnableLearning && word != null) {
			mConverter.learn(word);
		}
	}

	/**
	 * Memory a clause which is generated by consecutive clause conversion.
	 * 
	 * @param index  Index of a clause
	 */
	private void learnWord(int index) {
		ComposingText composingText = mComposingText;

		if (mEnableLearning && composingText.size(ComposingText.LAYER2) > index) {
			StrSegment seg = composingText.getStrSegment(ComposingText.LAYER2, index);
			if (seg instanceof StrSegmentClause) {
				mConverter.learn(((StrSegmentClause)seg).clause);
			} else {
				String stroke = composingText.toString(ComposingText.LAYER1, seg.from, seg.to);
				mConverter.learn(new WnnWord(seg.string, stroke));
			}
		}
	}

	/**
	 * Fits an editor info.
	 * 
	 * @param preferences  The preference data.
	 * @param info              The editor info.
	 */
	private void fitInputType(SharedPreferences preference, EditorInfo info) {
		if (info.inputType == EditorInfo.TYPE_NULL) {
			mDirectInputMode = true;
			return;
		}

		mEnableLearning   = preference.getBoolean("opt_enable_learning", true);
		mEnablePrediction = preference.getBoolean("opt_prediction", true);
		mEnableSpellCorrection = preference.getBoolean("opt_spell_correction", true);

		mDisableAutoCommitEnglishMask &= ~AUTO_COMMIT_ENGLISH_OFF;
		int preferenceDictionary = EngineState.PREFERENCE_DICTIONARY_NONE;
		mEnableConverter = true;
		mEnableSymbolList = true;
		mEnableSymbolListNonHalf = true;
		mAutoCaps = preference.getBoolean("auto_caps", true);
		mFilter.filter = 0;
		//mEnableAutoInsertSpace = true;
		mEnableAutoHideKeyboard = false;

		mEnableAutoInsertSpace = preference.getBoolean("is_skip_space", true);



		switch (info.inputType & EditorInfo.TYPE_MASK_CLASS) {
		case EditorInfo.TYPE_CLASS_NUMBER:
		case EditorInfo.TYPE_CLASS_DATETIME:
			mEnableConverter = false;
			break;

		case EditorInfo.TYPE_CLASS_PHONE:
			mEnableSymbolList = false;
			mEnableConverter = false;
			break;

		case EditorInfo.TYPE_CLASS_TEXT:

			switch (info.inputType & EditorInfo.TYPE_MASK_VARIATION) {
			case EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME:
				preferenceDictionary = EngineState.PREFERENCE_DICTIONARY_PERSON_NAME;
				break;
                
			case EditorInfo.TYPE_TEXT_VARIATION_PASSWORD:
				mEnableLearning = false;
				mEnableConverter = false;
				mEnableSymbolListNonHalf = false;
				mFilter.filter = CandidateFilter.FILTER_NON_ASCII; 
				mDisableAutoCommitEnglishMask |= AUTO_COMMIT_ENGLISH_OFF;
				mEnableAutoHideKeyboard = true;
				break;

			case EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS:
				//mFilter.filter = CandidateFilter.FILTER_NON_ASCII; 
				//mEnableSymbolListNonHalf = false;
				//mEnableAutoInsertSpace = false;
				//mDisableAutoCommitEnglishMask |= AUTO_COMMIT_ENGLISH_OFF;
				//preferenceDictionary = EngineState.PREFERENCE_DICTIONARY_EMAIL_ADDRESS_URI;
				break;

			case EditorInfo.TYPE_TEXT_VARIATION_URI:
				mEnableAutoInsertSpace = false;
				//mDisableAutoCommitEnglishMask |= AUTO_COMMIT_ENGLISH_OFF;
				//preferenceDictionary = EngineState.PREFERENCE_DICTIONARY_EMAIL_ADDRESS_URI;
				break;

			case EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS:
				preferenceDictionary = EngineState.PREFERENCE_DICTIONARY_POSTAL_ADDRESS;
				break;

			case EditorInfo.TYPE_TEXT_VARIATION_PHONETIC:
				mEnableLearning = false;
				mEnableConverter = false;
				mEnableSymbolList = false;
				break;

			default:
				break;
			}
			break;

		default:
			break;
		}

		if (mFilter.filter == 0) {
			mConverterEN.setFilter(null);
			mConverterJAJP.setFilter(null);
		} else {
			mConverterEN.setFilter(mFilter);
			mConverterJAJP.setFilter(mFilter);
		}

		EngineState state = new EngineState();
		state.preferenceDictionary = preferenceDictionary;
		state.convertType = EngineState.CONVERT_TYPE_NONE;
		state.keyboard = mEngineState.keyboard;
		updateEngineState(state);
		updateMetaKeyStateDisplay();

		checkTutorial(info.privateImeOptions);
	}
    
	/**
	 * Append a {@link StrSegment} to the composing text
	 * <br>
	 * If the length of the composing text exceeds
	 * {@code LIMIT_INPUT_NUMBER}, the appending operation is ignored.
	 *
	 * @param  str  Input segment
	 */
	private void appendStrSegment(StrSegment str) {
		ComposingText composingText = mComposingText;
        
		if (composingText.size(ComposingText.LAYER1) >= LIMIT_INPUT_NUMBER) {
			return; /* do nothing */
		}
		composingText.insertStrSegment(ComposingText.LAYER0, ComposingText.LAYER1, str);
		return;
	}

	/**
	 * Commit the consecutive clause conversion.
	 */
	private void commitConvertingText() {
		if (mEngineState.isConvertState()) {
			int size = mComposingText.size(ComposingText.LAYER2);
			for (int i = 0; i < size; i++) {
				learnWord(i);
			}

			String text = mComposingText.toString(ComposingText.LAYER2);
			mInputConnection.commitText(text, (FIX_CURSOR_TEXT_END ? 1 : text.length()));
			mPrevCommitText.append(text);
			mPrevCommitCount++;
			initializeScreen();
		}
	}
    
	/**
	 * Initialize the screen displayed by IME
	 */
	private void initializeScreen() {
		if (mComposingText.size(ComposingText.LAYER0) != 0) {
			mInputConnection.setComposingText("", 0);
		}
		mComposingText.clear();
		mExactMatchMode = false;       
		mStatus = STATUS_INIT;
		mHandler.removeMessages(MSG_PREDICTION);
		View candidateView = mCandidatesViewManager.getCurrentView();
		if ((candidateView != null) && candidateView.isShown()) {
			mCandidatesViewManager.clearCandidates();
		}
		mInputViewManager.onUpdateState(this);

		EngineState state = new EngineState();
		state.temporaryMode = EngineState.TEMPORARY_DICTIONARY_MODE_NONE;
		updateEngineState(state);
	}
    
	/**
	 * Whether the tail of the string is alphabet or not.
	 *
	 * @param  str      The string
	 * @return          {@code true} if the tail is alphabet; {@code false} if otherwise.
	 */
	private boolean isAlphabetLast(String str) {
		Matcher m = ENGLISH_CHARACTER_LAST.matcher(str);
		return m.matches();
	}

	/** @see com.hiroshica.android.input.nicownn2.NicoWnn#onFinishInput */
	@Override public void onFinishInput() {
		if (mInputConnection != null) {
			initializeScreen();
		}
		super.onFinishInput();
	}

	/**
	 * Check whether or not the converter is active.
	 * 
	 * @return {@code true} if the converter is active.
	 */
	private boolean isEnableL2Converter() {
		if (mConverter == null || !mEnableConverter) {
			return false;
		}

		if (mEngineState.isEnglish() && !mEnablePrediction) {
			return false;
		}

		return true;
	}

	/**
	 * Handling KeyEvent(KEYUP)
	 * <br>
	 * This method is called from {@link #onEvent()}.
	 *
	 * @param ev   An up key event
	 */
	private void onKeyUpEvent(KeyEvent ev) {
		int key = ev.getKeyCode();
		if(!mShiftPressing){
			if(key == KeyEvent.KEYCODE_SHIFT_LEFT || key == KeyEvent.KEYCODE_SHIFT_RIGHT){
				mHardShift = 0;
				mShiftPressing = true;
				updateMetaKeyStateDisplay();
			}
		}
		if(!mAltPressing ){
			if(key == KeyEvent.KEYCODE_ALT_LEFT || key == KeyEvent.KEYCODE_ALT_RIGHT){
				mHardAlt = 0;
				mAltPressing   = true;
				updateMetaKeyStateDisplay();
			}
		}
	}

	/**
	 * Initialize the committed text's information.
	 */
	private void initCommitInfoForWatchCursor() {
		if (!isEnableL2Converter()) {
			return;
		}

		mCommitStartCursor = mComposingStartCursor;
		mPrevCommitText.delete(0, mPrevCommitText.length());
	}
    
	/**
	 * Clear the commit text's info.
	 * @return {@code true}:cleared, {@code false}:has already cleared.
	 */
	private boolean clearCommitInfo() {
		if (mCommitStartCursor < 0) {
			return false;
		}

		mCommitStartCursor = -1;
		return true;
	}

	/**
	 * Verify the commit text.
	 */
	private void checkCommitInfo() {
		if (mCommitStartCursor < 0) {
			return;
		}

		int composingLength = mComposingText.toString(mTargetLayer).length();
		CharSequence seq = mInputConnection.getTextBeforeCursor(mPrevCommitText.length() + composingLength, 0);
		seq = seq.subSequence(0, seq.length() - composingLength);
		if (!seq.equals(mPrevCommitText.toString())) {
			mPrevCommitCount = 0;
			clearCommitInfo();
		}
	}

	/**
	 * Check and start the tutorial if it is the tutorial mode.
	 * 
	 * @param privateImeOptions IME's options
	 */
	private void checkTutorial(String privateImeOptions) {
		if (privateImeOptions == null) return;
		if (privateImeOptions.equals("com.android.setupwizard:ShowTutorial")) {
			if ((mTutorial == null) && mEnableTutorial) startTutorial();
		} else if (privateImeOptions.equals("com.android.setupwizard:HideTutorial")) {
			if (mTutorial != null) {
				if (mTutorial.close()) {
					mTutorial = null;
				}
			}
		}
	}

	/**
	 * Start the tutorial
	 */
	private void startTutorial() {
		DefaultSoftKeyboardJAJP manager = (DefaultSoftKeyboardJAJP) mInputViewManager;
		manager.setDefaultKeyboard();
		if (mEngineState.keyboard == EngineState.KEYBOARD_QWERTY) {
			manager.changeKeyboardType(DefaultSoftKeyboard.KEYBOARD_12KEY);
		}

		DefaultSoftKeyboardJAJP inputManager = ((DefaultSoftKeyboardJAJP) mInputViewManager);
		View v = inputManager.getKeyboardView();
		v.setOnTouchListener(new View.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				return true;
			}});
		mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TUTORIAL), 500);
	}

	/**
	 * Close the tutorial
	 */
	public void tutorialDone() {
		mTutorial = null;
	}

	/** @see NicoWnn#close */
	@Override protected void close() {
		mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLOSE), 0);
	}

	/**
	 * Break the sequence of words.
	 */
	private void breakSequence() {
		mEnableAutoDeleteSpace = false;
		mConverterJAJP.breakSequence();
		mConverterEN.breakSequence();
	}
}
