package com.jsdroid.editor;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.text.Layout;
import android.text.style.TypefaceSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;

public class ColorsText extends EditText {
    private Object colorLock = new Object();
    // 代码高亮颜色数组
    private int[] codeColors;
    // 滑动组件
    private View scrollView;
    // 最大行号:根据\n数量得到
    private int mMaxLineNumber;
    // 行号内边距
    private int mNumberPadding;
    private int mTextPadding;
    private int mLineNumberBgStrokeWidth;
    private int defaultTextColor = 0xffffffff;
    private int lineNumberColor = 0x99ffffff;
    private int lineNumberBackgroundColor = 0x99ffffff;
    private int lineNumberSplitColor = 0x99ffffff;
    private int lineNumberSplitWidth = 1;
    private int cursorColor = 0xffffffff;
    private int selectBackgroundColor = 0x33ffffff;

    public void setLineNumberColor(int lineNumberColor) {
        this.lineNumberColor = lineNumberColor;
    }

    public void setLineNumberBackgroundColor(int lineNumberBackgroundColor) {
        this.lineNumberBackgroundColor = lineNumberBackgroundColor;
    }

    public void setLineNumberSplitColor(int lineNumberSplitColor) {
        this.lineNumberSplitColor = lineNumberSplitColor;
    }

    public void setSelectBackgroundColor(int selectBackgroundColor) {
        this.selectBackgroundColor = selectBackgroundColor;
    }


    public void setDefaultTextColor(int defaultTextColor) {
        this.defaultTextColor = defaultTextColor;
    }

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

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

    // 设置光标颜色
    public void setCursorColor(int cursorColor) {
        this.cursorColor = cursorColor;
    }

    private void init() {
        mNumberPadding = DpiUtils.dip2px(getContext(), 5);
        mTextPadding = DpiUtils.dip2px(getContext(), 4);
        mLineNumberBgStrokeWidth = DpiUtils.dip2px(getContext(), 2);
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.WRAP_CONTENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        setLayoutParams(params);
        setGravity(Gravity.START);
        // 设值背景透明
        setBackgroundColor(Color.TRANSPARENT);
        // 设值字体大小
        setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
        // 设置字体颜色(透明是为了兼容不能反射绘制光标以及选择文字背景的情况)
        setTextColor(Color.TRANSPARENT);
        setTypeface(Typeface.MONOSPACE);
        setPadding(0, DpiUtils.dip2px(getContext(), 2), DpiUtils.dip2px(getContext(), 8), DpiUtils.dip2px(getContext(), 48));
    }


    /**
     * 生成每行文字对应的行号,如果行首为换行符则需要显示行号
     *
     * @return
     */
    public Map<Integer, String> getLineNumbers() {
        Map<Integer, String> maps = new HashMap<>();
        Layout layout = getLayout();
        if (layout == null) {
            return maps;
        }
        int lineNumber = 1;
        maps.put(0, Integer.toString(lineNumber));
        int lineCount = getLineCount();
        mMaxLineNumber = 1;
        for (int i = 1; i < lineCount; i++) {
            int charPos = layout.getLineStart(i);
            if (getText().charAt(charPos - 1) == '\n') {
                lineNumber++;
                maps.put(i, Integer.toString(lineNumber));
                mMaxLineNumber = lineNumber;
            }
        }
        return maps;
    }


    /**
     * 获取可视范围的文字首行与尾行
     *
     * @param rect
     * @param ret
     */
    public void getLineRangeForDraw(Rect rect, int[] ret) {
        Layout layout = getLayout();
        final int top = Math.max(rect.top, 0);
        final int bottom = Math.min(layout.getLineTop(layout.getLineCount()), rect.bottom);
        if (top >= bottom) {
            ret[0] = -1;
            ret[1] = -1;
            return;
        }
        ret[0] = layout.getLineForVertical(top);
        ret[1] = layout.getLineForVertical(bottom);
    }


    /**
     * 计算可视范围文字的首字位置
     *
     * @param widths
     * @return [位置,偏移]
     */
    public float[] getLineFirstCharPosForDraw(float[] widths) {
        float max = (getViewScrollX() - getPaddingLeft());
        int count = 0;
        float size = 0;
        for (int i = 0; i < widths.length; i++) {

            if (size + widths[i] >= max) {
                break;
            }
            size += widths[i];
            count++;
        }
        return new float[]{count, size};
    }


    Method getUpdatedHighlightPathMthod;

    /**
     * 获取绘制选择文字背景的Path路径
     *
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    Path getUpdatedHighlightPath() throws NoSuchMethodException,
            InvocationTargetException, IllegalAccessException {
        if (getUpdatedHighlightPathMthod == null) {
            getUpdatedHighlightPathMthod = TextView.class
                    .getDeclaredMethod("getUpdatedHighlightPath");
            getUpdatedHighlightPathMthod.setAccessible(true);
        }
        return (Path) getUpdatedHighlightPathMthod.invoke(this);
    }


    Field mHighlightPaintField;

    /**
     * 获取绘制选择文字背景的Paint画笔
     *
     * @return
     * @throws IllegalAccessException
     * @throws NoSuchFieldException
     */
    Paint getHighlightPaint() throws IllegalAccessException,
            NoSuchFieldException {
        if (mHighlightPaintField == null) {
            mHighlightPaintField = TextView.class
                    .getDeclaredField("mHighlightPaint");
            mHighlightPaintField.setAccessible(true);
        }
        return (Paint) mHighlightPaintField.get(this);
    }

    Field mEditorField;

    /**
     * 获取用于编辑的Editor
     *
     * @return
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    Object getEditor() throws NoSuchFieldException, IllegalAccessException {
        if (mEditorField == null) {
            mEditorField = TextView.class.getDeclaredField("mEditor");
            mEditorField.setAccessible(true);
        }
        return mEditorField.get(this);
    }


    /**
     * 绘制光标以及文字选择背景
     *
     * @param canvas
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws NoSuchFieldException
     */
    void drawCursorAndSelectPath(Canvas canvas) throws NoSuchMethodException,
            IllegalAccessException, InvocationTargetException,
            NoSuchFieldException {

        final int selectionStart = getSelectionStart();
        final int selectionEnd = getSelectionEnd();
        Path highlight = getUpdatedHighlightPath();
        Paint mHighlightPaint = getHighlightPaint();
        //设置选择文字背景颜色
        if (selectBackgroundColor != 0) {
            mHighlightPaint.setColor(selectBackgroundColor);
        }
        canvas.save();

        //getCompoundPaddingLeft获取真正的左内边距,getExtendedPaddingTop获取真正的上外边距
        canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());
        try {

            if (highlight != null) {
                if (selectionEnd == selectionStart) {
                    //绘制光标
                    Paint paint = getPaint();
                    paint.setColor(cursorColor);
                    canvas.drawRect(getCursorRect(), paint);
                } else {
                    //绘制选择文字阴影
                    canvas.drawPath(highlight, mHighlightPaint);
                }
            }

        } finally {
            canvas.restore();
        }

    }

    @Override
    protected void onDraw(Canvas canvas) {
        try {
            // 优化速度绘制光标以及选择文字背景
            drawCursorAndSelectPath(canvas);
        } catch (Exception e) {
            // 反射调用失败,使用默认的方法绘制背景,会绘制文字
            super.onDraw(canvas);
        }
        // 画文字
        canvas.save();
        canvas.translate(0, getExtendedPaddingTop());
        synchronized (colorLock) {
            drawText(canvas);
        }
        canvas.restore();
        // 绘制分割线
        Paint paint = getPaint();
        paint.setStrokeWidth(lineNumberSplitWidth);
        paint.setColor(lineNumberSplitColor);
        canvas.drawLine(getPaddingLeft() - mTextPadding, 0, getPaddingLeft() - mTextPadding, getHeight(), paint);

        // 根据行号计算左边距padding
        String max = Integer.toString(mMaxLineNumber);
        float lineNumberSize = getPaint().measureText(max) + mNumberPadding + mNumberPadding + mTextPadding;

        if (getPaddingLeft() != lineNumberSize) {
            setPadding((int) lineNumberSize, getPaddingTop(), getPaddingRight(), getPaddingBottom());
            invalidate();
        }
    }


    // 获取选择行,如果多余一行,返回-1
    public int getSelectLine() {
        Layout layout = getLayout();
        if (getSelectionStart() != getSelectionEnd()) {
            return -1;
        }
        return layout.getLineForOffset(getSelectionStart());
    }

    // 绘制文本着色
    private void drawText(Canvas canvas) {
        Map<Integer, String> lineNumbles = getLineNumbers();
        Layout layout = getLayout();
        int selectLine = getSelectLine();
        int range[] = new int[4];
        {// 计算需要绘制的行号所需要的范围

            int clipLeft = 0;
            int clipTop = (getScrollView().getScrollY() == 0) ? 0
                    : getExtendedPaddingTop() + getScrollView().getScrollY()
                    - getScrollView().getPaddingTop();
            int clipRight = getWidth();
            int clipBottom = clipTop + getScrollView().getHeight();
            Rect rect = new Rect(clipLeft, clipTop, clipRight, clipBottom);
            getLineRangeForDraw(rect, range);
        }
        int firstLine = range[0];
        int lastLine = range[1];
        if (firstLine < 0) {
            return;
        }
        int previousLineBottom = layout.getLineTop(firstLine);
        int previousLineEnd = layout.getLineStart(firstLine);
        int lineCount = getLineCount();
        Paint paint = getPaint();


        for (int lineNum = firstLine; lineNum <= lastLine
                && lineNum < lineCount; lineNum++) {
            int start = previousLineEnd;

            previousLineEnd = layout.getLineStart(lineNum + 1);

            int end = layout.getLineVisibleEnd(lineNum);
            int ltop = previousLineBottom;
            int lbottom = layout.getLineTop(lineNum + 1);
            previousLineBottom = lbottom;
            int lbaseline = lbottom - layout.getLineDescent(lineNum);
            int left = getPaddingLeft();
            // 绘制选择行背景
            if (lineNum == selectLine) {
                paint.setColor(lineNumberBackgroundColor);
                paint.setStyle(Paint.Style.STROKE);
                paint.setStrokeWidth(mLineNumberBgStrokeWidth);
                canvas.drawRect(getPaddingLeft() - mLineNumberBgStrokeWidth, ltop, getRight() - getPaddingRight() + mLineNumberBgStrokeWidth, lbottom, paint);
                paint.setStyle(Paint.Style.FILL);
            }
            // 绘制行号
            String lineNumberText = lineNumbles.get(lineNum);
            if (lineNumberText != null) {
                paint.setColor(lineNumberColor);
                canvas.drawText(lineNumberText, 0, lineNumberText.length(), mNumberPadding,
                        lbaseline, paint);
            }
            int textLength = getText().length();
            // 绘制文字
            if (start < textLength) {
                //计算需要绘制的文字位置
                //获取改行所有文字宽度
                float[] widths = new float[end - start + 1];
                paint.getTextWidths(getText(), start, end, widths);
                //计算获取看到的文字第一个位置,和对应的偏移x
                float firstNeedDrawPos[] = getLineFirstCharPosForDraw(widths);
                int firstPos = (int) firstNeedDrawPos[0];
                float offsetX = firstNeedDrawPos[1];
                int maxOffX = getViewScrollX() + getVisibleWidth();
                // 文字着色
                for (int i = start + firstPos; i < end && i < textLength; ) {
                    if (offsetX > maxOffX) {
                        break;
                    }
                    int color = getCodeColor(i);
                    {
                        float fontWidths = widths[i - start];
                        int fontCount = 1;
                        for (int j = i + 1; j < end && j < textLength; j++) {
                            if (color == getCodeColor(j)) {
                                fontCount++;
                                fontWidths += widths[j - start];
                            } else {
                                break;
                            }
                        }
                        if (color == 0) {
                            color = defaultTextColor;
                        }

                        paint.setColor(color);
                        canvas.drawText(getText(), i, i + fontCount, left + offsetX, lbaseline, paint);
                        i += fontCount;
                        offsetX += fontWidths;

                    }
                }
            }
        }

    }

    private int getCodeColor(int i) {
        if (codeColors != null && i < codeColors.length) {
            return codeColors[i];
        }
        return 0;
    }

    public int[] getCodeColors() {
        int textLength = getText().length();
        if (codeColors == null) {
            codeColors = new int[textLength];
        }
        //如果文字长度小于颜色长度,生成新的颜色数组
        if (textLength >= codeColors.length) {
            int newColors[] = new int[textLength + 500];
            for (int i = 0; i < codeColors.length; i++) {
                newColors[i] = codeColors[i];
            }
            codeColors = newColors;
        }
        return codeColors;
    }

    public int getViewScrollX() {
        return getScrollView().getScrollX();
    }

    public int getViewScrollY() {
        return getScrollView().getScrollY();
    }

    public View getScrollView() {
        return scrollView == null ? this : scrollView;
    }

    public void setScrollView(View scrollView) {
        this.scrollView = scrollView;
    }

    public int getVisibleWidth() {
        return Math.min(getWidth(), getScrollView().getWidth());
    }

    public int getVisibleHeight() {
        return Math.min(getHeight(), getScrollView().getHeight());
    }


    Rect getCursorRect() {
        Layout layout = getLayout();
        final int offset = getSelectionStart();
        final int line = layout.getLineForOffset(offset);
        final int top = layout.getLineTop(line);
        final int bottom = layout.getLineTop(line + 1);
        float horizontal = layout.getSecondaryHorizontal(offset);
//        horizontal = Math.max(0.5f, horizontal - 0.5f);
        int left = (int) (horizontal);
        return new Rect(left, top, left + DpiUtils.dip2px(getContext(), 1), bottom);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_UP:

                if (event.getY() > getHeight() - getPaddingBottom()) {
                    int len = getText().length();
                    if (len > 0 && getText().charAt(len - 1) != '\n') {

                    }
                    append("\n");

                }
                break;
        }
        return super.onTouchEvent(event);
    }

}