/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * 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.android.internal.widget;

import java.io.InputStream;
import java.util.ArrayList;

import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.Html;
import android.text.Layout;
import android.text.Spannable;
import android.text.Spanned;
import android.text.method.ArrowKeyMovementMethod;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.ParagraphStyle;
import android.text.style.QuoteSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;

/**
 * EditStyledText extends EditText for managing the flow and status to edit
 * the styled text. This manages the states and flows of editing, supports
 * inserting image, import/export HTML.
 */
public class EditStyledText extends EditText {

    private static final String LOG_TAG = "EditStyledText";
    private static final boolean DBG = false;

    /**
     * The modes of editing actions.
     */
    /** The mode that no editing action is done. */
    public static final int MODE_NOTHING = 0;
    /** The mode of copy. */
    public static final int MODE_COPY = 1;
    /** The mode of paste. */
    public static final int MODE_PASTE = 2;
    /** The mode of changing size. */
    public static final int MODE_SIZE = 3;
    /** The mode of changing color. */
    public static final int MODE_COLOR = 4;
    /** The mode of selection. */
    public static final int MODE_SELECT = 5;
    /** The mode of changing alignment. */
    public static final int MODE_ALIGN = 6;
    /** The mode of changing cut. */
    public static final int MODE_CUT = 7;

    /**
     * The state of selection.
     */
    /** The state that selection isn't started. */
    public static final int STATE_SELECT_OFF = 0;
    /** The state that selection is started. */
    public static final int STATE_SELECT_ON = 1;
    /** The state that selection is done, but not fixed. */
    public static final int STATE_SELECTED = 2;
    /** The state that selection is done and not fixed. */
    public static final int STATE_SELECT_FIX = 3;

    /**
     * The help message strings.
     */
    public static final int HINT_MSG_NULL = 0;
    public static final int HINT_MSG_COPY_BUF_BLANK = 1;
    public static final int HINT_MSG_SELECT_START = 2;
    public static final int HINT_MSG_SELECT_END = 3;
    public static final int HINT_MSG_PUSH_COMPETE = 4;

    
    /**
     * The help message strings.
     */
    public static final int DEFAULT_BACKGROUND_COLOR = 0x00FFFFFF;

    /**
     * EditStyledTextInterface provides functions for notifying messages to
     * calling class.
     */
    public interface EditStyledTextNotifier {
        public void notifyHintMsg(int msgId);
        public void notifyStateChanged(int mode, int state);
    }

    private EditStyledTextNotifier mESTInterface;

    /**
     * EditStyledTextEditorManager manages the flow and status of each
     * function for editing styled text.
     */
    private EditorManager mManager;
    private StyledTextConverter mConverter;
    private StyledTextDialog mDialog;
    private Drawable mDefaultBackground;
    private int mBackgroundColor;

    /**
     * EditStyledText extends EditText for managing flow of each editing
     * action.
     */
    public EditStyledText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public EditStyledText(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public EditStyledText(Context context) {
        super(context);
        init();
    }

    /**
     * Set Notifier.
     */
    public void setNotifier(EditStyledTextNotifier estInterface) {
        mESTInterface = estInterface;
    }

    /**
     * Set Builder for AlertDialog.
     * 
     * @param builder
     *            Builder for opening Alert Dialog.
     */
    public void setBuilder(Builder builder) {
        mDialog.setBuilder(builder);
    }

    /**
     * Set Parameters for ColorAlertDialog.
     * 
     * @param colortitle
     *            Title for Alert Dialog.
     * @param colornames
     *            List of name of selecting color.
     * @param colorints
     *            List of int of color.
     */
    public void setColorAlertParams(CharSequence colortitle,
            CharSequence[] colornames, CharSequence[] colorints) {
        mDialog.setColorAlertParams(colortitle, colornames, colorints);
    }

    /**
     * Set Parameters for SizeAlertDialog.
     * 
     * @param sizetitle
     *            Title for Alert Dialog.
     * @param sizenames
     *            List of name of selecting size.
     * @param sizedisplayints
     *            List of int of size displayed in TextView.
     * @param sizesendints
     *            List of int of size exported to HTML.
     */
    public void setSizeAlertParams(CharSequence sizetitle,
            CharSequence[] sizenames, CharSequence[] sizedisplayints,
            CharSequence[] sizesendints) {
        mDialog.setSizeAlertParams(sizetitle, sizenames, sizedisplayints,
                sizesendints);
    }

    public void setAlignAlertParams(CharSequence aligntitle,
            CharSequence[] alignnames) {
        mDialog.setAlignAlertParams(aligntitle, alignnames);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mManager.isSoftKeyBlocked() &&
                event.getAction() == MotionEvent.ACTION_UP) {
            cancelLongPress();
        }
        final boolean superResult = super.onTouchEvent(event);
        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (DBG) {
                Log.d(LOG_TAG, "--- onTouchEvent");
            }
            mManager.onCursorMoved();
        }
        return superResult;
    }

    /**
     * Start editing. This function have to be called before other editing
     * actions.
     */
    public void onStartEdit() {
        mManager.onStartEdit();
    }

    /**
     * End editing.
     */
    public void onEndEdit() {
        mManager.onEndEdit();
    }

    /**
     * Start "Copy" action.
     */
    public void onStartCopy() {
        mManager.onStartCopy();
    }

    /**
     * Start "Cut" action.
     */
    public void onStartCut() {
        mManager.onStartCut();
    }

    /**
     * Start "Paste" action.
     */
    public void onStartPaste() {
        mManager.onStartPaste();
    }

    /**
     * Start changing "Size" action.
     */
    public void onStartSize() {
        mManager.onStartSize();
    }

    /**
     * Start changing "Color" action.
     */
    public void onStartColor() {
        mManager.onStartColor();
    }

    /**
     * Start changing "BackgroundColor" action.
     */
    public void onStartBackgroundColor() {
        mManager.onStartBackgroundColor();
    }

    /**
     * Start changing "Alignment" action.
     */
    public void onStartAlign() {
        mManager.onStartAlign();
    }

    /**
     * Start "Select" action.
     */
    public void onStartSelect() {
        mManager.onStartSelect();
    }

    /**
     * Start "SelectAll" action.
     */
    public void onStartSelectAll() {
        mManager.onStartSelectAll();
    }

    /**
     * Fix Selected Item.
     */
    public void onFixSelectedItem() {
        mManager.onFixSelectedItem();
    }

    /**
     * InsertImage to TextView by using URI
     * 
     * @param uri
     *            URI of the iamge inserted to TextView.
     */
    public void onInsertImage(Uri uri) {
        mManager.onInsertImage(uri);
    }

    /**
     * InsertImage to TextView by using resource ID
     * 
     * @param resId
     *            Resource ID of the iamge inserted to TextView.
     */
    public void onInsertImage(int resId) {
        mManager.onInsertImage(resId);
    }

    public void onInsertHorizontalLine() {
        mManager.onInsertHorizontalLine();
    }

    public void onClearStyles() {
        mManager.onClearStyles();
    }
    /**
     * Set Size of the Item.
     * 
     * @param size
     *            The size of the Item.
     */
    public void setItemSize(int size) {
        mManager.setItemSize(size);
    }

    /**
     * Set Color of the Item.
     * 
     * @param color
     *            The color of the Item.
     */
    public void setItemColor(int color) {
        mManager.setItemColor(color);
    }

    /**
     * Set Alignment of the Item.
     * 
     * @param color
     *            The color of the Item.
     */
    public void setAlignment(Layout.Alignment align) {
        mManager.setAlignment(align);
    }

    /**
     * Set Background color of View.
     * 
     * @param color
     *            The background color of view.
     */
    @Override
    public void setBackgroundColor(int color) {
        super.setBackgroundColor(color);
        mBackgroundColor = color;
    }

    /**
     * Set html to EditStyledText.
     * 
     * @param html
     *            The html to be set.
     */
    public void setHtml(String html) {
        mConverter.SetHtml(html);
    }
    /**
     * Check whether editing is started or not.
     * 
     * @return Whether editing is started or not.
     */
    public boolean isEditting() {
        return mManager.isEditting();
    }

    /**
     * Check whether styled text or not.
     * 
     * @return Whether styled text or not.
     */
    public boolean isStyledText() {
        return mManager.isStyledText();
    }
    /**
     * Check whether SoftKey is Blocked or not.
     * 
     * @return whether SoftKey is Blocked or not.
     */
    public boolean isSoftKeyBlocked() {
        return mManager.isSoftKeyBlocked();
    }

    /**
     * Get the mode of the action.
     * 
     * @return The mode of the action.
     */
    public int getEditMode() {
        return mManager.getEditMode();
    }

    /**
     * Get the state of the selection.
     * 
     * @return The state of the selection.
     */
    public int getSelectState() {
        return mManager.getSelectState();
    }

    @Override
    public Bundle getInputExtras(boolean create) {
        if (DBG) {
            Log.d(LOG_TAG, "---getInputExtras");
        }
        Bundle bundle = super.getInputExtras(create);
        if (bundle != null) {
            bundle = new Bundle();
        }
        bundle.putBoolean("allowEmoji", true);
        return bundle;
    }

    /**
     * Get the state of the selection.
     * 
     * @return The state of the selection.
     */
    public String getHtml() {
        return mConverter.getHtml();
    }

    /**
     * Get the state of the selection.
     * 
     * @param uris
     *            The array of used uris.
     * @return The state of the selection.
     */
    public String getHtml(ArrayList<Uri> uris) {
        mConverter.getUriArray(uris, getText());
        return mConverter.getHtml();
    }

    /**
     * Get Background color of View.
     * 
     * @return The background color of View.
     */
    public int getBackgroundColor() {
        return mBackgroundColor;
    }

    /**
     * Get Foreground color of View.
     * 
     * @return The background color of View.
     */
    public int getForeGroundColor(int pos) {
        if (DBG) {
            Log.d(LOG_TAG, "---getForeGroundColor: " + pos);
        }
        if (pos < 0 || pos > getText().length()) {
            Log.e(LOG_TAG, "---getForeGroundColor: Illigal position.");
            return DEFAULT_BACKGROUND_COLOR;
        } else {
            ForegroundColorSpan[] spans =
                getText().getSpans(pos, pos, ForegroundColorSpan.class);
            if (spans.length > 0) {
                return spans[0].getForegroundColor();
            } else {
                return DEFAULT_BACKGROUND_COLOR;
            }
        }
    }

    /**
     * Initialize members.
     */
    private void init() {
        if (DBG) {
            Log.d(LOG_TAG, "--- init");
        }
        requestFocus();
        mDefaultBackground = getBackground();
        mBackgroundColor = DEFAULT_BACKGROUND_COLOR;
        mManager = new EditorManager(this);
        mConverter = new StyledTextConverter(this);
        mDialog = new StyledTextDialog(this);
        setMovementMethod(new StyledTextArrowKeyMethod(mManager));
        mManager.blockSoftKey();
        mManager.unblockSoftKey();
    }

    /**
     * Show Foreground Color Selecting Dialog.
     */
    private void onShowForegroundColorAlert() {
        mDialog.onShowForegroundColorAlertDialog();
    }

    /**
     * Show Background Color Selecting Dialog.
     */
    private void onShowBackgroundColorAlert() {
        mDialog.onShowBackgroundColorAlertDialog();
    }

    /**
     * Show Size Selecting Dialog.
     */
    private void onShowSizeAlert() {
        mDialog.onShowSizeAlertDialog();
    }

    /**
     * Show Alignment Selecting Dialog.
     */
    private void onShowAlignAlert() {
        mDialog.onShowAlignAlertDialog();
    }

    /**
     * Notify hint messages what action is expected to calling class.
     * 
     * @param msgId
     *            Id of the hint message.
     */
    private void setHintMessage(int msgId) {
        if (mESTInterface != null) {
            mESTInterface.notifyHintMsg(msgId);
        }
    }

    /**
     * Notify the event that the mode and state are changed.
     * 
     * @param mode
     *            Mode of the editing action.
     * @param state
     *            Mode of the selection state.
     */
    private void notifyStateChanged(int mode, int state) {
        if (mESTInterface != null) {
            mESTInterface.notifyStateChanged(mode, state);
        }
    }

    /**
     * EditorManager manages the flow and status of editing actions.
     */
    private class EditorManager {
        private boolean mEditFlag = false;
        private boolean mSoftKeyBlockFlag = false;
        private int mMode = 0;
        private int mState = 0;
        private int mCurStart = 0;
        private int mCurEnd = 0;
        private EditStyledText mEST;

        EditorManager(EditStyledText est) {
            mEST = est;
        }

        public void onStartEdit() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onStartEdit");
            }
            Log.d(LOG_TAG, "--- onstartedit:");
            handleResetEdit();
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onEndEdit() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onEndEdit");
            }
            handleCancel();
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onStartCopy() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onStartCopy");
            }
            handleCopy();
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onStartCut() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onStartCut");
            }
            handleCut();
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onStartPaste() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onStartPaste");
            }
            handlePaste();
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onStartSize() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onStartSize");
            }
            handleSize();
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onStartAlign() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onStartAlignRight");
            }
            handleAlign();
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onStartColor() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onClickColor");
            }
            handleColor();
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onStartBackgroundColor() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onClickColor");
            }
            mEST.onShowBackgroundColorAlert();
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onStartSelect() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onClickSelect");
            }
            mMode = MODE_SELECT;
            if (mState == STATE_SELECT_OFF) {
                handleSelect();
            } else {
                unsetSelect();
                handleSelect();
            }
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onCursorMoved() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onClickView");
            }
            if (mState == STATE_SELECT_ON || mState == STATE_SELECTED) {
                handleSelect();
                mEST.notifyStateChanged(mMode, mState);
            }
        }

        public void onStartSelectAll() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onClickSelectAll");
            }
            handleSelectAll();
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onFixSelectedItem() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onClickComplete");
            }
            handleComplete();
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onInsertImage(Uri uri) {
            if (DBG) {
                Log.d(LOG_TAG, "--- onInsertImage by URI: " + uri.getPath()
                        + "," + uri.toString());
            }
            insertImageSpan(new ImageSpan(mEST.getContext(), uri));
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onInsertImage(int resID) {
            if (DBG) {
                Log.d(LOG_TAG, "--- onInsertImage by resID");
            }
            insertImageSpan(new ImageSpan(mEST.getContext(), resID));
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onInsertHorizontalLine() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onInsertHorizontalLine:");
            }
            insertImageSpan(new HorizontalLineSpan(0xFF000000, mEST));
            mEST.notifyStateChanged(mMode, mState);
        }

        public void onClearStyles() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onClearStyles");
            }
            Editable txt = mEST.getText();
            int len = txt.length();
            Object[] styles = txt.getSpans(0, len, Object.class);
            for (Object style : styles) {
                if (style instanceof ParagraphStyle ||
                        style instanceof QuoteSpan ||
                        style instanceof CharacterStyle) {
                    if (style instanceof ImageSpan) {
                        int start = txt.getSpanStart(style);
                        int end = txt.getSpanEnd(style);
                        txt.replace(start, end, "");
                    }
                    txt.removeSpan(style);
                }
            }
            mEST.setBackgroundDrawable(mEST.mDefaultBackground);
            mEST.mBackgroundColor = DEFAULT_BACKGROUND_COLOR;
        }

        public void setItemSize(int size) {
            if (DBG) {
                Log.d(LOG_TAG, "--- onClickSizeItem");
            }
            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
                changeSizeSelectedText(size);
                handleResetEdit();
            }
        }

        public void setItemColor(int color) {
            if (DBG) {
                Log.d(LOG_TAG, "--- onClickColorItem");
            }
            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
                changeColorSelectedText(color);
                handleResetEdit();
            }
        }

        public void setAlignment(Layout.Alignment align) {
            if (DBG) {
                Log.d(LOG_TAG, "--- onClickColorItem");
            }
            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
                changeAlign(align);
                handleResetEdit();
            }
        }

        public boolean isEditting() {
            return mEditFlag;
        }

        /* If the style of the span is added, add check case for that style */
        public boolean isStyledText() {
            Editable txt = mEST.getText();
            int len = txt.length();
            if (txt.getSpans(0, len -1, ParagraphStyle.class).length > 0 ||
                    txt.getSpans(0, len -1, QuoteSpan.class).length > 0 ||
                    txt.getSpans(0, len -1, CharacterStyle.class).length > 0 ||
                    mEST.mBackgroundColor != DEFAULT_BACKGROUND_COLOR) {
                return true;
            }
            return false;
        }

        public boolean isSoftKeyBlocked() {
            return mSoftKeyBlockFlag;
        }

        public int getEditMode() {
            return mMode;
        }

        public int getSelectState() {
            return mState;
        }

        public int getSelectionStart() {
            return mCurStart;
        }

        public int getSelectionEnd() {
            return mCurEnd;
        }

        private void doNextHandle() {
            if (DBG) {
                Log.d(LOG_TAG, "--- doNextHandle: " + mMode + "," + mState);
            }
            switch (mMode) {
            case MODE_COPY:
                handleCopy();
                break;
            case MODE_CUT:
                handleCut();
                break;
            case MODE_PASTE:
                handlePaste();
                break;
            case MODE_SIZE:
                handleSize();
                break;
            case MODE_COLOR:
                handleColor();
                break;
            case MODE_ALIGN:
                handleAlign();
                break;
            default:
                break;
            }
        }

        private void handleCancel() {
            if (DBG) {
                Log.d(LOG_TAG, "--- handleCancel");
            }
            mMode = MODE_NOTHING;
            mState = STATE_SELECT_OFF;
            mEditFlag = false;
            Log.d(LOG_TAG, "--- handleCancel:" + mEST.getInputType());
            unblockSoftKey();
            unsetSelect();
        }

        private void handleComplete() {
            if (DBG) {
                Log.d(LOG_TAG, "--- handleComplete");
            }
            if (!mEditFlag) {
                return;
            }
            if (mState == STATE_SELECTED) {
                mState = STATE_SELECT_FIX;
            }
            doNextHandle();
        }

        private void handleTextViewFunc(int mode, int id) {
            if (DBG) {
                Log.d(LOG_TAG, "--- handleTextView: " + mMode + "," + mState +
                        "," + id);
            }
            if (!mEditFlag) {
                return;
            }
            if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
                mMode = mode;
                if (mState == STATE_SELECTED) {
                    mState = STATE_SELECT_FIX;
                    handleTextViewFunc(mode, id);
                } else {
                    handleSelect();
                }
            } else if (mMode != mode) {
                handleCancel();
                mMode = mode;
                handleTextViewFunc(mode, id);
            } else if (mState == STATE_SELECT_FIX) {
                mEST.onTextContextMenuItem(id);
                handleResetEdit();
            }
        }

        private void handleCopy() {
            if (DBG) {
                Log.d(LOG_TAG, "--- handleCopy: " + mMode + "," + mState);
            }
            handleTextViewFunc(MODE_COPY, android.R.id.copy);
        }

        private void handleCut() {
            if (DBG) {
                Log.d(LOG_TAG, "--- handleCopy: " + mMode + "," + mState);
            }
            handleTextViewFunc(MODE_CUT, android.R.id.cut);
        }

        private void handlePaste() {
            if (DBG) {
                Log.d(LOG_TAG, "--- handlePaste");
            }
            if (!mEditFlag) {
                return;
            }
            mEST.onTextContextMenuItem(android.R.id.paste);
        }

        private void handleSetSpan(int mode) {
            if (DBG) {
                Log.d(LOG_TAG, "--- handleSetSpan:" + mEditFlag + ","
                        + mState + ',' + mMode);
            }
            if (!mEditFlag) {
                Log.e(LOG_TAG, "--- handleSetSpan: Editing is not started.");
                return;
            }
            if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
                mMode = mode;
                if (mState == STATE_SELECTED) {
                    mState = STATE_SELECT_FIX;
                    handleSetSpan(mode);
                } else {
                    handleSelect();
                }
            } else if (mMode != mode) {
                handleCancel();
                mMode = mode;
                handleSetSpan(mode);
            } else {
                if (mState == STATE_SELECT_FIX) {
                    mEST.setHintMessage(HINT_MSG_NULL);
                    switch (mode) {
                    case MODE_COLOR:
                        mEST.onShowForegroundColorAlert();
                        break;
                    case MODE_SIZE:
                        mEST.onShowSizeAlert();
                        break;
                    case MODE_ALIGN:
                        mEST.onShowAlignAlert();
                        break;
                    default:
                        Log.e(LOG_TAG, "--- handleSetSpan: invalid mode.");
                        break;
                    }
                } else {
                    Log.d(LOG_TAG, "--- handleSetSpan: do nothing.");
                }
            }
        }

        private void handleSize() {
            handleSetSpan(MODE_SIZE);
        }

        private void handleColor() {
            handleSetSpan(MODE_COLOR);
        }

        private void handleAlign() {
            handleSetSpan(MODE_ALIGN);
        }

        private void handleSelect() {
            if (DBG) {
                Log.d(LOG_TAG, "--- handleSelect:" + mEditFlag + "," + mState);
            }
            if (!mEditFlag) {
                return;
            }
            if (mState == STATE_SELECT_OFF) {
                if (isTextSelected()) {
                    Log.e(LOG_TAG, "Selection is off, but selected");
                }
                setSelectStartPos();
                blockSoftKey();
                mEST.setHintMessage(HINT_MSG_SELECT_END);
            } else if (mState == STATE_SELECT_ON) {
                if (isTextSelected()) {
                    Log.e(LOG_TAG, "Selection now start, but selected");
                }
                setSelectedEndPos();
                mEST.setHintMessage(HINT_MSG_PUSH_COMPETE);
                doNextHandle();
            } else if (mState == STATE_SELECTED) {
                if (!isTextSelected()) {
                    Log.e(LOG_TAG, "Selection is done, but not selected");
                }
                setSelectedEndPos();
                doNextHandle();
            }
        }

        private void handleSelectAll() {
            if (DBG) {
                Log.d(LOG_TAG, "--- handleSelectAll");
            }
            if (!mEditFlag) {
                return;
            }
            mEST.selectAll();
            mState = STATE_SELECTED;
        }

        private void handleResetEdit() {
            if (DBG) {
                Log.d(LOG_TAG, "Reset Editor");
            }
            blockSoftKey();
            handleCancel();
            mEditFlag = true;
            mEST.setHintMessage(HINT_MSG_SELECT_START);
        }

        private void setSelection() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onSelect:" + mCurStart + "," + mCurEnd);
            }
            if (mCurStart >= 0 && mCurStart <= mEST.getText().length()
                    && mCurEnd >= 0 && mCurEnd <= mEST.getText().length()) {
                if (mCurStart < mCurEnd) {
                    mEST.setSelection(mCurStart, mCurEnd);
                } else {
                    mEST.setSelection(mCurEnd, mCurStart);
                }
                mState = STATE_SELECTED;
            } else {
                Log.e(LOG_TAG,
                        "Select is on, but cursor positions are illigal.:"
                                + mEST.getText().length() + "," + mCurStart
                                + "," + mCurEnd);
            }
        }

        private void unsetSelect() {
            if (DBG) {
                Log.d(LOG_TAG, "--- offSelect");
            }
            int currpos = mEST.getSelectionStart();
            mEST.setSelection(currpos, currpos);
            mState = STATE_SELECT_OFF;
        }

        private void setSelectStartPos() {
            if (DBG) {
                Log.d(LOG_TAG, "--- setSelectStartPos");
            }
            mCurStart = mEST.getSelectionStart();
            mState = STATE_SELECT_ON;
        }

        private void setSelectedEndPos() {
            if (DBG) {
                Log.d(LOG_TAG, "--- setSelectEndPos:");
            }
            if (mEST.getSelectionStart() == mCurStart) {
                setSelectedEndPos(mEST.getSelectionEnd());
            } else {
                setSelectedEndPos(mEST.getSelectionStart());
            }
        }

        public void setSelectedEndPos(int pos) {
            if (DBG) {
                Log.d(LOG_TAG, "--- setSelectedEndPos:");
            }
            mCurEnd = pos;
            setSelection();
        }

        private boolean isTextSelected() {
            if (DBG) {
                Log.d(LOG_TAG, "--- isTextSelected:" + mCurStart + ","
                        + mCurEnd);
            }
            return (mCurStart != mCurEnd)
                    && (mState == STATE_SELECTED ||
                            mState == STATE_SELECT_FIX);
        }

        private void setStyledTextSpan(Object span, int start, int end) {
            if (DBG) {
                Log.d(LOG_TAG, "--- setStyledTextSpan:" + mMode + ","
                        + start + "," + end);
            }
            if (start < end) {
                mEST.getText().setSpan(span, start, end,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            } else {
                mEST.getText().setSpan(span, end, start,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }

        private void changeSizeSelectedText(int size) {
            if (DBG) {
                Log.d(LOG_TAG, "--- changeSize:" + size);
            }
            setStyledTextSpan(new AbsoluteSizeSpan(size),
                mCurStart, mCurEnd);
        }

        private void changeColorSelectedText(int color) {
            if (DBG) {
                Log.d(LOG_TAG, "--- changeColor:" + color);
            }
            setStyledTextSpan(new ForegroundColorSpan(color),
                mCurStart, mCurEnd);
        }

        private void changeAlign(Layout.Alignment align) {
            if (DBG) {
                Log.d(LOG_TAG, "--- changeAlign:" + align);
            }
            setStyledTextSpan(new AlignmentSpan.Standard(align),
                    findLineStart(mEST.getText(), mCurStart),
                    findLineEnd(mEST.getText(), mCurEnd));
        }

        private int findLineStart(Editable text, int current) {
            if (DBG) {
                Log.d(LOG_TAG, "--- findLineStart: curr:" + current +
                        ", length:" + text.length());
            }
            int pos = current;
            for (; pos > 0; pos--) {
                if (text.charAt(pos - 1) == '\n') {
                    break;
                }
            }
            return pos;
        }

        private void insertImageSpan(ImageSpan span) {
            if (DBG) {
                Log.d(LOG_TAG, "--- insertImageSpan");
            }
            if (span != null) {
                Log.d(LOG_TAG, "--- insertimagespan:" + span.getDrawable().getIntrinsicHeight() + "," + span.getDrawable().getIntrinsicWidth());
                Log.d(LOG_TAG, "--- insertimagespan:" + span.getDrawable().getClass());
                int curpos = mEST.getSelectionStart();
                mEST.getText().insert(curpos, "\uFFFC");
                mEST.getText().setSpan(span, curpos, curpos + 1,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                mEST.notifyStateChanged(mMode, mState);
            } else {
                Log.e(LOG_TAG, "--- insertImageSpan: null span was inserted");
            }
        }

        private int findLineEnd(Editable text, int current) {
            if (DBG) {
                Log.d(LOG_TAG, "--- findLineEnd: curr:" + current +
                        ", length:" + text.length());
            }
            int pos = current;
            for (; pos < text.length(); pos++) {
                if (pos > 0 && text.charAt(pos - 1) == '\n') {
                    break;
                }
            }
            return pos;
        }

        private void blockSoftKey() {
            if (DBG) {
                Log.d(LOG_TAG, "--- blockSoftKey:");
            }
            InputMethodManager imm = (InputMethodManager) mEST.getContext().
            getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(mEST.getWindowToken(), 0);
            mEST.setOnClickListener(
                    new OnClickListener() {
                        public void onClick(View v) {
                            Log.d(LOG_TAG, "--- ontrackballclick:");
                            onFixSelectedItem();
                        }
            });
            mSoftKeyBlockFlag = true;
        }

        private void unblockSoftKey() {
            if (DBG) {
                Log.d(LOG_TAG, "--- unblockSoftKey:");
            }
            mEST.setOnClickListener(null);
            mSoftKeyBlockFlag = false;
        }
    }

    private class StyledTextConverter {
        private EditStyledText mEST;

        public StyledTextConverter(EditStyledText est) {
            mEST = est;
        }

        public String getHtml() {
            String htmlBody = Html.toHtml(mEST.getText());
            if (DBG) {
                Log.d(LOG_TAG, "--- getConvertedBody:" + htmlBody);
            }
            return htmlBody;
        }

        public void getUriArray(ArrayList<Uri> uris, Editable text) {
            uris.clear();
            if (DBG) {
                Log.d(LOG_TAG, "--- getUriArray:");
            }
            int len = text.length();
            int next;
            for (int i = 0; i < text.length(); i = next) {
                next = text.nextSpanTransition(i, len, ImageSpan.class);
                ImageSpan[] images = text.getSpans(i, next, ImageSpan.class);
                for (int j = 0; j < images.length; j++) {
                    if (DBG) {
                        Log.d(LOG_TAG, "--- getUriArray: foundArray" +
                                ((ImageSpan) images[j]).getSource());
                    }
                    uris.add(Uri.parse(
                            ((ImageSpan) images[j]).getSource()));
                }
            }
        }

        public void SetHtml (String html) {
            final Spanned spanned = Html.fromHtml(html, new Html.ImageGetter() {
                public Drawable getDrawable(String src) {
                    Log.d(LOG_TAG, "--- sethtml: src="+src);
                    if (src.startsWith("content://")) {
                        Uri uri = Uri.parse(src);
                        try {
                            InputStream is = mEST.getContext().getContentResolver().openInputStream(uri);
                            Bitmap bitmap = BitmapFactory.decodeStream(is);
                            Drawable drawable = new BitmapDrawable(bitmap);
                            drawable.setBounds(0, 0,
                                    drawable.getIntrinsicWidth(),
                                    drawable.getIntrinsicHeight());
                            is.close();
                            return drawable;
                        } catch (Exception e) {
                            Log.e(LOG_TAG, "--- set html: Failed to loaded content " + uri, e);
                            return null;
                        }
                    }
                    Log.d(LOG_TAG, "  unknown src="+src);
                    return null;
                }
            }, null);
            mEST.setText(spanned);
        }
    }

    private class StyledTextDialog {
        Builder mBuilder;
        CharSequence mColorTitle;
        CharSequence mSizeTitle;
        CharSequence mAlignTitle;
        CharSequence[] mColorNames;
        CharSequence[] mColorInts;
        CharSequence[] mSizeNames;
        CharSequence[] mSizeDisplayInts;
        CharSequence[] mSizeSendInts;
        CharSequence[] mAlignNames;
        EditStyledText mEST;

        public StyledTextDialog(EditStyledText est) {
            mEST = est;
        }

        public void setBuilder(Builder builder) {
            mBuilder = builder;
        }

        public void setColorAlertParams(CharSequence colortitle,
                CharSequence[] colornames, CharSequence[] colorints) {
            mColorTitle = colortitle;
            mColorNames = colornames;
            mColorInts = colorints;
        }

        public void setSizeAlertParams(CharSequence sizetitle,
                CharSequence[] sizenames, CharSequence[] sizedisplayints,
                CharSequence[] sizesendints) {
            mSizeTitle = sizetitle;
            mSizeNames = sizenames;
            mSizeDisplayInts = sizedisplayints;
            mSizeSendInts = sizesendints;
        }

        public void setAlignAlertParams(CharSequence aligntitle,
                CharSequence[] alignnames) {
            mAlignTitle = aligntitle;
            mAlignNames = alignnames;
        }

        private boolean checkColorAlertParams() {
            if (DBG) {
                Log.d(LOG_TAG, "--- checkParams");
            }
            if (mBuilder == null) {
                Log.e(LOG_TAG, "--- builder is null.");
                return false;
            } else if (mColorTitle == null || mColorNames == null
                    || mColorInts == null) {
                Log.e(LOG_TAG, "--- color alert params are null.");
                return false;
            } else if (mColorNames.length != mColorInts.length) {
                Log.e(LOG_TAG, "--- the length of color alert params are "
                        + "different.");
                return false;
            }
            return true;
        }

        private boolean checkSizeAlertParams() {
            if (DBG) {
                Log.d(LOG_TAG, "--- checkParams");
            }
            if (mBuilder == null) {
                Log.e(LOG_TAG, "--- builder is null.");
                return false;
            } else if (mSizeTitle == null || mSizeNames == null
                    || mSizeDisplayInts == null || mSizeSendInts == null) {
                Log.e(LOG_TAG, "--- size alert params are null.");
                return false;
            } else if (mSizeNames.length != mSizeDisplayInts.length
                    && mSizeSendInts.length != mSizeDisplayInts.length) {
                Log.e(LOG_TAG, "--- the length of size alert params are "
                        + "different.");
                return false;
            }
            return true;
        }

        private boolean checkAlignAlertParams() {
            if (DBG) {
                Log.d(LOG_TAG, "--- checkAlignAlertParams");
            }
            if (mBuilder == null) {
                Log.e(LOG_TAG, "--- builder is null.");
                return false;
            } else if (mAlignTitle == null) {
                Log.e(LOG_TAG, "--- align alert params are null.");
                return false;
            }
            return true;
        }

        private void onShowForegroundColorAlertDialog() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onShowForegroundColorAlertDialog");
            }
            if (!checkColorAlertParams()) {
                return;
            }
            mBuilder.setTitle(mColorTitle);
            mBuilder.setIcon(0);
            mBuilder.
            setItems(mColorNames,
                    new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    Log.d("EETVM", "mBuilder.onclick:" + which);
                    int color = Integer.parseInt(
                            (String) mColorInts[which], 16) - 0x01000000;
                    mEST.setItemColor(color);
                }
            });
            mBuilder.show();
        }

        private void onShowBackgroundColorAlertDialog() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onShowBackgroundColorAlertDialog");
            }
            if (!checkColorAlertParams()) {
                return;
            }
            mBuilder.setTitle(mColorTitle);
            mBuilder.setIcon(0);
            mBuilder.
            setItems(mColorNames,
                    new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    Log.d("EETVM", "mBuilder.onclick:" + which);
                    int color = Integer.parseInt(
                            (String) mColorInts[which], 16) - 0x01000000;
                    mEST.setBackgroundColor(color);
                }
            });
            mBuilder.show();
        }

        private void onShowSizeAlertDialog() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onShowSizeAlertDialog");
            }
            if (!checkSizeAlertParams()) {
                return;
            }
            mBuilder.setTitle(mSizeTitle);
            mBuilder.setIcon(0);
            mBuilder.
            setItems(mSizeNames,
                    new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    Log.d(LOG_TAG, "mBuilder.onclick:" + which);
                    int size = Integer
                    .parseInt((String) mSizeDisplayInts[which]);
                    mEST.setItemSize(size);
                }
            });
            mBuilder.show();
        }

        private void onShowAlignAlertDialog() {
            if (DBG) {
                Log.d(LOG_TAG, "--- onShowAlignAlertDialog");
            }
            if (!checkAlignAlertParams()) {
                return;
            }
            mBuilder.setTitle(mAlignTitle);
            mBuilder.setIcon(0);
            mBuilder.
            setItems(mAlignNames,
                    new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    Log.d(LOG_TAG, "mBuilder.onclick:" + which);
                    Layout.Alignment align = Layout.Alignment.ALIGN_NORMAL;
                    switch (which) {
                    case 0:
                        align = Layout.Alignment.ALIGN_NORMAL;
                        break;
                    case 1:
                        align = Layout.Alignment.ALIGN_CENTER;
                        break;
                    case 2:
                        align = Layout.Alignment.ALIGN_OPPOSITE;
                        break;
                    default:
                        break;
                    }
                    mEST.setAlignment(align);
                }
            });
            mBuilder.show();
        }
    }

    private class StyledTextArrowKeyMethod extends ArrowKeyMovementMethod {
        EditorManager mManager;
        StyledTextArrowKeyMethod(EditorManager manager) {
            super();
            mManager = manager;
        }

        @Override
        public boolean onKeyDown(TextView widget, Spannable buffer,
                int keyCode, KeyEvent event) {
            if (!mManager.isSoftKeyBlocked()) {
                return super.onKeyDown(widget, buffer, keyCode, event);
            }
            if (executeDown(widget, buffer, keyCode)) {
                return true;
            }
            return false;
        }

        private int getEndPos(TextView widget) {
            int end;
            if (widget.getSelectionStart() == mManager.getSelectionStart()) {
                end = widget.getSelectionEnd();
            } else {
                end = widget.getSelectionStart();
            }
            return end;
        }

        private boolean up(TextView widget, Spannable buffer) {
            if (DBG) {
                Log.d(LOG_TAG, "--- up:");
            }
            Layout layout = widget.getLayout();
            int end = getEndPos(widget);
            int line = layout.getLineForOffset(end);
            if (line > 0) {
                int to;
                if (layout.getParagraphDirection(line) ==
                    layout.getParagraphDirection(line - 1)) {
                    float h = layout.getPrimaryHorizontal(end);
                    to = layout.getOffsetForHorizontal(line - 1, h);
                } else {
                    to = layout.getLineStart(line - 1);
                }
                mManager.setSelectedEndPos(to);
                mManager.onCursorMoved();
                return true;
            }
            return false;
        }

        private boolean down(TextView widget, Spannable buffer) {
            if (DBG) {
                Log.d(LOG_TAG, "--- down:");
            }
            Layout layout = widget.getLayout();
            int end = getEndPos(widget);
            int line = layout.getLineForOffset(end);
            if (line < layout.getLineCount() - 1) {
                int to;
                if (layout.getParagraphDirection(line) ==
                    layout.getParagraphDirection(line + 1)) {
                    float h = layout.getPrimaryHorizontal(end);
                    to = layout.getOffsetForHorizontal(line + 1, h);
                } else {
                    to = layout.getLineStart(line + 1);
                }
                mManager.setSelectedEndPos(to);
                mManager.onCursorMoved();
                return true;
            }
            return false;
        }

        private boolean left(TextView widget, Spannable buffer) {
            if (DBG) {
                Log.d(LOG_TAG, "--- left:");
            }
            Layout layout = widget.getLayout();
            int to = layout.getOffsetToLeftOf(getEndPos(widget));
            mManager.setSelectedEndPos(to);
            mManager.onCursorMoved();
            return true;
        }

        private boolean right(TextView widget, Spannable buffer) {
            if (DBG) {
                Log.d(LOG_TAG, "--- right:");
            }
            Layout layout = widget.getLayout();
            int to = layout.getOffsetToRightOf(getEndPos(widget));
            mManager.setSelectedEndPos(to);
            mManager.onCursorMoved();
            return true;
        }

        private boolean executeDown(TextView widget, Spannable buffer,
                int keyCode) {
            if (DBG) {
                Log.d(LOG_TAG, "--- executeDown: " + keyCode);
            }
            boolean handled = false;

            switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_UP:
                handled |= up(widget, buffer);
                break;
            case KeyEvent.KEYCODE_DPAD_DOWN:
                handled |= down(widget, buffer);
                break;
            case KeyEvent.KEYCODE_DPAD_LEFT:
                handled |= left(widget, buffer);
                break;
            case KeyEvent.KEYCODE_DPAD_RIGHT:
                handled |= right(widget, buffer);
                break;
                case KeyEvent.KEYCODE_DPAD_CENTER:
                    mManager.onFixSelectedItem();
                    handled = true;
                    break;
            }
            return handled;
        }
    }

    public class HorizontalLineSpan extends ImageSpan {
        public HorizontalLineSpan(int color, View view) {
            super(new HorizontalLineDrawable(color, view));
        }
    }
    public class HorizontalLineDrawable extends ShapeDrawable {
        private View mView;
        public HorizontalLineDrawable(int color, View view) {
            super(new RectShape());
            mView = view;
            renewColor(color);
            renewBounds(view);
        }
        @Override
        public void draw(Canvas canvas) {
            if (DBG) {
                Log.d(LOG_TAG, "--- draw:");
            }
            renewColor();
            renewBounds(mView);
            super.draw(canvas);
        }

        private void renewBounds(View view) {
            if (DBG) {
                int width = mView.getBackground().getBounds().width();
                int height = mView.getBackground().getBounds().height();
                Log.d(LOG_TAG, "--- renewBounds:" + width + "," + height);
                Log.d(LOG_TAG, "--- renewBounds:" + mView.getClass());
            }
            int width = mView.getWidth();
            if (width > 20) {
                width -= 20;
            }
            setBounds(0, 0, width, 2);
        }
        private void renewColor(int color) {
            if (DBG) {
                Log.d(LOG_TAG, "--- renewColor:" + color);
            }
            getPaint().setColor(color);
        }
        private void renewColor() {
            if (DBG) {
                Log.d(LOG_TAG, "--- renewColor:");
            }
            if (mView instanceof View) {
                ImageSpan parent = getParentSpan();
                Editable text = ((EditStyledText)mView).getText();
                int start = text.getSpanStart(parent);
                ForegroundColorSpan[] spans = text.getSpans(start, start, ForegroundColorSpan.class);
                if (spans.length > 0) {
                    renewColor(spans[spans.length - 1].getForegroundColor());
                }
            }
        }
        private ImageSpan getParentSpan() {
            if (DBG) {
                Log.d(LOG_TAG, "--- getParentSpan:");
            }
            if (mView instanceof EditStyledText) {
                Editable text = ((EditStyledText)mView).getText();
                ImageSpan[] images = text.getSpans(0, text.length(), ImageSpan.class);
                if (images.length > 0) {
                    for (ImageSpan image: images) {
                        if (image.getDrawable() == this) {
                            return image;
                        }
                    }
                }
            }
            Log.e(LOG_TAG, "---renewBounds: Couldn't find");
            return null;
        }
    }
}
