package jp.sourceforge.nicoro.nicoscript;

import android.text.TextUtils;

import java.lang.ref.WeakReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.sourceforge.nicoro.Log;
import static jp.sourceforge.nicoro.Log.LOG_TAG;
import jp.sourceforge.nicoro.MessageChatController;
import jp.sourceforge.nicoro.MessageChatFork;
import jp.sourceforge.nicoro.Release;

/**
 * ニコスクリプト基底クラス<br>
 * Commandパターン的
 */
public abstract class NicoScript {
    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & false;

    private static final String PATTERN_TIME = "([0-9０-９]+)[:：]([0-9０-９]+)";

    private static final char COMMAND_START_0 = '＠';
    private static final char COMMAND_START_1 = '@';
    private static final char COMMAND_SPLIT_0 = ' ';
    private static final char COMMAND_SPLIT_1 = '　';
    private static final char COMMAND_BRACKET_BEGIN_0 = '｢';
    private static final char COMMAND_BRACKET_BEGIN_1 = '「';
    private static final char COMMAND_BRACKET_END_0 = '｣';
    private static final char COMMAND_BRACKET_END_1 = '」';
    protected static final char COMMAND_NUMBER_0 = '＃';
    protected static final char COMMAND_NUMBER_1 = '#';
    protected static final char COMMAND_MAIN_SEPARATOR_0 = '：';
    protected static final char COMMAND_MAIN_SEPARATOR_1 = ':';
    private static WeakReference<CommandSplitter> sRefCommandSplitter =
        new WeakReference<NicoScript.CommandSplitter>(null);

    /** 時間未設定の場合の大概のコマンドでのコメント有効時間（秒数） */
    protected static final int UNDEFINED_DEFAULT_TIME = 30;

    protected static final String UNDEFINED_DEFAULT_JUMP_MESSAGE = "移動します";

    /** 素となる投稿者コメント */
    protected MessageChatFork mChat;

    protected NicoScript(MessageChatFork chat) {
        mChat = chat;
    }

    /**
     * @see jp.sourceforge.nicoro.MessageChat#prepareRemove
     * @param controller
     */
    public void onPrepareRemove(MessageChatController controller) {
        // デフォルトは何もしない
    }
    /**
     * 投稿者コメントが最初に描画されるタイミングに１回だけ呼ばれる
     * @see jp.sourceforge.nicoro.MessageChat#draw
     * @param controller
     */
    public void onDraw(MessageChatController controller) {
        // デフォルトは何もしない
    }

    /**
     * 「12:34」のような形式の時間表記を秒数に変換して取得
     * @param text
     * @return 秒数。変換失敗時は-1
     */
    protected static int getTimeSecond(String text) {
        Matcher matcher = Pattern.compile(PATTERN_TIME).matcher(text);
        if (matcher.find()) {
            try {
                int min = getInt(matcher.group(1));
                int sec = getInt(matcher.group(2));
                return min * 60 + sec;
            } catch (NumberFormatException e) {
                Log.e(LOG_TAG, e.toString(), e);
            }
        }
        return -1;
    }

    protected static int getInt(String text) throws NumberFormatException {
        final int length = text.length();
        StringBuilder builder = new StringBuilder(length);
        for (int i = 0; i < length; ++i) {
            char ch = text.charAt(i);
            if (ch >= '0' && ch <= '9') {
                builder.append(ch);
            } else if (ch >= '０' && ch <= '９') {
                builder.append(ch - '０' + '0');
            } else {
                // 数字でない
                throw new NumberFormatException("unable to parse '" + text + "' as integer");
            }
        }
        return Integer.parseInt(builder.toString(), 10);
    }

    /**
     *
     * @param text 素となる投稿者コメントのchat要素のテキスト
     * @param chat 素となる投稿者コメント
     * @return パラメータから作成したNicoScriptオブジェクト
     */
    public static NicoScript createNicoScript(String text, MessageChatFork chat) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append("createNicoScript: text=")
                    .append(text).toString());
        }
        if (TextUtils.isEmpty(text)) {
            return null;
        }
        char text0 = text.charAt(0);
        if (text0 != COMMAND_START_0 && text0 != COMMAND_START_1) {
            return null;
        }

        CommandSplitter commandSplitter = getCommandSplitter(text, 1);
        String commandMain = commandSplitter.getNextCommand();
        if (TextUtils.isEmpty(commandMain)) {
            return null;
        }

        if (NicoScriptDefault.COMMAND.equals(commandMain)) {
            return new NicoScriptDefault(chat);
        } else if (NicoScriptGyaku.COMMAND.equals(commandMain)) {
            return new NicoScriptGyaku(chat, commandSplitter);
        } else if (NicoScriptForbidSeek.COMMAND.equals(commandMain)) {
            return new NicoScriptForbidSeek(chat);
        } else if (NicoScriptReplace.COMMAND.equals(commandMain)) {
            return new NicoScriptReplace(chat, commandSplitter);
        } else if (NicoScriptJump.COMMAND.equals(commandMain)) {
            return new NicoScriptJump(chat, commandSplitter);
        } else if (commandMain.startsWith(NicoScriptJumpMarker.COMMAND_PREFIX)) {
            return new NicoScriptJumpMarker(chat, commandMain);
        } else if (NicoScriptForbidComment.COMMAND.equals(commandMain)) {
            return new NicoScriptForbidComment(chat);
        } else if (NicoScriptKeywordJump.COMMAND.equals(commandMain)) {
            return new NicoScriptKeywordJump(chat, commandSplitter);
        } else if (commandMain.startsWith(NicoScriptVote.COMMAND_PREFIX)) {
            return new NicoScriptVote(chat, commandMain, commandSplitter);
        } else if (NicoScriptScoreView.COMMAND.equals(commandMain)) {
            // TODO 引数の変更
            return new NicoScriptScoreView(chat);
        } else if (NicoScriptScoreJump.COMMAND.equals(commandMain)) {
            // TODO 引数の変更
            return new NicoScriptScoreJump(chat);
        } else if (commandMain.startsWith(NicoScriptScore.COMMAND_PREFIX)) {
            return new NicoScriptScore(chat, commandMain, commandSplitter);
        } else if (commandMain.startsWith(NicoScriptBall.COMMAND_PREFIX)) {
            return new NicoScriptBall(chat, commandMain, commandSplitter);
        } else if (commandMain.startsWith(NicoScriptWindow.COMMAND_PREFIX)) {
            return new NicoScriptWindow(chat, commandMain, commandSplitter,
                    NicoScriptWindow.WINDOW_SIZE_NORMAL);
        } else if (commandMain.startsWith(NicoScriptWindow.COMMAND_SMALL_PREFIX)) {
            return new NicoScriptWindow(chat, commandMain, commandSplitter,
                    NicoScriptWindow.WINDOW_SIZE_SMALL);
        } else if (commandMain.startsWith(NicoScriptWindow.COMMAND_BIG_PREFIX)) {
            return new NicoScriptWindow(chat, commandMain, commandSplitter,
                    NicoScriptWindow.WINDOW_SIZE_BIG);
        } else if (NicoScriptDoor.COMMAND.equals(commandMain)) {
            return new NicoScriptDoor(chat, commandSplitter);
        }

        return null;
    }

    private static CommandSplitter getCommandSplitter(String text, int start) {
        CommandSplitter cs = sRefCommandSplitter.get();
        if (cs == null) {
            cs = new CommandSplitter(text, start);
            sRefCommandSplitter = new WeakReference<NicoScript.CommandSplitter>(cs);
        } else {
            cs.reset(text, start);
        }
        return cs;
    }

    protected static class CommandSplitter {
        private String mText;
        private int mStart;

        public CommandSplitter() {
            mText = null;
            mStart = 0;
        }
        public CommandSplitter(String t) {
            mText = t;
            mStart = 0;
        }
        public CommandSplitter(String t, int s) {
            mText = t;
            mStart = s;
        }

        public void reset(String t) {
            mText = t;
            mStart = 0;
        }
        public void reset(String t, int s) {
            mText = t;
            mStart = s;
        }

        /**
         * 次のコマンドを取得
         * @return コマンド文字列。区切り文字（スペース）が連続している場合は空文字列が返る場合あり
         */
        public String getNextCommand() {
            final int length = mText.length();
            if (mStart == length) {
                return null;
            }

            int start = mStart;
            int end = findEnd(COMMAND_SPLIT_0, COMMAND_SPLIT_1);
            if (end < 0) {
                end = length;
                mStart = end;
            } else {
                mStart = end + 1;
            }
            assert end >= start;
            return mText.substring(start, end);
        }

        /**
         * 次のコマンドを取得。フレーズ（｢｣で囲まれた文字列）の場合は｢｣を抜いた文字列を取得
         * @return コマンド文字列。区切り文字（スペース）が連続している場合は空文字列が返る場合あり
         */
        public String getNextCommandPhraze() {
            final int length = mText.length();
            if (mStart == length) {
                return null;
            }

            char ch = mText.charAt(mStart);
            if (ch != COMMAND_BRACKET_BEGIN_0 && ch != COMMAND_BRACKET_BEGIN_1) {
                return getNextCommand();
            }

            int start = mStart + 1;
            int end = findEnd(COMMAND_BRACKET_END_0, COMMAND_BRACKET_END_1);
            if (end < 0) {
                end = length;
                mStart = end;
            } else {
                mStart = end + 1;
                if (mStart < length) {
                    ch = mText.charAt(mStart);
                    if (ch == COMMAND_SPLIT_0 || ch == COMMAND_SPLIT_1) {
                        ++mStart;
                    }
                }
            }

            assert end >= start;
            return mText.substring(start, end);
        }

        public boolean isEnd() {
            final int length = mText.length();
            return (mStart == length);
        }

        private int findEnd(int findChar0, int findChar1) {
            final int end0 = mText.indexOf(findChar0, mStart);
            final int end1 = mText.indexOf(findChar1, mStart);
            int end;
            if (end0 >= 0) {
                if (end1 >= 0) {
                    if (end0 < end1) {
                        end = end0;
                    } else {
                        end = end1;
                    }
                } else {
                    end = end0;
                }
            } else {
                end = end1;
            }
            return end;
        }
    }

    protected static abstract class Keyword {
        /**
         * 一致条件
         * <ul>
         * <li>true: キーワードとコメントが完全に一致
         * <li>false: キーワードがコメントに部分的に一致
         */
        protected boolean mAgreeEntirely;

        protected Keyword() {
            mAgreeEntirely = false;
        }

        public boolean setAgreeEntirely(String cmd) {
            if (cmd.startsWith("部分一致")) {
                mAgreeEntirely = false;
                return true;
            } else if (cmd.startsWith("完全一致")) {
                mAgreeEntirely = true;
                return true;
            } else {
                // コマンドでなければ無視
                return false;
            }
        }

        public abstract boolean match(String text);
    }

    /**
     * キーワード判定を持つニコスクリプト用実装
     */
    protected static class SingleKeyword extends Keyword {
        /** コメントと比較するキーワード */
        String mKeyword;

        SingleKeyword() {
            mKeyword = null;
        }

        public void setKeyword(String keyword) {
            if (TextUtils.isEmpty(keyword)) {
                mKeyword = null;
            } else {
                mKeyword = keyword;
            }
        }

        @Override
        public boolean match(String text) {
            if (text == null || mKeyword == null) {
                return false;
            }
            if (mAgreeEntirely) {
                return text.equals(mKeyword);
            } else {
                return (text.indexOf(mKeyword) >= 0);
            }
        }
    }

    /**
     * 複数のキーワード判定を持つニコスクリプト用実装
     */
    protected static class MultiKeyword extends Keyword {
        /** コメントと比較するキーワード群 */
        String[] mKeyword;

        MultiKeyword() {
            mKeyword = null;
        }

        public void setKeyword(String keyword) {
            if (TextUtils.isEmpty(keyword)) {
                mKeyword = null;
            } else {
                mKeyword = keyword.split("[,、]");
            }
        }

        @Override
        public boolean match(String text) {
            if (text == null || mKeyword == null || mKeyword.length == 0) {
                return false;
            }
            if (mAgreeEntirely) {
                for (String keyword : mKeyword) {
                    if (text.equals(keyword)) {
                        return true;
                    }
                }
                return false;
            } else {
                for (String keyword : mKeyword) {
                    if (text.indexOf(keyword) >= 0) {
                        return true;
                    }
                }
                return false;
            }
        }
    }

    /**
     * ラベル（＠*：ラベルの形式）を持つニコスクリプト用実装
     */
    protected static class Label {
        String mLabel = null;

        public boolean setLabel(String commandMain) {
            mLabel = null;
            int index = commandMain.indexOf(COMMAND_MAIN_SEPARATOR_0);
            if (index < 0) {
                index = commandMain.indexOf(COMMAND_MAIN_SEPARATOR_1);
                if (index < 0) {
                    return false;
                }
            }
            mLabel = commandMain.substring(index + 1);
            return true;
        }

        public String getLabel() {
            return mLabel;
        }
    }

    public interface Jump {
        String getJumpToVideoNumber();
        int getJumpToSecond();
        String getJumpToLabel();
        String getJumpMessage();
        int getJumpStartTimeSecond();
        int getReturnTimeSecond();
        String getReturnMessage();
        boolean hasJumpMessage();
    }

    public interface NicosOrLocal {
        boolean isSaveLocal();
        boolean match(String text);
    }

    public interface GetStyle {
        int getColor();
        int getFontSize();
        int getFontSizeType();
        int getPos();
        int getLineHeight();
    }

    public interface GetStyleSide extends GetStyle {
        int getPosSide();
    }
}
