package com.test.testapplication;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.RequiresApi;
import android.support.annotation.StringRes;
import android.text.*;
import android.text.method.MovementMethod;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;

/**
 * A TextView that only serves StaticLayout.
 * Not allowing attributes are as follows.
 *  left drawable
 *  maxWidth
 *  SpannableString
 *  ellipsize(truncate)
 */
public class LabelView extends View {

    private CharSequence mText = null;

    private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();

    private static final int ALIGN_START = 0;
    private static final int ALIGN_END = 1;
    private static final int ALIGN_CENTER = 2;

    private static final int BOLD = 1;

    private TextPaint mTextPaint = null;
    private Layout mLayout = null;
    private BoringLayout.Metrics mBoring = null;
    private BoringLayout mSavedLayout = null;

    private Layout.Alignment mAlignment;

    //    private ColorStateList mTextColor;
    private float mSpacingMult = 1.0f;
    private int mCurTextColor;
    private float mCurTextSize = 0;
    private int mCurWidth = 0;
    private int mCurHeight = 0;
//    private boolean mTextChanged = false;
//    private boolean mTextSizeChanged = false;

    private MovementMethod mMovement;
//    private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();

//    private int lastSelStart = 0;
//    private int lastSelEnd= 0;

//    private Stopwatch onMeasureStopwatch = Stopwatch.createUnstarted();
//    private Stopwatch onLayoutStopwatch = Stopwatch.createUnstarted();
//    private Stopwatch onMeasureTotalStopwatch = Stopwatch.createUnstarted();
//    private Stopwatch onLayoutTotalStopwatch = Stopwatch.createUnstarted();
//
//    private int onMeasureCount = 0;
//    private int onLayoutCount = 0;

    public LabelView(Context context) {
        super(context);
    }

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

    public LabelView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public LabelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs);
    }

    public void init(Context context, AttributeSet attrs){
        final Resources res = getResources();

        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.density = res.getDisplayMetrics().density;

        TypedValue typedValue = new TypedValue();
        final Resources.Theme theme = context.getTheme();

        {
            TypedArray a = theme.obtainStyledAttributes(typedValue.data, new int[]{R.attr.colorAccent});
            int color = a.getColor(0, 0);
            a.recycle();
            mTextPaint.linkColor = color;
        }

        int defaultTextColor;

        {
            TypedArray a = theme.obtainStyledAttributes(typedValue.data, new int[]{R.attr.editTextColor});
            int color = a.getColor(0, 0);
            a.recycle();
            defaultTextColor = color;
        }

        {
            TypedArray a = theme.obtainStyledAttributes(
                    attrs, R.styleable.Label, 0, 0);
            mText = a.getText(R.styleable.Label_text);
            mSpacingMult = a.getFloat(R.styleable.Label_lineSpacingMultiplier, mSpacingMult);

            int alignment = a.getInt(R.styleable.Label_gravity, 0);

            switch (alignment){
                case ALIGN_END:{
                    mAlignment = Layout.Alignment.ALIGN_OPPOSITE;
                    break;
                }
                case ALIGN_CENTER:{
                    mAlignment = Layout.Alignment.ALIGN_CENTER;
                    break;
                }
                default:{
                    mAlignment = Layout.Alignment.ALIGN_NORMAL;
                    break;
                }
            }

            float textSize = a.getDimensionPixelSize( R.styleable.Label_textSize, 0);

            if( textSize == 0 ){
                setTextSize(15);
            }
            else{
                mTextPaint.setTextSize( textSize );
            }

            int textStyle = a.getInt(R.styleable.Label_textStyle, 0);

            if( textStyle == BOLD ){
                mTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
            }

            mCurTextColor = a.getColor( R.styleable.Label_textColor, defaultTextColor );
            mTextPaint.setColor(mCurTextColor);
            a.recycle();
        }

        if( mText == null ){
            mText = "";
        }

        // To calculate width && height
//        mTextChanged = true;
//        mTextSizeChanged = true;

//        mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
    }

//    public int getThemeAccentColor () {
//        final TypedValue value = new TypedValue ();
//        getContext().getTheme().resolveAttribute (R.attr.colorAccent, value, true);
//        return value.data;
//    }

    public void setText(CharSequence text){
        if( text == null ) text = "";

//        mTextChanged = mTextSizeChanged = mText == null || !text.equals(mText) ;

//        if( text instanceof Spannable ) {
//            mText = mSpannableFactory.newSpannable(text);
//        }
//        else{
        mText = text;
//        }

        if (mLayout != null) {
//            nullLayouts();
//            requestLayout();
//            invalidate();
            checkForRelayout();
        }
    }

    public final void setText(@StringRes int resid) {
        setText(getContext().getResources().getText(resid));
    }

//    public void setTypeface(Typeface typeFace){
//        if( mTextPaint.getTypeface() != typeFace ){
//            mTextPaint.setTypeface(typeFace);
//
//            if (mLayout != null) {
//                requestLayout();
//                invalidate();
//            }
//        }
//    }

    public void setTextSize(float size){
        if( mCurTextSize == size ) return;


        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
        mCurTextSize = size;
//        mTextSizeChanged = true;
    }

    public void setTextSize(int unit, float size) {
        Context c = getContext();
        Resources r;

        if (c == null)
            r = Resources.getSystem();
        else
            r = c.getResources();

        setRawTextSize(TypedValue.applyDimension(
                unit, size, r.getDisplayMetrics()));
    }

    private void setRawTextSize(float size) {
        if (size != mTextPaint.getTextSize()) {
            mTextPaint.setTextSize(size);

            if (mLayout != null) {
                nullLayouts();
                requestLayout();
                invalidate();
            }
        }
    }

    public void setTextColor(@ColorInt int color) {
        if (color != mCurTextColor) {
            mCurTextColor = color;
            mTextPaint.setColor(mCurTextColor);
            invalidate();
        }
    }

    public final void setMovementMethod(MovementMethod movement) {
        if (mMovement != movement) {
            mMovement = movement;

            if (movement != null && !(mText instanceof Spannable)) {
                setText(mText);
            }

            fixFocusableAndClickableSettings();

//            // SelectionModifierCursorController depends on textCanBeSelected, which depends on
//            // mMovement
//            if (mEditor != null) mEditor.prepareCursorControllers();
        }
    }

    private void fixFocusableAndClickableSettings() {
//        if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
        if (mMovement != null) {
            setFocusable(true);
            setClickable(true);
            setLongClickable(true);
        } else {
            setFocusable(false);
            setClickable(false);
            setLongClickable(false);
        }
    }

    /**
     * Check whether entirely new text requires a new view layout
     * or merely a new text layout.
     */
    private void checkForRelayout() {
        // If we have a fixed width, we can just swap in a new text layout
        // if the text height stays the same or if the view height is fixed.

        ViewGroup.LayoutParams layoutParams = getLayoutParams();

        if ( (layoutParams != null && layoutParams.width != ViewGroup.LayoutParams.WRAP_CONTENT ) &&
                (getRight() - getLeft() - getPaddingLeft() - getPaddingRight() > 0) ) {
            // Static width, so try making a new text layout.

            int oldht = mLayout.getHeight();
            int want = mLayout.getWidth();

            /*
             * No need to bring the text into view, since the size is not
             * changing (unless we do the requestLayout(), in which case it
             * will happen at measure).
             */
            makeNewLayout(want, UNKNOWN_BORING);

            // In a fixed-height view, so use our new text layout.
            if (layoutParams.height != ViewGroup.LayoutParams.WRAP_CONTENT &&
                    layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
                invalidate();
                return;
            }

            // Dynamic height, but height has stayed the same,
            // so use our new text layout.
            if (mLayout.getHeight() == oldht) {
                invalidate();
                return;
            }

            // We lose: the height has changed and we have a dynamic height.
            // Request a new view layout using our new text layout.
            requestLayout();
            invalidate();
        } else {
            // Dynamic width, so we have no choice but to request a new
            // view layout with a new text layout.
            nullLayouts();
            requestLayout();
            invalidate();
        }
    }

    /**
     * set mLayout to null
     */
    private void nullLayouts(){
        if( mLayout instanceof BoringLayout ) {
            mSavedLayout = (BoringLayout) mLayout;
        }
        mLayout = null;
        mBoring = null;
    }

    private int desired(Layout layout) {
        int n = layout.getLineCount();
        CharSequence text = layout.getText();
        float max = 0;

        // if any line was wrapped, we can't use it.
        // but it's ok for the last line not to have a newline

        for (int i = 0; i < n - 1; i++) {
            if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
                return -1;
        }

        for (int i = 0; i < n; i++) {
            max = Math.max(max, layout.getLineWidth(i));
        }

        return (int) Math.ceil(max);
    }

    private int getDesiredHeight(Layout layout, boolean cap) {
        if (layout == null) {
            return 0;
        }

        int linecount = layout.getLineCount();
        int pad = getPaddingTop() + getPaddingBottom();
        int desired = layout.getLineTop(linecount);

        desired += pad;

        // TODO: Add lines limits calculating
//        if (mMaxMode == LINES) {
//            /*
//             * Don't cap the hint to a certain number of lines.
//             * (Do cap it, though, if we have a maximum pixel height.)
//             */
//            if (cap) {
//                if (linecount > mMaximum) {
//                    desired = layout.getLineTop(mMaximum);
//
//                    if (dr != null) {
//                        desired = Math.max(desired, dr.mDrawableHeightLeft);
//                        desired = Math.max(desired, dr.mDrawableHeightRight);
//                    }
//
//                    desired += pad;
//                    linecount = mMaximum;
//                }
//            }
//        } else {
//            desired = Math.min(desired, mMaximum);
//        }
//
//        if (mMinMode == LINES) {
//            if (linecount < mMinimum) {
//                desired += getLineHeight() * (mMinimum - linecount);
//            }
//        } else {
//            desired = Math.max(desired, mMinimum);
//        }

        // Check against our minimum height
        desired = Math.max(desired, getSuggestedMinimumHeight());

        return desired;
    }

//    private Layout makeNewLayout(int width, BoringLayout.Metrics metrics){
////        Layout layout;
//        BoringLayout.Metrics boring = metrics == UNKNOWN_BORING ? BoringLayout.isBoring(mText, mTextPaint, metrics) : metrics;
//
//        if( boring == null || boring.width > width ) {
//            mLayout = new StaticLayout(mText, mTextPaint, width,
//                    Layout.Alignment.ALIGN_NORMAL,
//                    mSpacingMult, 0f, true);
//            mSavedLayout = null;
//        }
//        else{
//            mLayout = BoringLayout.make(mText, mTextPaint, width,
//                    Layout.Alignment.ALIGN_NORMAL, mSpacingMult, 0f, boring, true);
//        }
//
//        return mLayout;
//    }

    /**
     * The width passed in is now the desired layout width,
     * not the full view width with padding.
     * Not support ellipsize, so we just need these two parameters.
     * {@hide}
     */
    protected void makeNewLayout(int wantWidth, BoringLayout.Metrics boring) {

        if (wantWidth < 0) {
            wantWidth = 0;
        }

        mLayout = makeSingleLayout(wantWidth, boring, true);
    }

    /**
     * @hide
     */
    protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, boolean useSaved) {
        Layout result = null;

        if (boring == UNKNOWN_BORING) {
            boring = BoringLayout.isBoring(mText, mTextPaint, boring);
            if (boring != null) {
                mBoring = boring;
            }
        }

        // 檢查後符合 BoringLayout 的規範
        if (boring != null) {
            // 計算後的寬度小於希望的寬度 表示不需換行
            if (boring.width <= wantWidth ) {
                if (useSaved && mSavedLayout != null) {
                    result = mSavedLayout.replaceOrMake(mText, mTextPaint, wantWidth,
                            mAlignment, mSpacingMult, 0f, boring, true);
                } else {
                    result = mLayout = BoringLayout.make(mText, mTextPaint, wantWidth,
                            mAlignment, mSpacingMult, 0f, boring, true);
                }

                if (useSaved) {
                    mSavedLayout = (BoringLayout) result;
                }
            }
        }

        // 可能不符合 BoringLayout 的規範 或是需要換行
        if (result == null) {
            result = new StaticLayout(mText, mTextPaint, wantWidth,
                    mAlignment,
                    mSpacingMult, 0f, true);
            mSavedLayout = null;
        }

        return result;
    }

//    @Override
//    protected void onSelectionChanged(int selStart, int selEnd) {
//        if (selStart == -1 || selEnd == -1) {
//            // @hack : https://code.google.com/p/android/issues/detail?id=137509
//            CharSequence text = getText();
//            if (text instanceof Spannable) {
//                Selection.setSelection((Spannable) text, lastSelStart, lastSelEnd);
//            }
//        } else {
//            lastSelStart = selStart;
//            lastSelEnd = selEnd;
//            super.onSelectionChanged(selStart, selEnd);
//        }
//    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        onMeasureTotalStopwatch.start();
//        onMeasureStopwatch.reset().start();

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;

        BoringLayout.Metrics boring = UNKNOWN_BORING;

        int des = -1;
        boolean fromexisting = false;

        if (widthMode == MeasureSpec.EXACTLY) {
            // Parent has told us how big to be. So be it.
            width = widthSize;
        } else {
            if (mLayout != null ) {
                des = desired(mLayout);
            }

            if (des < 0) {
                boring = BoringLayout.isBoring(mText, mTextPaint, mBoring);
                if (boring != null) {
                    mBoring = boring;
                }
            } else {
                fromexisting = true;
            }

            if (boring == null || boring == UNKNOWN_BORING) {
                if (des < 0) {
                    des = (int) Math.ceil(Layout.getDesiredWidth(mText, mTextPaint));
                }
                width = des;
            } else {
                width = boring.width;
            }

            width += getPaddingLeft() + getPaddingRight();

            // Check against our minimum width
            width = Math.max(width, getSuggestedMinimumWidth());

            if (widthMode == MeasureSpec.AT_MOST) {
                width = Math.min(widthSize, width);
            }
        }

        int want = width - getPaddingLeft() - getPaddingRight();
//        int unpaddedWidth = want;

        if (mLayout == null) {
            makeNewLayout(want, boring);
        } else {
            final boolean layoutChanged = (mLayout.getWidth() != want) ||
                    (mLayout.getWidth() !=
                            width - getPaddingLeft() - getPaddingRight());

            final boolean widthChanged =
                    (want > mLayout.getWidth()) &&
                    (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));

            if (layoutChanged ) {
                if (widthChanged) {
                    mLayout.increaseWidthTo(want);
                } else {
                    makeNewLayout(want, boring);
                }
            } else {
                // Nothing has changed
            }
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            // Parent has told us how big to be. So be it.
            height = heightSize;
        } else {
            int desired = getDesiredHeight(mLayout, true);

            height = desired;
//            mDesiredHeightAtMeasure = desired;

            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(desired, heightSize);
            }
        }

//        int unpaddedHeight = height - getPaddingTop() - getPaddingBottom();
        // TODO: Add lines limits calculating
//        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
//            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
//        }

        // TODO: what the preDraw do?
//        /*
//         * We didn't let makeNewLayout() register to bring the cursor into view,
//         * so do it here if there is any possibility that it is needed.
//         */
//        if (mMovement != null ||
//                mLayout.getWidth() > unpaddedWidth ||
//                mLayout.getHeight() > unpaddedHeight) {
//            registerForPreDraw();
//        } else {
//            scrollTo(0, 0);
//        }

        setMeasuredDimension(width, height);

//        onMeasureCount ++;
//
//        Timber.d("onMeasure use " + onMeasureStopwatch.stop().toString());
//        Timber.d("onMeasure avg use " + onMeasureTotalStopwatch.stop().elapsed(TimeUnit.MICROSECONDS) / onMeasureCount + "μs");
    }

//    @Override
//    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
////        Stopwatch stopwatch = Stopwatch.createStarted();
//        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//
//        int width;
//        int height;
//
//        int des = -1;
//
//        BoringLayout.Metrics boring = UNKNOWN_BORING;
//
//        if (widthMode == MeasureSpec.EXACTLY) {
//            // Parent has told us how big to be. So be it.
//            des = widthSize;
//        }
//        else{
//            // 若文字未改變 且已初始化 Layout 則使用此方法計算寬度(速度較快一些)
//            if( !mTextChanged && mLayout != null ){
//                if( !mTextSizeChanged ){
//                    des = mCurWidth;
//                }
//                else {
//                    des = mCurWidth = desired(mLayout);
//                }
//            }
//
//            // 文字已改變
//            if( des < 0 ){
//                des = mCurWidth = (int) Math.min(Layout.getDesiredWidth(mText, mTextPaint), widthSize);
//            }
//        }
//
//        width = des;
//
//        if( mLayout != null ){
//            // 字體大小改變 僅需增加寬度
//            if( mLayout.getWidth() < width ){
//                mLayout.increaseWidthTo(width);
//            }
//            // 文字未改變 寬度亦未改變
//            else if( mLayout.getWidth() == width ){
//                // Do nothing
//            }
//            // 文字已改變 但使用 BoringLayout
//            else if( mLayout instanceof BoringLayout ){
//                boring = BoringLayout.isBoring(mText, mTextPaint, boring);
//
//                if( boring != null && mSavedLayout != null && boring.width <= width ) {
//                    mLayout = mSavedLayout.replaceOrMake(mText, mTextPaint, width,
//                            Layout.Alignment.ALIGN_NORMAL, mSpacingMult, 0f, boring, true);
//                }
//                else{
//                    nullLayouts();
//                }
//            }
//            // 文字已改變 且使用 StaticLayout
//            else{
//                nullLayouts();
//            }
//        }
//
//        if( mLayout == null ){
//            makeNewLayout(width, boring);
//        }
//
//        if( heightMode == MeasureSpec.EXACTLY ){
//            height = heightSize;
//        }
//        else{
//            if( mTextSizeChanged ) {
//                height = mCurHeight = heightSize == 0 ? mLayout.getLineTop(mLayout.getLineCount()) : Math.min(mLayout.getLineTop(mLayout.getLineCount()), heightSize);
//            }
//            else{
//                height = mCurHeight;
//            }
//        }
//
////        if( mTextChanged ) mTextChanged = false;
//        if( mTextSizeChanged ) mTextSizeChanged = false;
//
//        setMeasuredDimension(width, height);
//
////        if( BuildConfig.DEBUG ) Log.d("TEXT", stopwatch.stop().toString());
////        if( BuildConfig.DEBUG ) {
////            if( stopwatch.toString().contains("ms") ) {
////                Log.d("TEXT", mText.toString());
////            }
////        }
//    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//        onLayoutTotalStopwatch.start();
//        onLayoutStopwatch.reset().start();

        super.onLayout(changed, left, top, right, bottom);

//        onLayoutCount ++;

//        Timber.d("onLayout use " + onLayoutStopwatch.stop().toString());
//        Timber.d("onLayout avg use " + onLayoutTotalStopwatch.stop().elapsed(TimeUnit.MICROSECONDS) / onLayoutCount + "μs");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();

        if( mLayout != null ){
            canvas.translate(getPaddingLeft(), getPaddingTop());
            mLayout.draw(canvas);
        }

        canvas.restore();
    }
}
