package com.xiaoxuan.demo.tv.widget.shimmer;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;

/**
 * @author xiaoxuan 参考网上实现思路,稍加整理修改
 * 抽象调用方法,参照《Android源码设计模式》第三章Builder模式
 */
public abstract class AbsFocusBorder extends View implements FocusBorder, ViewTreeObserver.OnGlobalFocusChangeListener
{
    private static final long DEFAULT_ANIM_DURATION_TIME = 300;
    
    private static final long DEFAULT_SHIMMER_DURATION_TIME = 1000;
    
    protected long mAnimDuration = DEFAULT_ANIM_DURATION_TIME;
    
    protected long mShimmerDuration = DEFAULT_SHIMMER_DURATION_TIME;
    
    protected RectF mFrameRectF = new RectF();
    
    protected RectF mPaddingRectF = new RectF();
    
    protected RectF mPaddingOfsetRectF = new RectF();
    
    protected RectF mTempRectF = new RectF();
    
    private LinearGradient mShimmerLinearGradient;
    
    private Matrix mShimmerGradientMatrix;
    
    private Paint mShimmerPaint;
    
    private int mShimmerColor = 0x66FFFFFF;
    
    private float mShimmerTranslate = 0;
    
    private boolean mShimmerAnimating = false;
    
    private boolean mIsShimmerAnim = true;
    
    private boolean mReAnim = false; // 修复RecyclerView焦点临时标记
    
    private ObjectAnimator mTranslationXAnimator;
    
    private ObjectAnimator mTranslationYAnimator;
    
    private ObjectAnimator mWidthAnimator;
    
    private ObjectAnimator mHeightAnimator;
    
    private ObjectAnimator mShimmerAnimator;
    
    private AnimatorSet mAnimatorSet;
    
    private RecyclerViewScrollListener mRecyclerViewScrollListener;
    
    private WeakReference<RecyclerView> mWeakRecyclerView;
    
    private WeakReference<View> mOldFocusView;
    
    private OnFocusCallback mOnFocusCallback;
    
    private boolean mIsVisible = false;
    
    protected AbsFocusBorder(Context context, int shimmerColor, long shimmerDuration, boolean isShimmerAnim,
        long animDuration, RectF paddingOfsetRectF)
    {
        super(context);
        
        this.mShimmerColor = shimmerColor;
        this.mShimmerDuration = shimmerDuration;
        this.mIsShimmerAnim = isShimmerAnim;
        this.mAnimDuration = animDuration;
        if (null != paddingOfsetRectF)
            this.mPaddingOfsetRectF.set(paddingOfsetRectF);
        setLayerType(View.LAYER_TYPE_SOFTWARE, null); // 关闭硬件加速
        setVisibility(INVISIBLE);
        
        mShimmerPaint = new Paint();
        mShimmerGradientMatrix = new Matrix();
    }
    
    @Override
    public boolean isInEditMode()
    {
        return true;
    }
    
    /**
     * 绘制闪光
     * 
     * @param canvas
     */
    protected void onDrawShimmer(Canvas canvas)
    {
        if (mShimmerAnimating)
        {
            canvas.save();
            mTempRectF.set(mFrameRectF);
            mTempRectF.inset(2f, 2f);
            float shimmerTranslateX = mTempRectF.width() * mShimmerTranslate;
            float shimmerTranslateY = mTempRectF.height() * mShimmerTranslate;
            mShimmerGradientMatrix.setTranslate(shimmerTranslateX, shimmerTranslateY);
            mShimmerLinearGradient.setLocalMatrix(mShimmerGradientMatrix);
            canvas.drawRoundRect(mTempRectF, getRoundRadius(), getRoundRadius(), mShimmerPaint);
            canvas.restore();
        }
    }
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        if (w != oldw || h != oldh)
        {
            mFrameRectF.set(mPaddingRectF.left, mPaddingRectF.top, w - mPaddingRectF.right, h - mPaddingRectF.bottom);
        }
    }
    
    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        onDrawShimmer(canvas);
    }
    
    @Override
    protected void onDetachedFromWindow()
    {
        unBoundGlobalFocusListener();
        super.onDetachedFromWindow();
    }
    
    private void setShimmerAnimating(boolean shimmerAnimating)
    {
        mShimmerAnimating = shimmerAnimating;
        if (mShimmerAnimating)
        {
            mShimmerLinearGradient = new LinearGradient(0, 0, mFrameRectF.width(), mFrameRectF.height(),
                new int[] {0x00FFFFFF, 0x1AFFFFFF, mShimmerColor, 0x1AFFFFFF, 0x00FFFFFF},
                new float[] {0f, 0.2f, 0.5f, 0.8f, 1f}, Shader.TileMode.CLAMP);
            mShimmerPaint.setShader(mShimmerLinearGradient);
        }
    }
    
    protected void setShimmerTranslate(float shimmerTranslate)
    {
        if (mIsShimmerAnim && mShimmerTranslate != shimmerTranslate)
        {
            mShimmerTranslate = shimmerTranslate;
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
    
    protected float getShimmerTranslate()
    {
        return mShimmerTranslate;
    }
    
    protected void setWidth(int width)
    {
        if (getLayoutParams().width != width)
        {
            getLayoutParams().width = width;
            requestLayout();
        }
    }
    
    protected void setHeight(int height)
    {
        if (getLayoutParams().height != height)
        {
            getLayoutParams().height = height;
            requestLayout();
        }
    }
    
    @Override
    public void setVisible(boolean visible)
    {
        if (mIsVisible != visible)
        {
            mIsVisible = visible;
            setVisibility(visible ? VISIBLE : INVISIBLE);
            
            if (!visible && null != mOldFocusView && null != mOldFocusView.get())
            {
                runFocusScaleAnimation(mOldFocusView.get(), 1f, 1f);
                mOldFocusView.clear();
                mOldFocusView = null;
            }
        }
    }
    
    @Override
    public boolean isVisible()
    {
        return mIsVisible;
    }
    
    private void registerScrollListener(RecyclerView recyclerView)
    {
        if (null != mWeakRecyclerView && mWeakRecyclerView.get() == recyclerView)
        {
            return;
        }
        
        if (null == mRecyclerViewScrollListener)
        {
            mRecyclerViewScrollListener = new RecyclerViewScrollListener(this);
        }
        
        if (null != mWeakRecyclerView && null != mWeakRecyclerView.get())
        {
            mWeakRecyclerView.get().removeOnScrollListener(mRecyclerViewScrollListener);
            mWeakRecyclerView.clear();
        }
        
        recyclerView.removeOnScrollListener(mRecyclerViewScrollListener);
        recyclerView.addOnScrollListener(mRecyclerViewScrollListener);
        mWeakRecyclerView = new WeakReference<>(recyclerView);
    }
    
    protected Rect findLocationWithView(View view)
    {
        return findOffsetDescendantRectToMyCoords(view);
    }
    
    protected Rect findOffsetDescendantRectToMyCoords(View descendant)
    {
        final ViewGroup root = (ViewGroup)getParent();
        final Rect rect = new Rect();
        mReAnim = false;
        if (descendant == root)
        {
            return rect;
        }
        
        ViewParent theParent = descendant.getParent();
        Object tag;
        Point point;
        
        while ((theParent != null) && (theParent instanceof View) && (theParent != root))
        {
            
            rect.offset(descendant.getLeft() - descendant.getScrollX(), descendant.getTop() - descendant.getScrollY());
            
            // 兼容TvRecyclerView
            if (theParent instanceof RecyclerView)
            {
                final RecyclerView rv = (RecyclerView)theParent;
                registerScrollListener((RecyclerView)theParent);
                tag = ((View)theParent).getTag();
                if (null != tag && tag instanceof Point)
                {
                    point = (Point)tag;
                    rect.offset(-point.x, -point.y);
                }
                if (null == tag && rv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE
                    && (mRecyclerViewScrollListener.mScrolledX != 0 || mRecyclerViewScrollListener.mScrolledY != 0))
                {
                    mReAnim = true;
                }
            }
            
            descendant = (View)theParent;
            theParent = descendant.getParent();
        }
        
        if (theParent == root)
        {
            rect.offset(descendant.getLeft() - descendant.getScrollX(), descendant.getTop() - descendant.getScrollY());
        }
        
        return rect;
    }
    
    @Override
    public void onFocus(@NonNull View focusView, FocusBorder.Options options)
    {
        if (null != mOldFocusView && null != mOldFocusView.get())
        {
            runFocusScaleAnimation(mOldFocusView.get(), 1f, 1f);
            mOldFocusView.clear();
        }
        
        if (options instanceof Options)
        {
            final Options baseOptions = (Options)options;
            if (baseOptions.isScale())
            {
                mOldFocusView = new WeakReference<>(focusView);
            }
            runFocusAnimation(focusView, baseOptions);
        }
    }
    
    @Override
    public void boundGlobalFocusListener(@NonNull OnFocusCallback callback)
    {
        mOnFocusCallback = callback;
        getViewTreeObserver().addOnGlobalFocusChangeListener(this);
    }
    
    @Override
    public void unBoundGlobalFocusListener()
    {
        if (null != mOnFocusCallback)
        {
            mOnFocusCallback = null;
            getViewTreeObserver().removeOnGlobalFocusChangeListener(this);
        }
    }
    
    @Override
    public void onGlobalFocusChanged(View oldFocus, View newFocus)
    {
        runFocusScaleAnimation(oldFocus, 1f, 1f);
        
        final Options options = null != mOnFocusCallback ? (Options)mOnFocusCallback.onFocus(oldFocus, newFocus) : null;
        if (null != options)
        {
            runFocusAnimation(newFocus, options);
        }
    }
    
    private void runFocusAnimation(View focusView, Options options)
    {
        setVisible(true);
        runFocusScaleAnimation(focusView, options.scaleX, options.scaleY); // 焦点缩放动画
        runBorderAnimation(focusView, options); // 移动边框的动画。
    }
    
    protected void runBorderAnimation(View focusView, Options options)
    {
        if (null == focusView)
            return;
        
        if (null != mAnimatorSet)
        {
            mAnimatorSet.cancel();
        }
        
        createBorderAnimation(focusView, options);
        
        mAnimatorSet.start();
    }
    
    /**
     * 焦点缩放动画
     * 
     * @param oldOrNewFocusView
     * @param
     */
    protected void runFocusScaleAnimation(@Nullable View oldOrNewFocusView, float scaleX, float scaleY)
    {
        if (null == oldOrNewFocusView)
            return;
        if (scaleX != oldOrNewFocusView.getScaleX() || scaleY != oldOrNewFocusView.getScaleY())
        {
            oldOrNewFocusView.animate().scaleX(scaleX).scaleY(scaleY).setDuration(mAnimDuration).start();
        }
    }
    
    protected void createBorderAnimation(View focusView, Options options)
    {
        
        final float paddingWidth =
            mPaddingRectF.left + mPaddingRectF.right + mPaddingOfsetRectF.left + mPaddingOfsetRectF.right;
        final float paddingHeight =
            mPaddingRectF.top + mPaddingRectF.bottom + mPaddingOfsetRectF.top + mPaddingOfsetRectF.bottom;
        final int newWidth = (int)(focusView.getMeasuredWidth() * options.scaleX + paddingWidth);
        final int newHeight = (int)(focusView.getMeasuredHeight() * options.scaleY + paddingHeight);
        final Rect fromRect = findLocationWithView(this);
        final Rect toRect = findLocationWithView(focusView);
        final int x = toRect.left - fromRect.left;
        final int y = toRect.top - fromRect.top;
        final float newX = x - Math.abs(focusView.getMeasuredWidth() - newWidth) / 2f;
        final float newY = y - Math.abs(focusView.getMeasuredHeight() - newHeight) / 2f;
        
        final List<Animator> together = new ArrayList<>();
        final List<Animator> appendTogether = getTogetherAnimators(newX, newY, newWidth, newHeight, options);
        together.add(getTranslationXAnimator(newX));
        together.add(getTranslationYAnimator(newY));
        together.add(getWidthAnimator(newWidth));
        together.add(getHeightAnimator(newHeight));
        if (null != appendTogether && !appendTogether.isEmpty())
        {
            together.addAll(appendTogether);
        }
        
        final List<Animator> sequentially = new ArrayList<>();
        final List<Animator> appendSequentially = getSequentiallyAnimators(newX, newY, newWidth, newHeight, options);
        if (mIsShimmerAnim)
        {
            sequentially.add(getShimmerAnimator());
        }
        if (null != appendSequentially && !appendSequentially.isEmpty())
        {
            sequentially.addAll(appendSequentially);
        }
        
        mAnimatorSet = new AnimatorSet();
        mAnimatorSet.setInterpolator(new DecelerateInterpolator(1));
        mAnimatorSet.playTogether(together);
        mAnimatorSet.playSequentially(sequentially);
    }
    
    private ObjectAnimator getTranslationXAnimator(float x)
    {
        if (null == mTranslationXAnimator)
        {
            mTranslationXAnimator = ObjectAnimator.ofFloat(this, "translationX", x).setDuration(mAnimDuration);
        }
        else
        {
            mTranslationXAnimator.setFloatValues(x);
        }
        return mTranslationXAnimator;
    }
    
    private ObjectAnimator getTranslationYAnimator(float y)
    {
        if (null == mTranslationYAnimator)
        {
            mTranslationYAnimator = ObjectAnimator.ofFloat(this, "translationY", y).setDuration(mAnimDuration);
        }
        else
        {
            mTranslationYAnimator.setFloatValues(y);
        }
        return mTranslationYAnimator;
    }
    
    private ObjectAnimator getHeightAnimator(int height)
    {
        if (null == mHeightAnimator)
        {
            mHeightAnimator =
                ObjectAnimator.ofInt(this, "height", getMeasuredHeight(), height).setDuration(mAnimDuration);
        }
        else
        {
            mHeightAnimator.setIntValues(getMeasuredHeight(), height);
        }
        return mHeightAnimator;
    }
    
    private ObjectAnimator getWidthAnimator(int width)
    {
        if (null == mWidthAnimator)
        {
            mWidthAnimator = ObjectAnimator.ofInt(this, "width", getMeasuredWidth(), width).setDuration(mAnimDuration);
        }
        else
        {
            mWidthAnimator.setIntValues(getMeasuredWidth(), width);
        }
        return mWidthAnimator;
    }
    
    private ObjectAnimator getShimmerAnimator()
    {
        if (null == mShimmerAnimator)
        {
            mShimmerAnimator = ObjectAnimator.ofFloat(this, "shimmerTranslate", -1f, 1f);
            mShimmerAnimator.setInterpolator(new LinearInterpolator());
            mShimmerAnimator.setDuration(mShimmerDuration);
            mShimmerAnimator.setStartDelay(400);
            mShimmerAnimator.addListener(new AnimatorListenerAdapter()
            {
                @Override
                public void onAnimationStart(Animator animation)
                {
                    setShimmerAnimating(true);
                }
                
                @Override
                public void onAnimationEnd(Animator animation)
                {
                    setShimmerAnimating(false);
                }
            });
        }
        return mShimmerAnimator;
    }
    
    abstract float getRoundRadius();
    
    abstract List<Animator> getTogetherAnimators(float newX, float newY, int newWidth, int newHeight, Options options);
    
    abstract List<Animator> getSequentiallyAnimators(float newX, float newY, int newWidth, int newHeight,
        Options options);
    
    private static class RecyclerViewScrollListener extends RecyclerView.OnScrollListener
    {
        private WeakReference<AbsFocusBorder> mFocusBorder;
        
        private int mScrolledX = 0, mScrolledY = 0;
        
        public RecyclerViewScrollListener(AbsFocusBorder border)
        {
            mFocusBorder = new WeakReference<>(border);
        }
        
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy)
        {
            mScrolledX = Math.abs(dx) == 1 ? 0 : dx;
            mScrolledY = Math.abs(dy) == 1 ? 0 : dy;
        }
        
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState)
        {
            if (newState == RecyclerView.SCROLL_STATE_IDLE)
            {
                final AbsFocusBorder border = mFocusBorder.get();
                final View focused = recyclerView.getFocusedChild();
                if (null != border && null != focused)
                {
                    if (border.mReAnim || mScrolledX != 0 || mScrolledY != 0)
                    {
                        border.runBorderAnimation(focused, Options.OptionsHolder.INSTANCE);
                    }
                }
                mScrolledX = mScrolledY = 0;
            }
        }
    }
    
    public static class Options extends FocusBorder.Options
    {
        protected float scaleX = 1f, scaleY = 1f;
        
        Options()
        {
        }
        
        private static class OptionsHolder
        {
            private static final Options INSTANCE = new Options();
        }
        
        public static Options get(float scaleX, float scaleY)
        {
            OptionsHolder.INSTANCE.scaleX = scaleX;
            OptionsHolder.INSTANCE.scaleY = scaleY;
            return OptionsHolder.INSTANCE;
        }
        
        public boolean isScale()
        {
            return scaleX != 1f || scaleY != 1f;
        }
    }
    
    public static abstract class Builder
    {
        protected int mShimmerColor = 0x8FFFFFFF;
        
        protected boolean mIsShimmerAnim = true;
        
        protected long mAnimDuration = AbsFocusBorder.DEFAULT_ANIM_DURATION_TIME;
        
        protected long mShimmerDuration = AbsFocusBorder.DEFAULT_SHIMMER_DURATION_TIME;
        
        protected RectF mPaddingOfsetRectF = new RectF();
        
        public Builder shimmerColor(int color)
        {
            this.mShimmerColor = color;
            return this;
        }
        
        public Builder shimmerDuration(long duration)
        {
            this.mShimmerDuration = duration;
            return this;
        }
        
        public Builder noShimmer()
        {
            this.mIsShimmerAnim = false;
            return this;
        }
        
        public Builder animDuration(long duration)
        {
            this.mAnimDuration = duration;
            return this;
        }
        
        public Builder padding(float padding)
        {
            return padding(padding, padding, padding, padding);
        }
        
        public Builder padding(float left, float top, float right, float bottom)
        {
            this.mPaddingOfsetRectF.left = left;
            this.mPaddingOfsetRectF.top = top;
            this.mPaddingOfsetRectF.right = right;
            this.mPaddingOfsetRectF.bottom = bottom;
            return this;
        }
        
        public abstract FocusBorder build(Activity activity);
        
        public abstract FocusBorder build(ViewGroup viewGroup);
    }
}