package com.efounder.chat.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import com.google.android.material.appbar.AppBarLayout;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;

import com.efounder.chat.R;


/**
 * Author: nanchen
 * Email: liushilin520@foxmail.com
 * Date: 2017-06-20  14:36
 */

public class SimpleViewBehavior extends CoordinatorLayout.Behavior<View> {

    private static final int UNSPECIFIED_INT = Integer.MAX_VALUE;
    private static final float UNSPECIFIED_FLOAT = Float.MAX_VALUE;

    private static final int DEPEND_TYPE_HEIGHT = 0;
    private static final int DEPEND_TYPE_WIDTH = 1;
    private static final int DEPEND_TYPE_X = 2;
    private static final int DEPEND_TYPE_Y = 3;

    private int mDependViewId = 0;              //默认没有依赖对象
    private int mDependType = DEPEND_TYPE_Y;    //默认按照y方向变化
    private int mDependTargetX;                 //X方向的允许最大距离(影响动画percent)
    private int mDependTargetY;                 //Y方向的允许最大距离(影响动画percent)
    private int mDependTargetWidth;             //依赖控件起始最大宽度(影响动画percent)
    private int mDependTargetHeight;            //依赖控件起始最大高度(影响动画percent)
    private int targetX;
    private int targetY;
    private int targetWidth;
    private int targetHeight;
    private int targetBackgroundColor;
    private float targetAlpha;
    private float targetRotateX;
    private float targetRotateY;
    private int mAnimationId = 0;               //自定义动画id(xml文件定义动画)

    private int mDependStartX;
    private int mDependStartY;
    private int mDependStartWidth;
    private int mDependStartHeight;
    private int mStartX;
    private int mStartY;
    private int mStartWidth;
    private int mStartHeight;
    private int mStartBackgroundColor;
    private float mStartAlpha;
    private float mStartRotateX;
    private float mStartRotateY;

    private Animation mAnimation;
    private boolean isPrepared;

    public SimpleViewBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleViewBehavior);
        mDependViewId = a.getResourceId(R.styleable.SimpleViewBehavior_svb_dependOn, mDependViewId);
        mDependType = a.getInt(R.styleable.SimpleViewBehavior_svb_dependType, mDependType);
        mDependTargetX = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetX, UNSPECIFIED_INT);
        mDependTargetY = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetY, UNSPECIFIED_INT);
        mDependTargetWidth = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetWidth, UNSPECIFIED_INT);
        mDependTargetHeight = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetHeight, UNSPECIFIED_INT);
        targetX = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetX, UNSPECIFIED_INT);
        targetY = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetY, UNSPECIFIED_INT);
        targetWidth = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetWidth, UNSPECIFIED_INT);
        targetHeight = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetHeight, UNSPECIFIED_INT);
        targetBackgroundColor = a.getColor(R.styleable.SimpleViewBehavior_svb_targetBackgroundColor, UNSPECIFIED_INT);
        targetAlpha = a.getFloat(R.styleable.SimpleViewBehavior_svb_targetAlpha, UNSPECIFIED_FLOAT);
        targetRotateX = a.getFloat(R.styleable.SimpleViewBehavior_svb_targetRotateX, UNSPECIFIED_FLOAT);
        targetRotateY = a.getFloat(R.styleable.SimpleViewBehavior_svb_targetRotateY, UNSPECIFIED_FLOAT);
        mAnimationId = a.getResourceId(R.styleable.SimpleViewBehavior_svb_animation, mAnimationId);
        a.recycle();
    }

    /** 初始化数据 */
    private void prepare(CoordinatorLayout parent, View child, View dependency) {
        mDependStartX = (int) dependency.getX();
        mDependStartY = (int) dependency.getY();
        mDependStartWidth = dependency.getWidth();
        mDependStartHeight = dependency.getHeight();
        mStartX = (int) child.getX();
        mStartY = (int) child.getY();
        mStartWidth = child.getWidth();
        mStartHeight = child.getHeight();
        mStartAlpha = child.getAlpha();
        mStartRotateX = child.getRotationX();
        mStartRotateY = child.getRotationY();

        //特殊处理y方向变化
        if (mDependTargetY == UNSPECIFIED_INT && dependency instanceof AppBarLayout) {
            mDependTargetY = ((AppBarLayout) dependency).getTotalScrollRange();
        }
        // 背景颜色渐变
        if (child.getBackground() instanceof ColorDrawable) mStartBackgroundColor = ((ColorDrawable) child.getBackground()).getColor();
        // 自定义动画
        if (mAnimationId != 0) {
            mAnimation = AnimationUtils.loadAnimation(child.getContext(), mAnimationId);
            mAnimation.initialize(child.getWidth(), child.getHeight(), parent.getWidth(), parent.getHeight());
        }
        // 兼容5.0以上的沉浸模式
        if (Build.VERSION.SDK_INT > 16 && parent.getFitsSystemWindows() && targetY != UNSPECIFIED_INT) {
            targetY += getStatusBarHeight(parent.getContext());
        }
        isPrepared = true;
    }

    /**
     * child 是指应用behavior的View ，dependency 担任触发behavior的角色，并与child进行互动。
     * layoutDependsOn方法在每次layout发生变化时都会调用，我们需要在dependency控件发生变化时返回True，
     * 在我们的例子中是用户在屏幕上滑动时（因为AppBarLayout发生了移动），然后我们需要让child做出相应的反应。
     */
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency.getId() == mDependViewId;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        // 该方法会在滑动的时候一直回调,但只需要初始化一次
        if (!isPrepared) prepare(parent, child, dependency);
        updateView(child, dependency);
        return false;
    }

    /**
     * 这个是CoordinatorLayout在进行measure的过程中，利用Behavior对象对子view进行大小测量的一个方法。
     * 在这个方法内，我们可以通过parent.getDependencies(child);这个方法，获取到这个child依赖的view，然后通过获取这个child依赖的view的大小来决定自身的大小。
     */
    @Override
    public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
        return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
    }

    /**
     * 这个方法是用来子view用来布局自身使用，如果依赖其他view，那么系统会首先调用
     * public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency)
     * 这个方法，可以在这个回调中记录dependency的一些位置信息，在onLayoutChild中利用保存下来的信息进行计算，然后得到自身的具体位置。
     */
    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
        boolean bool = super.onLayoutChild(parent, child, layoutDirection);
        if (isPrepared) updateView(child, parent.getDependencies(child).get(0));
        return bool;
    }

    public void updateView(View child, View dependency) {
        float percent = 0;
        float start = 0;
        float current = 0;
        float end = UNSPECIFIED_INT;
        switch (mDependType) {
            case DEPEND_TYPE_WIDTH:
                start = mDependStartWidth;
                current = dependency.getWidth();
                end = mDependTargetWidth;
                break;
            case DEPEND_TYPE_HEIGHT:
                start = mDependStartHeight;
                current = dependency.getHeight();
                end = mDependTargetHeight;
                break;
            case DEPEND_TYPE_X:
                start = mDependStartX;
                current = dependency.getX();
                end = mDependTargetX;
                break;
            case DEPEND_TYPE_Y:
                start = mDependStartY;
                current = dependency.getY();
                end = mDependTargetY;
                break;
        }
        if (end != UNSPECIFIED_INT) {
            percent = Math.abs(current - start) / Math.abs(end - start);
        }
        updateViewWithPercent(child, percent > 1 ? 1 : percent);
    }

    /** 更新View */
    public void updateViewWithPercent(View child, float percent) {
        if (mAnimation == null) {
            //如果没有自定义动画,那么使用属性动画
            float newX = targetX == UNSPECIFIED_INT ? 0 : (targetX - mStartX) * percent;
            float newY = targetY == UNSPECIFIED_INT ? 0 : (targetY - mStartY) * percent;
            //缩放动画
            if (targetWidth != UNSPECIFIED_INT || targetHeight != UNSPECIFIED_INT) {
                child.setScaleX(scaleEvaluator(mStartWidth, targetWidth, percent));
                child.setScaleY(scaleEvaluator(mStartHeight, targetHeight, percent));
                float newWidth = floatEvaluator(mStartWidth, targetWidth, percent);
                float newHeight = floatEvaluator(mStartWidth, targetWidth, percent);
                newX -= (mStartWidth - newWidth) / 2;
                newY -= (mStartHeight - newHeight) / 2;
            }
            //平移动画
            child.setTranslationX(newX);
            child.setTranslationY(newY);
            //透明度变化
            if (targetAlpha != UNSPECIFIED_FLOAT) child.setAlpha(floatEvaluator(mStartAlpha, targetAlpha, percent));
            //背景渐变
            if (targetBackgroundColor != UNSPECIFIED_INT && mStartBackgroundColor != 0) {
                child.setBackgroundColor(argbEvaluator(mStartBackgroundColor, targetBackgroundColor, percent));
            }
            //旋转动画
            if (targetRotateX != UNSPECIFIED_FLOAT) child.setRotationX(floatEvaluator(mStartRotateX, targetRotateX, percent));
            if (targetRotateY != UNSPECIFIED_FLOAT) child.setRotationY(floatEvaluator(mStartRotateY, targetRotateY, percent));
        } else {
            mAnimation.setStartTime(0);
            mAnimation.restrictDuration(100);
            Transformation transformation = new Transformation();
            mAnimation.getTransformation((long) (percent * 100), transformation);
            BehaviorAnimation animation = new BehaviorAnimation(transformation);
            child.startAnimation(animation);
        }
        child.requestLayout();
    }

    private static class BehaviorAnimation extends Animation {

        private Transformation mTransformation;

        public BehaviorAnimation(Transformation transformation) {
            mTransformation = transformation;
            setDuration(0);
            setFillAfter(true);
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            t.compose(mTransformation);
            super.applyTransformation(interpolatedTime, t);
        }
    }

    public static float floatEvaluator(float originalSize, float finalSize, float percent) {
        return (finalSize - originalSize) * percent + originalSize;
    }

    public static float scaleEvaluator(float originalSize, float finalSize, float percent) {
        float calcSize = (finalSize - originalSize) * percent + originalSize;
        return calcSize / originalSize;
    }

    public static int argbEvaluator(int startColor, int endColor, float percent) {
        int startA = (startColor >> 24) & 0xff;
        int startR = (startColor >> 16) & 0xff;
        int startG = (startColor >> 8) & 0xff;
        int startB = startColor & 0xff;

        int endA = (endColor >> 24) & 0xff;
        int endR = (endColor >> 16) & 0xff;
        int endG = (endColor >> 8) & 0xff;
        int endB = endColor & 0xff;

        return ((startA + (int) (percent * (endA - startA))) << 24) |//
                ((startR + (int) (percent * (endR - startR))) << 16) |//
                ((startG + (int) (percent * (endG - startG))) << 8) |//
                ((startB + (int) (percent * (endB - startB))));
    }

    /** 获取状态栏的高度 */
    private static int getStatusBarHeight(Context context) {
        int result = 0;
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }
}

