/*
 * Copyright (C) 2008-2012  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 jp.co.omronsoft.openwnn;

import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.LinkedList;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.graphics.Rect;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.text.TextPaint;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ImageSpan;
import android.text.style.DynamicDrawableSpan;
import android.util.Log;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.EditText;
import android.widget.AbsoluteLayout;
import android.widget.ImageView;

/**
 * The default candidates view manager class using {@link EditText}.
 *
 * @author Copyright (C) 2009-2011 OMRON SOFTWARE CO., LTD.  All Rights Reserved.
 */
public class TextCandidatesViewManager extends CandidatesViewManager implements GestureDetector.OnGestureListener {
    /** Number of lines to display (Portrait) */
    public static final int LINE_NUM_PORTRAIT       = 2;
    /** Number of lines to display (Landscape) */
    public static final int LINE_NUM_LANDSCAPE      = 1;

    /** Maximum lines */
    private static final int DISPLAY_LINE_MAX_COUNT = 1000;
    /** Maximum number of displaying candidates par one line (full view mode) */
    private static final int FULL_VIEW_DIV = 4;
    /** Maximum number of displaying candidates par one line (full view mode)(symbol)(portrait) */
    private static final int FULL_VIEW_SYMBOL_DIV_PORT = 6;
    /** Maximum number of displaying candidates par one line (full view mode)(symbol)(landscape) */
    private static final int FULL_VIEW_SYMBOL_DIV_LAND = 10;
    /** Delay of set candidate */
    private static final int SET_CANDIDATE_DELAY = 50;
    /** First line count */
    private static final int SET_CANDIDATE_FIRST_LINE_COUNT = 7;
    /** Delay line count */
    private static final int SET_CANDIDATE_DELAY_LINE_COUNT = 1;

    /** Focus is none now */
    private static final int FOCUS_NONE = -1;
    /** Handler for focus Candidate */
    private static final int MSG_MOVE_FOCUS = 0;
    /** Handler for set  Candidate */
    private static final int MSG_SET_CANDIDATES = 1;
    /** Handler for select Candidate */
    private static final int MSG_SELECT_CANDIDATES = 2;

    /** NUmber of Candidate display lines */
    private static final int SETTING_NUMBER_OF_LINEMAX = 5;

    /** Body view of the candidates list */
    private ViewGroup  mViewBody = null;

    /** The view of the Symbol Tab */
    private TextView mViewTabSymbol;
    /** The view of the Emoticon Tab */
    private TextView mViewTabEmoticon;
    /** Scroller of {@code mViewBodyText} */
    private ScrollView mViewBodyScroll;
    /** Base of {@code mViewCandidateList1st}, {@code mViewCandidateList2nd} */
    private ViewGroup mViewCandidateBase;
    /** Button displayed bottom of the view when there are more candidates. */
    private ImageView mReadMoreButton;
    /** Layout for the candidates list on normal view */
    private LinearLayout mViewCandidateList1st;
    /** Layout for the candidates list on full view */
    private AbsoluteLayout mViewCandidateList2nd;
    /** View for symbol tab */
    private LinearLayout mViewCandidateListTab;
    /** {@link OpenWnn} instance using this manager */
    private OpenWnn mWnn;
    /** View type (VIEW_TYPE_NORMAL or VIEW_TYPE_FULL or VIEW_TYPE_CLOSE) */
    private int mViewType;
    /** Portrait display({@code true}) or landscape({@code false}) */
    private boolean mPortrait;

    /** Width of the view */
    private int mViewWidth;
    /** Minimum width of a candidate (density support) */
    private int mCandidateMinimumWidth;
    /** Maximum width of a candidate (density support) */
    private int mCandidateMinimumHeight;
    /** Minimum height of the category candidate view */
    private int mCandidateCategoryMinimumHeight;
    /** Left align threshold of the candidate view */
    private int mCandidateLeftAlignThreshold;
    /** Height of keyboard */
    private int mKeyboardHeight;
    /** Height of symbol keyboard */
    private int mSymbolKeyboardHeight;
    /** Height of symbol keyboard tab */
    private int mSymbolKeyboardTabHeight;
    /** Whether being able to use Emoticon */
    private boolean mEnableEmoticon = false;

    /** Whether hide the view if there is no candidate */
    private boolean mAutoHideMode = true;
    /** The converter to get candidates from and notice the selected candidate to. */
    private WnnEngine mConverter;
    /** Limitation of displaying candidates */
    private int mDisplayLimit;

    /** Vibrator for touch vibration */
    private Vibrator mVibrator = null;
    /** AudioManager for click sound */
    private AudioManager mSound = null;

    /** Number of candidates displaying for 1st */
    private int mWordCount1st;
    /** Number of candidates displaying for 2nd */
    private int mWordCount2nd;
    /** List of candidates for 1st */
    private ArrayList<WnnWord> mWnnWordArray1st = new ArrayList<WnnWord>();
    /** List of candidates for 2nd */
    private ArrayList<WnnWord> mWnnWordArray2nd = new ArrayList<WnnWord>();
    /** List of select candidates */
    private LinkedList<WnnWord> mWnnWordSelectedList = new LinkedList<WnnWord>();

    /** Gesture detector */
    private GestureDetector mGestureDetector;
    /** Character width of the candidate area */
    private int mLineLength = 0;
    /** Number of lines displayed */
    private int mLineCount = 1;

    /** {@code true} if the full screen mode is selected */
    private boolean mIsFullView = false;

    /** The event object for "touch" */
    private MotionEvent mMotionEvent = null;

    /** The offset when the candidates is flowed out the candidate window */
    private int mDisplayEndOffset = 0;
    /** {@code true} if there are more candidates to display. */
    private boolean mCanReadMore = false;
    /** Color of the candidates */
    private int mTextColor = 0;
    /** Template object for each candidate and normal/full view change button */
    private TextView mViewCandidateTemplate;
    /** Number of candidates in full view */
    private int mFullViewWordCount;
    /** Number of candidates in the current line (in full view) */
    private int mFullViewOccupyCount;
    /** View of the previous candidate (in full view) */
    private TextView mFullViewPrevView;
    /** Id of the top line view (in full view) */
    private int mFullViewPrevLineTopId;
    /** Layout of the previous candidate (in full view) */
    private ViewGroup.LayoutParams mFullViewPrevParams;
    /** Whether all candidates are displayed */
    private boolean mCreateCandidateDone;
    /** Number of lines in normal view */
    private int mNormalViewWordCountOfLine;

    /** List of textView for CandiData List 1st for Symbol mode */
    private ArrayList<TextView> mTextViewArray1st = new ArrayList<TextView>();
    /** List of textView for CandiData List 2st for Symbol mode */
    private ArrayList<TextView> mTextViewArray2nd = new ArrayList<TextView>();
    /** Now focus textView index */
    private int mCurrentFocusIndex = FOCUS_NONE;
    /** Focused View */
    private View mFocusedView = null;
    /** Focused View Background */
    private Drawable mFocusedViewBackground = null;
    /** Axis to find next TextView for Up/Down */
    private int mFocusAxisX = 0;
    /** Now focused TextView in mTextViewArray1st */
    private boolean mHasFocusedArray1st = true;

    /** Portrait Number of Lines from Preference */
    private int mPortraitNumberOfLine = LINE_NUM_PORTRAIT;
    /** Landscape Number of Lines from Preference */
    private int mLandscapeNumberOfLine = LINE_NUM_LANDSCAPE;

    /** Coordinates of line */
    private int mLineY = 0;

    /** {@code true} if the candidate is selected */
    private boolean mIsSymbolSelected = false;

    /** Whether candidates is symbol */
    private boolean mIsSymbolMode = false;

    /** Symbol mode */
    private int mSymbolMode = OpenWnnJAJP.ENGINE_MODE_SYMBOL;

    /** Text size of candidates */
    private float mCandNormalTextSize;

    /** Text size of category */
    private float mCandCategoryTextSize;

    /** HardKeyboard hidden({@code true}) or disp({@code false}) */
    private boolean mHardKeyboardHidden = true;

    /** Minimum height of the candidate 1line view */
    private int mCandidateOneLineMinimumHeight;

    /** Whether candidates long click enable */
    private boolean mEnableCandidateLongClick = true;

    /** Keyboard vertical gap value */
    private static final float KEYBOARD_VERTICAL_GAP = 0.009f;

    /** Keyboard vertical gap count */
    private static final int KEYBOARD_VERTICAL_GAP_COUNT = 3;

    /** {@code Handler} Handler for focus Candidate wait delay */
    private Handler mHandler = new Handler() {
            @Override public void handleMessage(Message msg) {
                switch (msg.what) {
                case MSG_MOVE_FOCUS:
                    moveFocus(msg.arg1, msg.arg2 == 1);
                    break;

                case MSG_SET_CANDIDATES:
                    if (mViewType == CandidatesViewManager.VIEW_TYPE_FULL && mIsSymbolMode) {
                        displayCandidates(mConverter, false, SET_CANDIDATE_DELAY_LINE_COUNT);
                    }
                    break;

                case MSG_SELECT_CANDIDATES:
                    WnnWord word = null;
                    while ((word = mWnnWordSelectedList.poll()) != null) {
                        selectCandidate(word);
                    }
                    break;

                default:
                    break;
                }
            }
        };

    /** Event listener for touching a candidate for 1st */
    private OnClickListener mCandidateOnClick1st = new OnClickListener() {
        public void onClick(View v) {
            onClickCandidate(v, mWnnWordArray1st);
        }
    };

    /** Event listener for touching a candidate for 2nd */
    private OnClickListener mCandidateOnClick2nd = new OnClickListener() {
        public void onClick(View v) {
            onClickCandidate(v, mWnnWordArray2nd);
        }
    };

    /** Event listener for long-clicking a candidate for 1st */
    private OnLongClickListener mCandidateOnLongClick1st = new OnLongClickListener() {
        public boolean onLongClick(View v) {
            return onLongClickCandidate(v, mWnnWordArray1st);
        }
    };

    /** Event listener for long-clicking a candidate for for 2nd */
    private OnLongClickListener mCandidateOnLongClick2nd = new OnLongClickListener() {
        public boolean onLongClick(View v) {
            return onLongClickCandidate(v, mWnnWordArray2nd);
        }
    };

    /** Event listener for click a symbol tab */
    private OnClickListener mTabOnClick = new OnClickListener() {
        public void onClick(View v) {
            if (!v.isShown()) {
                return;
            }
            playSoundAndVibration();

            if (v instanceof TextView) {
                TextView text = (TextView)v;
                switch (text.getId()) {
                case R.id.candview_symbol:
                    if (mSymbolMode != OpenWnnJAJP.ENGINE_MODE_SYMBOL) {
                        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.CHANGE_MODE,
                                                      OpenWnnJAJP.ENGINE_MODE_SYMBOL));
                    }
                    break;

                case R.id.candview_emoticon:
                    if (mSymbolMode != OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI) {
                        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.CHANGE_MODE,
                                OpenWnnJAJP.ENGINE_MODE_SYMBOL));
                    }
                    break;

                default:
                    break;
                }
            }
        }
    };

    /**
     * Constructor
     */
    public TextCandidatesViewManager() {
        this(-1);
    }

    /**
     * Constructor
     *
     * @param displayLimit      The limit of display
     */
    public TextCandidatesViewManager(int displayLimit) {
        mDisplayLimit = displayLimit;
    }

    /**
     * Handle a click event on the candidate.
     * @param v  View
     * @param list  List of candidates
     */
    private void onClickCandidate(View v, ArrayList<WnnWord> list) {
        if (!v.isShown()) {
            return;
        }
        playSoundAndVibration();

        if (v instanceof TextView) {
            TextView text = (TextView)v;
            int wordcount = text.getId();
            WnnWord word = list.get(wordcount);
            
            if (mHandler.hasMessages(MSG_SET_CANDIDATES)) {
                mWnnWordSelectedList.add(word);
                return;
            }
            clearFocusCandidate();
            selectCandidate(word);
        }
    }

    /**
     * Handle a long click event on the candidate.
     * @param v  View
     * @param list  List of candidates
     */
    public boolean onLongClickCandidate(View v, ArrayList<WnnWord> list) {
        if (mViewLongPressDialog == null) {
            return false;
        }

        if (mIsSymbolMode) {
            return false;
        }

        if (!mEnableCandidateLongClick) {
            return false;
        }

        if (!v.isShown()) {
            return true;
        }

        Drawable d = v.getBackground();
        if (d != null) {
            if(d.getState().length == 0){
                return true;
            }
        }

        int wordcount = ((TextView)v).getId();
        mWord = list.get(wordcount);
        clearFocusCandidate();
        displayDialog(v, mWord);

        return true;
    }

    /**
     * Set auto-hide mode.
     * @param hide      {@code true} if the view will hidden when no candidate exists;
     *                  {@code false} if the view is always shown.
     */
    public void setAutoHide(boolean hide) {
        mAutoHideMode = hide;
    }

    /** @see CandidatesViewManager#initView */
    public View initView(OpenWnn parent, int width, int height) {
        mWnn = parent;
        mViewWidth = width;
        Resources r = mWnn.getResources();
        mCandidateMinimumWidth = r.getDimensionPixelSize(R.dimen.cand_minimum_width);
        mCandidateMinimumHeight = r.getDimensionPixelSize(R.dimen.cand_minimum_height);
        if (OpenWnn.isXLarge()) {
            mCandidateOneLineMinimumHeight = r.getDimensionPixelSize(R.dimen.candidate_layout_height);
        }
        mCandidateCategoryMinimumHeight = r.getDimensionPixelSize(R.dimen.cand_category_minimum_height);
        mCandidateLeftAlignThreshold = r.getDimensionPixelSize(R.dimen.cand_left_align_threshold);
        mKeyboardHeight = r.getDimensionPixelSize(R.dimen.keyboard_height);
        if (OpenWnn.isXLarge()) {
            mKeyboardHeight += Math.round(height * KEYBOARD_VERTICAL_GAP)
                                * KEYBOARD_VERTICAL_GAP_COUNT;
        }
        mSymbolKeyboardHeight = r.getDimensionPixelSize(R.dimen.symbol_keyboard_height);
        Drawable d = r.getDrawable(R.drawable.tab_no_select);
        mSymbolKeyboardTabHeight = d.getMinimumHeight();

        mPortrait =
            (r.getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE);

        mCandNormalTextSize = r.getDimensionPixelSize(R.dimen.cand_normal_text_size);
        mCandCategoryTextSize = r.getDimensionPixelSize(R.dimen.cand_category_text_size);

        LayoutInflater inflater = parent.getLayoutInflater();
        mViewBody = (ViewGroup)inflater.inflate(R.layout.candidates, null);

        mViewTabSymbol = (TextView)mViewBody.findViewById(R.id.candview_symbol);
        mViewTabEmoticon = (TextView)mViewBody.findViewById(R.id.candview_emoticon);

        mViewBodyScroll = (ScrollView)mViewBody.findViewById(R.id.candview_scroll);

        mViewCandidateBase = (ViewGroup)mViewBody.findViewById(R.id.candview_base);

        setNumeberOfDisplayLines();
        createNormalCandidateView();
        mViewCandidateList2nd = (AbsoluteLayout)mViewBody.findViewById(R.id.candidates_2nd_view);

        mTextColor = r.getColor(R.color.candidate_text);

        mReadMoreButton = (ImageView)mViewBody.findViewById(R.id.read_more_button);
        mReadMoreButton.setOnTouchListener(new View.OnTouchListener() {
                public boolean onTouch(View v, MotionEvent event) {
                    int resid = 0;
                    switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        if (mIsFullView) {
                            resid = R.drawable.cand_up_press;
                        } else {
                            resid = R.drawable.cand_down_press;
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        if (mIsFullView) {
                            resid = R.drawable.cand_up;
                        } else {
                            resid = R.drawable.cand_down;
                        }
                        break;
                    default:
                        break;
                    }

                    if (resid != 0) {
                        mReadMoreButton.setImageResource(resid);
                    }
                    return false;
                }
            });
        mReadMoreButton.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    if (!v.isShown()) {
                        return;
                    }
                    playSoundAndVibration();

                    if (mIsFullView) {
                        mIsFullView = false;
                        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
                    } else {
                        mIsFullView = true;
                        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL));
                    }
                }
            });

        setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE);

        mGestureDetector = new GestureDetector(this);

        mViewLongPressDialog = (View)inflater.inflate(R.layout.candidate_longpress_dialog, null);

        /* select button */
        Button longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_select);
        longPressDialogButton.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    playSoundAndVibration();
                    clearFocusCandidate();
                    selectCandidate(mWord);
                    closeDialog();
                }
            });

        /* cancel button */
        longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_cancel);
        longPressDialogButton.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    playSoundAndVibration();
                    mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
                    mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.UPDATE_CANDIDATE));
                    closeDialog();
                }
            });

        return mViewBody;
    }

    /**
     * Create the normal candidate view
     */
    private void createNormalCandidateView() {
        mViewCandidateList1st = (LinearLayout)mViewBody.findViewById(R.id.candidates_1st_view);
        mViewCandidateList1st.setOnClickListener(mCandidateOnClick1st);

        mViewCandidateListTab = (LinearLayout)mViewBody.findViewById(R.id.candview_tab);
        TextView tSymbol = mViewTabSymbol;
        tSymbol.setOnClickListener(mTabOnClick);
        TextView tEmoticon = mViewTabEmoticon;
        tEmoticon.setOnClickListener(mTabOnClick);

        int line = SETTING_NUMBER_OF_LINEMAX;
        int width = mViewWidth;
        for (int i = 0; i < line; i++) {
            LinearLayout lineView = new LinearLayout(mViewBodyScroll.getContext());
            lineView.setOrientation(LinearLayout.HORIZONTAL);
            LinearLayout.LayoutParams layoutParams =
                new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                                              ViewGroup.LayoutParams.WRAP_CONTENT);
            lineView.setLayoutParams(layoutParams);
            for (int j = 0; j < (width / getCandidateMinimumWidth()); j++) {
                TextView tv = createCandidateView();
                lineView.addView(tv);
            }

            if (i == 0) {
                TextView tv = createCandidateView();
                layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                                                             ViewGroup.LayoutParams.WRAP_CONTENT);
                layoutParams.weight = 0;
                layoutParams.gravity = Gravity.RIGHT;
                tv.setLayoutParams(layoutParams);

                lineView.addView(tv);
                mViewCandidateTemplate = tv;
            }
            mViewCandidateList1st.addView(lineView);
        }
    }

    /** @see CandidatesViewManager#getCurrentView */
    public View getCurrentView() {
        return mViewBody;
    }

    /** @see CandidatesViewManager#setViewType */
    public void setViewType(int type) {
        boolean readMore = setViewLayout(type);

        if (readMore) {
            displayCandidates(this.mConverter, false, -1);
        } else {
            if (type == CandidatesViewManager.VIEW_TYPE_NORMAL) {
                mIsFullView = false;
                if (mDisplayEndOffset > 0) {
                    int maxLine = getMaxLine();
                    displayCandidates(this.mConverter, false, maxLine);
                } else {
                    setReadMore();
                }
            } else {
                if (mViewBody.isShown()) {
                    mWnn.setCandidatesViewShown(false);
                }
            }
        }
    }

    /**
     * Set the view layout
     *
     * @param type      View type
     * @return          {@code true} if display is updated; {@code false} if otherwise
     */
    private boolean setViewLayout(int type) {
        
        ViewGroup.LayoutParams params;
        int line = (mPortrait) ? mPortraitNumberOfLine : mLandscapeNumberOfLine;

        if ((mViewType == CandidatesViewManager.VIEW_TYPE_FULL)
                && (type == CandidatesViewManager.VIEW_TYPE_NORMAL)) {
            clearFocusCandidate();
        }

        mViewType = type;

        switch (type) {
        case CandidatesViewManager.VIEW_TYPE_CLOSE:
            params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                                                   getCandidateMinimumHeight() * line);
            mViewBodyScroll.setLayoutParams(params);
            mViewCandidateListTab.setVisibility(View.GONE);
            mViewCandidateBase.setMinimumHeight(-1);
            mHandler.removeMessages(MSG_SET_CANDIDATES);
            return false;

        case CandidatesViewManager.VIEW_TYPE_NORMAL:
            params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                                                   getCandidateMinimumHeight() * line);
            mViewBodyScroll.setLayoutParams(params);
            mViewBodyScroll.scrollTo(0, 0);
            mViewCandidateListTab.setVisibility(View.GONE);
            mViewCandidateList1st.setVisibility(View.VISIBLE);
            mViewCandidateList2nd.setVisibility(View.GONE);
            mViewCandidateBase.setMinimumHeight(-1);
            return false;

        case CandidatesViewManager.VIEW_TYPE_FULL:
        default:
            params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                                                   getCandidateViewHeight());
            mViewBodyScroll.setLayoutParams(params);
            if (mIsSymbolMode) {
                updateSymbolType();
                mViewCandidateListTab.setVisibility(View.VISIBLE);
            } else {
                mViewCandidateListTab.setVisibility(View.GONE);
            }
            mViewCandidateList2nd.setVisibility(View.VISIBLE);
            mViewCandidateBase.setMinimumHeight(-1);
            return true;
        }
    }

    /** @see CandidatesViewManager#getViewType */
    public int getViewType() {
        return mViewType;
    }

    /** @see CandidatesViewManager#displayCandidates */
    public void displayCandidates(WnnEngine converter) {

        mHandler.removeMessages(MSG_SET_CANDIDATES);

        if (mIsSymbolSelected) {
            mIsSymbolSelected = false;
            if (mSymbolMode == OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI) {
                return;
            }

            int prevLineCount = mLineCount;
            int prevWordCount1st = mWordCount1st;
            clearNormalViewCandidate();
            mWordCount1st = 0;
            mLineCount = 1;
            mLineLength = 0;
            mNormalViewWordCountOfLine = 0;
            mWnnWordArray1st.clear();
            mTextViewArray1st.clear();
            if (((prevWordCount1st == 0) && (mWordCount1st == 1)) ||
                (prevLineCount < mLineCount)) {
                mViewBodyScroll.scrollTo(0, mViewBodyScroll.getScrollY() + getCandidateMinimumHeight());
            }
            if (isFocusCandidate() && mHasFocusedArray1st) {
                mCurrentFocusIndex = 0;
                Message m = mHandler.obtainMessage(MSG_MOVE_FOCUS, 0, 0);
                mHandler.sendMessage(m);
            }
            return;
        }

        mCanReadMore = false;
        mDisplayEndOffset = 0;
        mIsFullView = false;
        mFullViewWordCount = 0;
        mFullViewOccupyCount = 0;
        mFullViewPrevLineTopId = 0;
        mFullViewPrevView = null;
        mCreateCandidateDone = false;
        mNormalViewWordCountOfLine = 0;

        clearCandidates();
        mConverter = converter;
        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));

        mViewCandidateTemplate.setVisibility(View.VISIBLE);
        mViewCandidateTemplate.setBackgroundResource(R.drawable.cand_back);

        displayCandidates(converter, true, getMaxLine());

        if (mIsSymbolMode) {
            mIsFullView = true;
            mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL));
        }
    }

    /** @see CandidatesViewManager#getMaxLine */
    private int getMaxLine() {
        int maxLine = (mPortrait) ? mPortraitNumberOfLine : mLandscapeNumberOfLine;
        return maxLine;
    }

    /**
     * Get categories text.
     * @param String Source string replacement
     * @return String Categories text
     */
    private String getCategoriesText(String categoriesString) {
        String ret = null;

        Resources r = mWnn.getResources();
        if(categoriesString.equals(r.getString(R.string.half_symbol_categories_txt))) {
            ret = r.getString(R.string.half_symbol_txt);
        } else if (categoriesString.equals(r.getString(R.string.full_symbol_categories_txt))) {
            ret = r.getString(R.string.full_symbol_txt);
        } else {
            ret = new String("");
        }

        return ret;
    }

    /**
     * Display the candidates.
     * 
     * @param converter  {@link WnnEngine} which holds candidates.
     * @param dispFirst  Whether it is the first time displaying the candidates
     * @param maxLine    The maximum number of displaying lines
     */
    private void displayCandidates(WnnEngine converter, boolean dispFirst, int maxLine) {
        if (converter == null) {
            return;
        }

        /* Concatenate the candidates already got and the last one in dispFirst mode */
        int displayLimit = mDisplayLimit;

        boolean isDelay = false;
        boolean isBreak = false;

        if (converter instanceof SymbolList) {
            if (!dispFirst) {
                if (maxLine == -1) {
                    isDelay = true;
                    maxLine = mLineCount + SET_CANDIDATE_FIRST_LINE_COUNT;

                    mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, SET_CANDIDATE_DELAY);

                } else if (maxLine == SET_CANDIDATE_DELAY_LINE_COUNT) {
                    isDelay = true;
                    maxLine = mLineCount + SET_CANDIDATE_DELAY_LINE_COUNT;

                    mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, SET_CANDIDATE_DELAY);
                }
            }
            if (mSymbolMode != OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI) {
                displayLimit = -1;
            }
        }

        /* Get candidates */
        WnnWord result = null;
        String prevCandidate = null;
        while ((displayLimit == -1 || getWordCount() < displayLimit)) {
            for (int i = 0; i < DISPLAY_LINE_MAX_COUNT; i++) {
                result = converter.getNextCandidate();
                if (result == null) {
                    break;
                }

                if (converter instanceof SymbolList) {
                    break;
                }

                if ((prevCandidate == null) || !prevCandidate.equals(result.candidate)) {
                    break;
                }
            }

            if (result == null) {
                break;
            } else {
                prevCandidate = result.candidate;
            }

            if (converter instanceof SymbolList) {
                if (isCategory(result)) {
                    if (getWordCount() != 0) {
                        createNextLine(false);
                    }
                    result.candidate = getCategoriesText(result.candidate);
                    setCandidate(true, result);
                    createNextLine(true);
                    continue;
                }
            }

            setCandidate(false, result);

            if ((dispFirst || isDelay) && (maxLine < mLineCount)) {
                mCanReadMore = true;
                isBreak = true;
                break;
            }
        }

        if (!isBreak && !mCreateCandidateDone) {
            /* align left if necessary */
            createNextLine(false);
            mCreateCandidateDone = true;
            mHandler.removeMessages(MSG_SET_CANDIDATES);
            mHandler.sendMessage(mHandler.obtainMessage(MSG_SELECT_CANDIDATES));
        }

        if (getWordCount() < 1) { /* no candidates */
            if (mAutoHideMode) {
                mWnn.setCandidatesViewShown(false);
                return;
            } else {
                mCanReadMore = false;
                mIsFullView = false;
                setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL);
            }
        }

        setReadMore();

        if (!(mViewBody.isShown())) {
            mWnn.setCandidatesViewShown(true);
        }
        return;
    }

    /**
     * Add a candidate into the list.
     * @param isCategory  {@code true}:caption of category, {@code false}:normal word
     * @param word        A candidate word
     */
    private void setCandidate(boolean isCategory, WnnWord word) {
        int textLength = measureText(word.candidate, 0, word.candidate.length());
        TextView template = mViewCandidateTemplate;
        textLength += template.getPaddingLeft() + template.getPaddingRight();
        int maxWidth = mViewWidth;
        boolean isEmojiSymbol = false;
        if (mIsSymbolMode && (word.candidate.length() < 3)) {
            isEmojiSymbol = true;
        }
        TextView textView;

        boolean is2nd = isFirstListOver(mIsFullView, mLineCount, word);
        if (is2nd) {
            /* Full view */
            int viewDivison = getCandidateViewDivison();
            int indentWidth = mViewWidth / viewDivison;
            int occupyCount = Math.min((textLength + indentWidth + getCandidateSpaceWidth(isEmojiSymbol)) / indentWidth, viewDivison);
            if (isCategory) {
                occupyCount = viewDivison;
            }

            if (viewDivison < (mFullViewOccupyCount + occupyCount)) {
                if (viewDivison != mFullViewOccupyCount) {
                    mFullViewPrevParams.width += (viewDivison - mFullViewOccupyCount) * indentWidth;
                    if (mFullViewPrevView != null) {
                        mViewCandidateList2nd.updateViewLayout(mFullViewPrevView, mFullViewPrevParams);
                    }
                }
                mFullViewOccupyCount = 0;
                if (mFullViewPrevView != null) {
                    mFullViewPrevLineTopId = mFullViewPrevView.getId();
                }
                mLineCount++;
                if (isCategory) {
                    mLineY += mCandidateCategoryMinimumHeight;
                } else {
                    mLineY += getCandidateMinimumHeight();
                }
            }
            if (mFullViewWordCount == 0) {
                mLineY = 0;
            }

            ViewGroup layout = mViewCandidateList2nd;

            int width = indentWidth * occupyCount;
            int height = getCandidateMinimumHeight();
            if (isCategory) {
                height = mCandidateCategoryMinimumHeight;
            }

            ViewGroup.LayoutParams params = buildLayoutParams(mViewCandidateList2nd, width, height);

            textView = (TextView) layout.getChildAt(mFullViewWordCount);
            if (textView == null) {
                textView = createCandidateView();
                textView.setLayoutParams(params);

                mViewCandidateList2nd.addView(textView);
            } else {
                mViewCandidateList2nd.updateViewLayout(textView, params);
            }

            mFullViewOccupyCount += occupyCount;
            mFullViewWordCount++;
            mFullViewPrevView = textView;
            mFullViewPrevParams = params;

        } else {
            int viewDivison = getCandidateViewDivison();
            int indentWidth = mViewWidth / viewDivison;
            textLength = Math.max(textLength, indentWidth);

            /* Normal view */
            int nextEnd = mLineLength + textLength;
            nextEnd += getCandidateSpaceWidth(isEmojiSymbol);

            if (mLineCount == 1 && !mIsSymbolMode) {
                maxWidth -= getCandidateMinimumWidth();
            }

            if ((maxWidth < nextEnd) && (getWordCount() != 0)) {

                createNextLineFor1st();
                if (getMaxLine() < mLineCount) {
                    mLineLength = 0;
                    /* Call this method again to add the candidate in the full view */
                    if (!mIsSymbolSelected) {
                        setCandidate(isCategory, word);
                    }
                    return;
                }
                
                mLineLength = textLength;
                mLineLength += getCandidateSpaceWidth(isEmojiSymbol);
            } else {
                mLineLength = nextEnd;
            }

            LinearLayout lineView = (LinearLayout) mViewCandidateList1st.getChildAt(mLineCount - 1);
            textView = (TextView) lineView.getChildAt(mNormalViewWordCountOfLine);

            if (isCategory) {
                if (mLineCount == 1) {
                    mViewCandidateTemplate.setBackgroundDrawable(null);
                }
                mLineLength += mCandidateLeftAlignThreshold;
            } else {
                int CompareLength = textLength;
                CompareLength += getCandidateSpaceWidth(isEmojiSymbol);
            }

            mNormalViewWordCountOfLine++;
        }

        textView.setText(word.candidate);
        if (is2nd) {
            textView.setId(mWordCount2nd);
        } else {
            textView.setId(mWordCount1st);
        }
        textView.setVisibility(View.VISIBLE);
        textView.setPressed(false);
        textView.setFocusable(false);

        if (isCategory) {
            textView.setText("      " + word.candidate);

            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCandCategoryTextSize);
            textView.setBackgroundDrawable(null);
            textView.setGravity(Gravity.CENTER_VERTICAL);
            textView.setMinHeight(mCandidateCategoryMinimumHeight);
            textView.setHeight(mCandidateCategoryMinimumHeight);

            textView.setOnClickListener(null);
            textView.setOnLongClickListener(null);
            textView.setTextColor(mTextColor);
        } else {
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCandNormalTextSize);
            textView.setGravity(Gravity.CENTER);
            textView.setMinHeight(getCandidateMinimumHeight());
            textView.setHeight(getCandidateMinimumHeight());

            if (is2nd) {
                textView.setOnClickListener(mCandidateOnClick2nd);
                textView.setOnLongClickListener(mCandidateOnLongClick2nd);
            } else {
                textView.setOnClickListener(mCandidateOnClick1st);
                textView.setOnLongClickListener(mCandidateOnLongClick1st);
            }

            textView.setBackgroundResource(R.drawable.cand_back);
 
            textView.setTextColor(mTextColor);
        }

        if (maxWidth < textLength) {
            textView.setEllipsize(TextUtils.TruncateAt.END);
        } else {
            textView.setEllipsize(null);
        }

        ImageSpan span = null;
        if (word.candidate.equals(" ")) {
            span = new ImageSpan(mWnn, R.drawable.word_half_space,
                                 DynamicDrawableSpan.ALIGN_BASELINE);
        } else if (word.candidate.equals("\u3000" /* full-width space */)) {
            span = new ImageSpan(mWnn, R.drawable.word_full_space,
                                 DynamicDrawableSpan.ALIGN_BASELINE);
        }

        if (span != null) {
            SpannableString spannable = new SpannableString("   ");
            spannable.setSpan(span, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            textView.setText(spannable);
        }
        textView.setPadding(0, 0, 0, 0);

        if (is2nd) {
            mWnnWordArray2nd.add(mWordCount2nd, word);
            mWordCount2nd++;
            mTextViewArray2nd.add(textView);
        } else {
            mWnnWordArray1st.add(mWordCount1st, word);
            mWordCount1st++;
            mTextViewArray1st.add(textView);
        }
    }

    /**
     * Create AbsoluteLayout.LayoutParams
     * @param layout AbsoluteLayout
     * @param width  The width of the display
     * @param height The height of the display
     * @return Layout parameter
     */
    private ViewGroup.LayoutParams buildLayoutParams(AbsoluteLayout layout, int width, int height) {

        int viewDivison = getCandidateViewDivison();
        int indentWidth = mViewWidth / viewDivison;
        int x         = indentWidth * mFullViewOccupyCount;
        int y         = mLineY;
        ViewGroup.LayoutParams params
              = new AbsoluteLayout.LayoutParams(width, height, x, y);

        return params;
    }

    /**
     * Create a view for a candidate.
     * @return the view
     */
    private TextView createCandidateView() {
        TextView text = new CandidateTextView(mViewBodyScroll.getContext());
        text.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCandNormalTextSize);
        text.setBackgroundResource(R.drawable.cand_back);
        text.setCompoundDrawablePadding(0);
        text.setGravity(Gravity.CENTER);
        text.setSingleLine();
        text.setPadding(0, 0, 0, 0);
        text.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                                                           ViewGroup.LayoutParams.WRAP_CONTENT,
                                                           1.0f));
        text.setMinHeight(getCandidateMinimumHeight());
        text.setMinimumWidth(getCandidateMinimumWidth());
        text.setSoundEffectsEnabled(false);
        return text;
    }

    /**
     * Display {@code mReadMoreText} if there are more candidates.
     */
    private void setReadMore() {
        if (mIsSymbolMode) {
            mReadMoreButton.setVisibility(View.GONE);
            mViewCandidateTemplate.setVisibility(View.GONE);
            return;
        }

        int resid = 0;

        if (mIsFullView) {
            mReadMoreButton.setVisibility(View.VISIBLE);
            resid = R.drawable.cand_up;
        } else {
            if (mCanReadMore) {
                mReadMoreButton.setVisibility(View.VISIBLE);
                resid = R.drawable.cand_down;
            } else {
                mReadMoreButton.setVisibility(View.GONE);
                mViewCandidateTemplate.setVisibility(View.GONE);
            }
        }

        if (resid != 0) {
            mReadMoreButton.setImageResource(resid);
        }
    }

    /**
     * Clear the list of the normal candidate view.
     */
    private void clearNormalViewCandidate() {
        LinearLayout candidateList = mViewCandidateList1st;
        int lineNum = candidateList.getChildCount();
        for (int i = 0; i < lineNum; i++) {

            LinearLayout lineView = (LinearLayout)candidateList.getChildAt(i);
            int size = lineView.getChildCount();
            for (int j = 0; j < size; j++) {
                View v = lineView.getChildAt(j);
                v.setVisibility(View.GONE);
            }
        }
    }

    /** @see CandidatesViewManager#clearCandidates */
    public void clearCandidates() {
        closeDialog();
        clearFocusCandidate();
        clearNormalViewCandidate();

        ViewGroup layout = mViewCandidateList2nd;
        int size = layout.getChildCount();
        for (int i = 0; i < size; i++) {
            View v = layout.getChildAt(i);
            v.setVisibility(View.GONE);
        }

        mLineCount = 1;
        mWordCount1st = 0;
        mWordCount2nd = 0;
        mWnnWordArray1st.clear();
        mWnnWordArray2nd.clear();
        mTextViewArray1st.clear();
        mTextViewArray2nd.clear();
        mLineLength = 0;

        mLineY = 0;

        mIsFullView = false;
        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
        if (mAutoHideMode) {
            setViewLayout(CandidatesViewManager.VIEW_TYPE_CLOSE);
        }

        if (mAutoHideMode && mViewBody.isShown()) {
            mWnn.setCandidatesViewShown(false);
        }
        mCanReadMore = false;
        setReadMore();
    }

    /** @see CandidatesViewManager#setPreferences */
    public void setPreferences(SharedPreferences pref) {
        try {
            if (pref.getBoolean("key_vibration", false)) {
                mVibrator = (Vibrator)mWnn.getSystemService(Context.VIBRATOR_SERVICE);
            } else {
                mVibrator = null;
            }
            if (pref.getBoolean("key_sound", false)) {
                mSound = (AudioManager)mWnn.getSystemService(Context.AUDIO_SERVICE);
            } else {
                mSound = null;
            }
            setNumeberOfDisplayLines();
        } catch (Exception ex) {
            Log.e("OpenWnn", "NO VIBRATOR");
        }
    }
    
    /**
     * Set normal mode.
     */
    public void setNormalMode() {
        setReadMore();
        mIsFullView = false;
        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
    }

    /**
     * Set full mode.
     */
    public void setFullMode() {
        mIsFullView = true;
        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL));
    }

    /**
     * Set symbol mode.
     */
    public void setSymbolMode(boolean enable, int mode) {
        if (mIsSymbolMode && !enable) {
            setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE);
        }
        mSymbolMode = mode;
        mIsSymbolMode = enable;
    }

    /**
     * Set scroll up.
     */
    public void setScrollUp() {
        if (!mViewBodyScroll.pageScroll(ScrollView.FOCUS_UP)) {
            mViewBodyScroll.scrollTo(0, mViewBodyScroll.getChildAt(0).getHeight());
        }
    }

    /**
     * Set scroll down.
     */
    public void setScrollDown() {
        if (!mViewBodyScroll.pageScroll(ScrollView.FOCUS_DOWN)) {
            mViewBodyScroll.scrollTo(0, 0);
        }
    }

    /**
     * Set scroll full up.
     */
    public void setScrollFullUp() {
        if (!mViewBodyScroll.fullScroll(ScrollView.FOCUS_UP)) {
            mViewBodyScroll.scrollTo(0, mViewBodyScroll.getChildAt(0).getHeight());
        }
    }

    /**
     * Set scroll full down.
     */
    public void setScrollFullDown() {
        if (!mViewBodyScroll.fullScroll(ScrollView.FOCUS_DOWN)) {
            mViewBodyScroll.scrollTo(0, 0);
        }
    }

    /**
     * Process {@link OpenWnnEvent#CANDIDATE_VIEW_TOUCH} event.
     *
     * @return      {@code true} if event is processed; {@code false} if otherwise
     */
    public boolean onTouchSync() {
        return mGestureDetector.onTouchEvent(mMotionEvent);
    }

    /**
     * Select a candidate.
     * <br>
     * This method notices the selected word to {@link OpenWnn}.
     *
     * @param word  The selected word
     */
    private void selectCandidate(WnnWord word) {
        if (!mIsSymbolMode) {
            mIsFullView = false;
            mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
        }
        mIsSymbolSelected = mIsSymbolMode;
        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.SELECT_CANDIDATE, word));
    }

    private void playSoundAndVibration() {
        if (mVibrator != null) {
            try {
                mVibrator.vibrate(5);
            } catch (Exception ex) {
                Log.e("OpenWnn", "TextCandidatesViewManager::selectCandidate Vibrator " + ex.toString());
            }
        }
        if (mSound != null) {
            try {
                mSound.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, -1);
            } catch (Exception ex) {
                Log.e("OpenWnn", "TextCandidatesViewManager::selectCandidate Sound " + ex.toString());
            }
        }
    }

    /** @see android.view.GestureDetector.OnGestureListener#onDown */
    public boolean onDown(MotionEvent arg0) {
        return false;
    }

    /** @see android.view.GestureDetector.OnGestureListener#onFling */
    public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
        boolean consumed = false;
        if (arg1 != null && arg0 != null && arg1.getY() < arg0.getY()) {
            if ((mViewType == CandidatesViewManager.VIEW_TYPE_NORMAL) && mCanReadMore) {
                if (mVibrator != null) {
                    try {
                        mVibrator.vibrate(5);
                    } catch (Exception ex) {
                        Log.e("iwnn", "TextCandidatesViewManager::onFling Vibrator " + ex.toString());
                    }
                }
                mIsFullView = true;
                mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL));
                consumed = true;
            }
        } else {
            if (mViewBodyScroll.getScrollY() == 0) {
                if (mVibrator != null) {
                    try {
                        mVibrator.vibrate(5);
                    } catch (Exception ex) {
                        Log.e("iwnn", "TextCandidatesViewManager::onFling Sound " + ex.toString());
                    }
                }
                mIsFullView = false;
                mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
                consumed = true;
            }
        }

        return consumed;
    }

    /** @see android.view.GestureDetector.OnGestureListener#onLongPress */
    public void onLongPress(MotionEvent arg0) {
        return;
    }

    /** @see android.view.GestureDetector.OnGestureListener#onScroll */
    public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
        return false;
    }

    /** @see android.view.GestureDetector.OnGestureListener#onShowPress */
    public void onShowPress(MotionEvent arg0) {
    }

    /** @see android.view.GestureDetector.OnGestureListener#onSingleTapUp */
    public boolean onSingleTapUp(MotionEvent arg0) {
        return false;
    }
    
    /**
     * Retrieve the width of string to draw.
     * 
     * @param text          The string
     * @param start         The start position (specified by the number of character)
     * @param end           The end position (specified by the number of character)
     * @return          The width of string to draw
     */ 
    public int measureText(CharSequence text, int start, int end) {
        if (end - start < 3) {
            return getCandidateMinimumWidth();
        }

        TextPaint paint = mViewCandidateTemplate.getPaint();
        return (int)paint.measureText(text, start, end);
    }

    /**
     * Create a layout for the next line.
     */
    private void createNextLine(boolean isCategory) {
        if (isFirstListOver(mIsFullView, mLineCount, null)) {
            /* Full view */
            mFullViewOccupyCount = 0;
            if (mFullViewPrevView != null) {
                mFullViewPrevLineTopId = mFullViewPrevView.getId();
            }
            if (isCategory) {
                mLineY += mCandidateCategoryMinimumHeight;
            } else {
                mLineY += getCandidateMinimumHeight();
            }
            mLineCount++;
        } else {
            createNextLineFor1st();
        }
    }

    /**
     * Create a layout for the next line.
     */
    private void createNextLineFor1st() {
        LinearLayout lineView = (LinearLayout) mViewCandidateList1st.getChildAt(mLineCount - 1);
        float weight = 0;
        if (mLineLength < mCandidateLeftAlignThreshold) {
            if (mLineCount == 1) {
                mViewCandidateTemplate.setVisibility(View.GONE);
            }
        } else {
            weight = 1.0f;
        }

        LinearLayout.LayoutParams params
            = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                                            ViewGroup.LayoutParams.WRAP_CONTENT,
                                            weight);

        int child = lineView.getChildCount();
        for (int i = 0; i < child; i++) {
            View view = lineView.getChildAt(i);

            if (view != mViewCandidateTemplate) {
                view.setLayoutParams(params);
                view.setPadding(0, 0, 0, 0);
            }
        }

        mLineLength = 0;
        mNormalViewWordCountOfLine = 0;
        mLineCount++;
    }

    /**
     * Judge if it's a category.
     *
     * @return {@code true} if category
     */
    boolean isCategory(WnnWord word) {
        int length = word.candidate.length();
        return ((length > 3) && (word.candidate.charAt(0) == '['));
    }

    /**
     * Get a minimum width of a candidate view.
     *
     * @return the minimum width of a candidate view.
     */
    private int getCandidateMinimumWidth() {
        return mCandidateMinimumWidth;
    }

    /**
     * @return the minimum height of a candidate view.
     */
    private int getCandidateMinimumHeight() {
        return mCandidateMinimumHeight;
    }

    /**
     * Get a height of a candidate view.
     *
     * @return the height of a candidate view.
     */
    private int getCandidateViewHeight() {
        if (OpenWnn.isXLarge()) {
           return mKeyboardHeight + mCandidateOneLineMinimumHeight - mSymbolKeyboardHeight
                         - mSymbolKeyboardTabHeight;
        } else {
            int numberOfLine = (mPortrait) ? mPortraitNumberOfLine : mLandscapeNumberOfLine;
            Resources resource = mWnn.getResources();
            Drawable keyboardBackground = resource.getDrawable(R.drawable.keyboard_background);
            Rect keyboardPadding = new Rect(0 ,0 ,0 ,0);
            keyboardBackground.getPadding(keyboardPadding);
            int keyboardTotalPadding = keyboardPadding.top + keyboardPadding.bottom;
            if (mIsSymbolMode) {
                return mKeyboardHeight + numberOfLine * getCandidateMinimumHeight()
                       - mSymbolKeyboardHeight - mSymbolKeyboardTabHeight;
            } else if (!mHardKeyboardHidden) {
                return mKeyboardHeight + numberOfLine * getCandidateMinimumHeight()
                       - mSymbolKeyboardHeight;
            } else {
                return mKeyboardHeight + keyboardTotalPadding
                       + numberOfLine * getCandidateMinimumHeight();
            }
        }
    }

    /**
     * Update symbol type.
     */
    private void updateSymbolType() {
        switch (mSymbolMode) {
        case OpenWnnJAJP.ENGINE_MODE_SYMBOL:
            updateTabStatus(mViewTabSymbol, true, true);
            updateTabStatus(mViewTabEmoticon, mEnableEmoticon, false);
            break;

        case OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI:
            updateTabStatus(mViewTabSymbol, true, false);
            updateTabStatus(mViewTabEmoticon, mEnableEmoticon, true);
            break;

        default:
            updateTabStatus(mViewTabSymbol, true, false);
            updateTabStatus(mViewTabEmoticon, mEnableEmoticon, false);
            break;
        }
    }

    /**
     * Update tab status.
     *
     * @param tab           The tab view.
     * @param enabled       The tab is enabled.
     * @param selected      The tab is selected.
     */
    private void updateTabStatus(TextView tab, boolean enabled, boolean selected) {
        tab.setVisibility(View.VISIBLE);
        tab.setEnabled(enabled);
        int backgroundId = 0;
        int colorId = 0;
        if (enabled) {
            if (selected) {
                backgroundId = R.drawable.cand_tab;
                colorId = R.color.tab_textcolor_select;
            } else {
                backgroundId = R.drawable.cand_tab_noselect;
                colorId = R.color.tab_textcolor_no_select;
            }
        } else {
            backgroundId = R.drawable.cand_tab_noselect;
            colorId = R.color.tab_textcolor_disable;
        }
        tab.setBackgroundResource(backgroundId);
        tab.setTextColor(mWnn.getResources().getColor(colorId));
    }

    /**
     * Get candidate number of division.
     * @return Number of division
     */
    private int getCandidateViewDivison() {
        int viewDivison;

        if (mIsSymbolMode) {
            int mode = mSymbolMode;
            switch (mode) {
            case OpenWnnJAJP.ENGINE_MODE_SYMBOL:
                viewDivison = (mPortrait) ? FULL_VIEW_SYMBOL_DIV_PORT : FULL_VIEW_SYMBOL_DIV_LAND;
                break;
            case OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI:
            default:
                viewDivison = FULL_VIEW_DIV;
                break;
            }
        } else {
             viewDivison = FULL_VIEW_DIV;
        }
        return viewDivison;
    }

    /**
     * @return Word count
     */
    private int getWordCount() {
        return mWordCount1st + mWordCount2nd;
    }

    /**
     * @return Add second
     */
    private boolean isFirstListOver(boolean isFullView, int lineCount, WnnWord word) {

        if (mIsSymbolMode) {
            switch (mSymbolMode) {
            case OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI:
                return true;
            case OpenWnnJAJP.ENGINE_MODE_SYMBOL:
				return true;
            default:
                return (isFullView || getMaxLine() < lineCount);
            }
        } else {
            return (isFullView || getMaxLine() < lineCount);
        }
    }

    /**
     * @return Candidate space width
     */
    private int getCandidateSpaceWidth(boolean isEmojiSymbol) {
        Resources r = mWnn.getResources();
        if (mPortrait) {
            if (isEmojiSymbol) {
                return 0;
            } else {
                return r.getDimensionPixelSize(R.dimen.cand_space_width);
            }
        } else {
            if (isEmojiSymbol) {
                return r.getDimensionPixelSize(R.dimen.cand_space_width_emoji_symbol);
            } else {
                return r.getDimensionPixelSize(R.dimen.cand_space_width);
            }
        }
    }

    /**
     * KeyEvent action for the candidate view.
     *
     * @param key    Key event
     */
    public void processMoveKeyEvent(int key) {
        if (!mViewBody.isShown()) {
            return;
        }

        switch (key) {
        case KeyEvent.KEYCODE_DPAD_UP:
            moveFocus(-1, true);
            break;

        case KeyEvent.KEYCODE_DPAD_DOWN:
            moveFocus(1, true);
            break;

        case KeyEvent.KEYCODE_DPAD_LEFT:
            moveFocus(-1, false);
            break;

        case KeyEvent.KEYCODE_DPAD_RIGHT:
            moveFocus(1, false);
            break;

        default:
            break;
        }
    }

    /**
     * Get a flag candidate is focused now.
     *
     * @return the Candidate is focused of a flag.
     */
    public boolean isFocusCandidate(){
        if (mCurrentFocusIndex != FOCUS_NONE) {
            return true;
        }
        return false;
    }

    /**
     * Give focus to View of candidate.
     */
    public void setViewStatusOfFocusedCandidate(){
        View view = mFocusedView;
        if (view != null) {
            view.setBackgroundDrawable(mFocusedViewBackground);
            view.setPadding(0, 0, 0, 0);
        }

        TextView v = getFocusedView();
        mFocusedView = v;
        if (v != null) {
            mFocusedViewBackground = v.getBackground();
            v.setBackgroundResource(R.drawable.cand_back_focuse);
            v.setPadding(0, 0, 0, 0);

            int viewBodyTop = getViewTopOnScreen(mViewBodyScroll);
            int viewBodyBottom = viewBodyTop + mViewBodyScroll.getHeight();
            int focusedViewTop = getViewTopOnScreen(v);
            int focusedViewBottom = focusedViewTop + v.getHeight();

            if (focusedViewBottom > viewBodyBottom) {
                mViewBodyScroll.scrollBy(0, (focusedViewBottom - viewBodyBottom));
            } else if (focusedViewTop < viewBodyTop) {
                mViewBodyScroll.scrollBy(0, (focusedViewTop - viewBodyTop));
            }
        }
    }

    /**
     * Clear focus to selected candidate.
     */
    public void clearFocusCandidate(){
        View view = mFocusedView;
        if (view != null) {
            view.setBackgroundDrawable(mFocusedViewBackground);
            view.setPadding(0, 0, 0, 0);
            mFocusedView = null;
        }

        mFocusAxisX = 0;
        mHasFocusedArray1st = true;
        mCurrentFocusIndex = FOCUS_NONE;
        mHandler.removeMessages(MSG_MOVE_FOCUS);
        mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_END));
    }

    /**
     * @see CandidatesViewManager#selectFocusCandidate
     */
    public void selectFocusCandidate(){
        if (mCurrentFocusIndex != FOCUS_NONE) {
            WnnWord word = getFocusedWnnWord();

            if (mHandler.hasMessages(MSG_SET_CANDIDATES)) {
                mWnnWordSelectedList.add(word);
            } else {
                selectCandidate(word);
            }
        }
    }

    /** @see CandidatesViewManager#getFocusedWnnWord */
    public WnnWord getFocusedWnnWord() {
        return getWnnWord(mCurrentFocusIndex);
    }

    /**
     * Get WnnWord.
     * 
     * @return WnnWord word
     */
    public WnnWord getWnnWord(int index) {
        WnnWord word = null;
        if (index < 0) {
            index = 0;
            mHandler.removeMessages(MSG_MOVE_FOCUS);
            Log.i("iwnn", "TextCandidatesViewManager::getWnnWord  index < 0 ");
        } else {
            int size = mHasFocusedArray1st ? mWnnWordArray1st.size() : mWnnWordArray2nd.size();
            if (index >= size) {
                index = size - 1;
                mHandler.removeMessages(MSG_MOVE_FOCUS);
                Log.i("iwnn", "TextCandidatesViewManager::getWnnWord  index > candidate max ");
            }
        }
     
        if (mHasFocusedArray1st) {
            word = mWnnWordArray1st.get(index);
        } else {
            word = mWnnWordArray2nd.get(index);
        }
        return word;
    }

    /**
     * Set display candidate line from SharedPreferences.
     */
    private void setNumeberOfDisplayLines(){
        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mWnn);
        mPortraitNumberOfLine = Integer.parseInt(pref.getString("setting_portrait", "2"));
        mLandscapeNumberOfLine = Integer.parseInt(pref.getString("setting_landscape", "1"));
    }

    /**
     * Set emoticon enabled.
     */
    public void setEnableEmoticon(boolean enableEmoticon) {
        mEnableEmoticon = enableEmoticon;
    }

    /**
     * Get View of focus candidate.
     */
    public TextView getFocusedView() {
        if (mCurrentFocusIndex == FOCUS_NONE) {
            return null;
        }
        TextView t;
        if (mHasFocusedArray1st) {
            t = mTextViewArray1st.get(mCurrentFocusIndex);
        } else {
            t = mTextViewArray2nd.get(mCurrentFocusIndex);
        }
        return t;
    }

    /**
     * Move the focus to next candidate.
     *
     * @param direction  The direction of increment or decrement.
     * @param updown     {@code true} if move is up or down.
     */
    public void moveFocus(int direction, boolean updown) {
        boolean isStart = (mCurrentFocusIndex == FOCUS_NONE);
        if (direction == 0) {
            setViewStatusOfFocusedCandidate();
        }

        int size1st = mTextViewArray1st.size();
        if (mHasFocusedArray1st && (size1st == 0)) {
            mHasFocusedArray1st = false;
        }
        ArrayList<TextView> list = mHasFocusedArray1st ? mTextViewArray1st : mTextViewArray2nd;
        int size = list.size();
        int start = (mCurrentFocusIndex == FOCUS_NONE) ? 0 : (mCurrentFocusIndex + direction);

        int index = -1;
        boolean hasChangedLine = false;
        for (int i = start; (0 <= i) && (i < size); i += direction) {
            TextView view = list.get(i);
            if (!view.isShown()) {
                break;
            }

            if (mIsSymbolMode && (view.getBackground() == null)) {
                continue;
            }

            if (updown) {
                int left = view.getLeft();
                if ((left <= mFocusAxisX)
                        && (mFocusAxisX < view.getRight())) {
                    index = i;
                    break;
                }

                if (left == 0) {
                    hasChangedLine = true;
                }
            } else {
                index = i;
                break;
            }
        }

        if ((index < 0) && hasChangedLine && !mHasFocusedArray1st && (0 < direction)) {
            index = mTextViewArray2nd.size() - 1;
        }

        if (0 <= index) {
            mCurrentFocusIndex = index;
            setViewStatusOfFocusedCandidate();
            if (!updown) {
                mFocusAxisX = getFocusedView().getLeft();
            }
        } else {
            if (mCanReadMore && (0 < size1st)) {

                if ((mHasFocusedArray1st && (direction < 0))
                        || (!mHasFocusedArray1st && (0 < direction))) {
                    updown = false;
                }

                mHasFocusedArray1st = !mHasFocusedArray1st;

                if (!mHasFocusedArray1st && !mIsFullView) {
                    setFullMode();
                }
            }

            if (size1st == 0) {
                updown = false;
            }

            if (0 < direction) {
                mCurrentFocusIndex = -1;
            } else {
                mCurrentFocusIndex = (mHasFocusedArray1st ? size1st : mTextViewArray2nd.size());
            }
            Message m = mHandler.obtainMessage(MSG_MOVE_FOCUS, direction, updown ? 1 : 0);
            mHandler.sendMessage(m);
        }

        if (isStart) {
            mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_START));
        }
    }

    /**
     * Set hardkeyboard hidden.
     *
     * @param hardKeyboardHidden hardkeyaboard hidden.
     */
    public void setHardKeyboardHidden(boolean hardKeyboardHidden) {
        mHardKeyboardHidden = hardKeyboardHidden;
    }

    /**
     * Get view top position on screen.
     *
     * @param view target view.
     * @return int view top position on screen
     */
    public int getViewTopOnScreen(View view) {
        int[] location = new int[2];
        view.getLocationOnScreen(location);
        return location[1];
    }


    /** @see CandidatesViewManager#setCandidateMsgRemove */
    public void setCandidateMsgRemove() {
        mHandler.removeMessages(MSG_SET_CANDIDATES);
    }
}