package com.gingersoft.gsa.cloud.ui.view;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicBlur;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;

import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import com.gingersoft.gsa.cloud.base.R;

import org.jetbrains.annotations.NotNull;


public class SwitchButton extends View {

    private float switchElevation;
    private float switcherCornerRadius;
    boolean isChecked;

    private float iconRadius;
    private float iconClipRadius;
    private float iconCollapsedWidth;
    private float iconHeight = 0f;

    private RectF switcherRect = new RectF(0f, 0f, 0f, 0f);
    private Paint switcherPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//繪製switcher按鈕的paint

    private RectF iconRect = new RectF(0f, 0f, 0f, 0f);
    private RectF iconClipRect = new RectF(0f, 0f, 0f, 0f);
    private Paint iconPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//繪製按鈕上的icon

    private Paint iconClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private Paint shadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Bitmap shadow;
    private float shadowOffset;

    @ColorInt
    private int onColor;
    @ColorInt
    private int offColor;
    @ColorInt
    private int iconColor;
    @ColorInt
    private int currentColor;

    private float iconProgress = 0f;

    private AnimatorSet animatorSet = new AnimatorSet();
    //默認寬高
    private int defHeight = 0;
    private int defWidth = 0;

    private float onClickOffset = 0;
    private float iconTranslateX = 0f;

    OnCheckChangedListener checkChangedListener;

    private final long SWITCHER_ANIMATION_DURATION = 800L;
    private final long COLOR_ANIMATION_DURATION = 300L;
    private final long TRANSLATE_ANIMATION_DURATION = 200L;
    private final float ON_CLICK_RADIUS_OFFSET = 2f;
    private final double BOUNCE_ANIM_AMPLITUDE_IN = 0.2;
    private final double BOUNCE_ANIM_AMPLITUDE_OUT = 0.15;
    private final double BOUNCE_ANIM_FREQUENCY_IN = 14.5;
    private final double BOUNCE_ANIM_FREQUENCY_OUT = 12.0;

    private final String STATE = "switch_state";
    private final String KEY_CHECKED = "checked";

    public SwitchButton(Context context) {
        this(context, null);
    }

    public SwitchButton(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SwitchButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }


    private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwitchButton, defStyleAttr, R.style.MySwitcher);
        switchElevation = typedArray.getDimension(R.styleable.SwitchButton_elevation, 0f);
        onColor = typedArray.getColor(R.styleable.SwitchButton_switcherBtn_on_color, 0);
        offColor = typedArray.getColor(R.styleable.SwitchButton_switcherBtn_off_color, 0);
        iconColor = typedArray.getColor(R.styleable.SwitchButton_switcherBtn_icon_color, 0);
        isChecked = typedArray.getBoolean(R.styleable.SwitchButton_android_checked, true);

        if (!isChecked) {
            setIconProgress(1f);
        }

        if (isChecked) {
            setCurrentColor(onColor);
        } else {
            setCurrentColor(offColor);
        }
        iconPaint.setColor(iconColor);

        defHeight = typedArray.getDimensionPixelOffset(R.styleable.SwitchButton_switcherBtn_height, 0);
        defWidth = typedArray.getDimensionPixelOffset(R.styleable.SwitchButton_switcherBtn_width, 0);

        typedArray.recycle();

        if (!isLollipopAndAbove() && switchElevation > 0f) {
            shadowPaint.setColorFilter(new PorterDuffColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN));
            shadowPaint.setAlpha(51); // 20%
            setShadowBlurRadius(switchElevation);
            setLayerType(LAYER_TYPE_SOFTWARE, null);
        }
        setOnClickListener(v -> animateSwitch());
    }

    float iconTranslateA;
    float iconTranslateB;

    public void setOnClickOffset(float onClickOffset) {
        this.onClickOffset = onClickOffset;
        switcherRect.left = onClickOffset + shadowOffset;
        switcherRect.top = onClickOffset + shadowOffset / 2;
        switcherRect.right = getWidth() - onClickOffset - shadowOffset;
        switcherRect.bottom = getHeight() - onClickOffset - shadowOffset - shadowOffset / 2;
        if (!isLollipopAndAbove()) {
            generateShadow();
        }
        invalidate();
    }

    public void setCurrentColor(int currentColor) {
        this.currentColor = currentColor;
        switcherPaint.setColor(currentColor);
        iconClipPaint.setColor(currentColor);
    }

    public void setIconProgress(float value) {
        if (iconProgress != value) {
            iconProgress = value;

            float iconOffset = lerp(0f, iconRadius - iconCollapsedWidth / 2, value);
            iconRect.left = getWidth() - switcherCornerRadius - iconCollapsedWidth / 2 - iconOffset;
            iconRect.right = getWidth() - switcherCornerRadius + iconCollapsedWidth / 2 + iconOffset;

            float clipOffset = lerp(0f, iconClipRadius, value);
            iconClipRect.set(
                    iconRect.centerX() - clipOffset,
                    iconRect.centerY() - clipOffset,
                    iconRect.centerX() + clipOffset,
                    iconRect.centerY() + clipOffset
            );
            if (!isLollipopAndAbove()) {
                generateShadow();
            }
            postInvalidateOnAnimation();
        }
    }

    private void animateSwitch() {
        if (checkChangedListener != null) {
            checkChangedListener.onChanged(!isChecked);
        }
        animatorSet.cancel();
        animatorSet = new AnimatorSet();

        setOnClickOffset(ON_CLICK_RADIUS_OFFSET);

        double amplitude = BOUNCE_ANIM_AMPLITUDE_IN;
        double frequency = BOUNCE_ANIM_FREQUENCY_IN;
        iconTranslateA = 0f;
        iconTranslateB = -(getWidth() - shadowOffset - switcherCornerRadius * 2);
        float newProgress = 1f;

        if (!isChecked) {
            amplitude = BOUNCE_ANIM_AMPLITUDE_OUT;
            frequency = BOUNCE_ANIM_FREQUENCY_OUT;
            iconTranslateA = iconTranslateB;
            iconTranslateB = -shadowOffset;
            newProgress = 0f;
        }

        ValueAnimator switcherAnimator = ValueAnimator.ofFloat(iconProgress, newProgress);
        switcherAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                setIconProgress((float) animation.getAnimatedValue());
            }
        });
        switcherAnimator.setInterpolator(new BounceInterpolator(amplitude, frequency));
        switcherAnimator.setDuration(SWITCHER_ANIMATION_DURATION);

        ValueAnimator translateAnimator = ValueAnimator.ofFloat(0f, 1f);
        translateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                iconTranslateX = lerp(iconTranslateA, iconTranslateB, value);
            }
        });
        translateAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                onClickOffset = 0f;
            }
        });
        translateAnimator.setDuration(TRANSLATE_ANIMATION_DURATION);

        int toColor;
        if (!isChecked) {
            toColor = onColor;
        } else {
            toColor = offColor;
        }

        iconClipPaint.setColor(toColor);

        ValueAnimator colorAnimator = new ValueAnimator();
        colorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                setCurrentColor((int) animation.getAnimatedValue());
            }
        });
        colorAnimator.setIntValues(currentColor, toColor);
        colorAnimator.setEvaluator(new ArgbEvaluator());
        colorAnimator.setDuration(COLOR_ANIMATION_DURATION);
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                isChecked = !isChecked;
            }
        });
        animatorSet.playTogether(switcherAnimator, translateAnimator, colorAnimator);
        animatorSet.start();
    }

    private void setShadowBlurRadius(Float elevation) {
        float maxElevation = dip2px(getContext(), 24f);
        switchElevation = Math.min(25f * (elevation / maxElevation), 25f);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
            width = defWidth;
            height = defHeight;
        }

        //不是21以上，寬高需要加上z軸*2
        if (!isLollipopAndAbove()) {
            width += switchElevation * 2;
            height += switchElevation * 2;
        }
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (isLollipopAndAbove()) {//21以上
            setOutlineProvider(new SwitchOutline(w, h));
            setElevation(switchElevation);
        } else {
            shadowOffset = switchElevation;
            iconTranslateX = -shadowOffset;
        }

        this.switcherRect.left = this.shadowOffset;
        this.switcherRect.top = this.shadowOffset / (float) 2;
        this.switcherRect.right = (float) this.getWidth() - this.shadowOffset;
        this.switcherRect.bottom = (float) this.getHeight() - this.shadowOffset - this.shadowOffset / (float) 2;
        //圓角為高度的一半
        this.switcherCornerRadius = ((float) this.getHeight() - this.shadowOffset * (float) 2) / 2.0F;
        //內部的icon圓角為按鈕圓角的0.6倍
        this.iconRadius = this.switcherCornerRadius * 0.6F;
        //應該是圓的圓角
        this.iconClipRadius = this.iconRadius / 2.25F;//icon剪輯半徑
        //
        this.iconCollapsedWidth = this.iconRadius - this.iconClipRadius;
        this.iconHeight = this.iconRadius * 2.0F;
        //left 為寬度減去 按鈕圓角 減去
        this.iconRect.set((float) this.getWidth() - this.switcherCornerRadius - this.iconCollapsedWidth / (float) 2, ((float) this.getHeight() - this.iconHeight) / 2.0F - this.shadowOffset / (float) 2, (float) this.getWidth() - this.switcherCornerRadius + this.iconCollapsedWidth / (float) 2, (float) this.getHeight() - ((float) this.getHeight() - this.iconHeight) / 2.0F - this.shadowOffset / (float) 2);
        if (!this.isChecked) {
            this.iconRect.left = (float) this.getWidth() - this.switcherCornerRadius - this.iconCollapsedWidth / (float) 2 - (this.iconRadius - this.iconCollapsedWidth / (float) 2);
            this.iconRect.right = (float) this.getWidth() - this.switcherCornerRadius + this.iconCollapsedWidth / (float) 2 + (this.iconRadius - this.iconCollapsedWidth / (float) 2);
            this.iconClipRect.set(this.iconRect.centerX() - this.iconClipRadius, this.iconRect.centerY() - this.iconClipRadius, this.iconRect.centerX() + this.iconClipRadius, this.iconRect.centerY() + this.iconClipRadius);
            this.iconTranslateX = -((float) this.getWidth() - this.shadowOffset - this.switcherCornerRadius * (float) 2);
        }

        if (!isLollipopAndAbove()) {
            this.generateShadow();
        }
    }

    private void generateShadow() {
        if (switchElevation == 0f) {
            return;
        }
        if (!isInEditMode()) {
            if (shadow == null) {
                shadow = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ALPHA_8);
            } else {
                shadow.eraseColor(Color.TRANSPARENT);
            }
            Canvas c = new Canvas(shadow);

            c.drawRoundRect(switcherRect, switcherCornerRadius, switcherCornerRadius, shadowPaint);

            RenderScript rs = RenderScript.create(getContext());
            ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs, Element.U8(rs));
            Allocation input = Allocation.createFromBitmap(rs, shadow);
            Allocation output = Allocation.createTyped(rs, input.getType());
            blur.setRadius(switchElevation);
            blur.setInput(input);
            blur.forEach(output);
            output.copyTo(shadow);
            input.destroy();
            output.destroy();
            blur.destroy();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // shadow，繪製陰影
        if (!isLollipopAndAbove() && switchElevation > 0f && !isInEditMode()) {
            canvas.drawBitmap(shadow, 0f, shadowOffset, null);
        }

        // switcher 繪製按鈕外部，
        canvas.drawRoundRect(switcherRect, switcherCornerRadius, switcherCornerRadius, switcherPaint);

        // icon
        canvas.translate(iconTranslateX, 0);
        canvas.drawRoundRect(iconRect, switcherCornerRadius, switcherCornerRadius, iconPaint);
        /* don't draw clip path if icon is collapsed (to prevent drawing small circle
            on rounded rect when switch is isChecked)*/
        if (iconClipRect.width() > iconCollapsedWidth) {
            canvas.drawRoundRect(iconClipRect, iconRadius, iconRadius, iconClipPaint);
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private final class SwitchOutline extends ViewOutlineProvider {
        private int width;
        private int height;

        public void getOutline(@NotNull View view, @NotNull Outline outline) {
            outline.setRoundRect(0, 0, this.width, this.height, switcherCornerRadius);
        }

        public int getWidth() {
            return this.width;
        }

        public void setWidth(int var1) {
            this.width = var1;
        }

        public int getHeight() {
            return this.height;
        }

        public void setHeight(int var1) {
            this.height = var1;
        }

        public SwitchOutline(int width, int height) {
            this.width = width;
            this.height = height;
        }
    }


    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            super.onRestoreInstanceState(((Bundle) state).getParcelable(STATE));
            isChecked = ((Bundle) state).getBoolean(KEY_CHECKED);
            if (!isChecked) {
                forceUncheck();
            }
        }
    }

    public void setOnCheckChangedListener(OnCheckChangedListener checkChangedListener) {
        this.checkChangedListener = checkChangedListener;
    }

    public interface OnCheckChangedListener {
        void onChanged(boolean state);
    }

    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean checked) {
        if (this.isChecked != checked) {
//            if (withAnimation) {
            animateSwitch();
//            } else {
//                this.isChecked = checked;
//                if (!checked) {
//                    currentColor = offColor;
//                    iconProgress = 1f;
//                } else {
//                    currentColor = onColor;
//                    iconProgress = 0f;
//                }
//            }
        }
    }

    private void forceUncheck() {
        setCurrentColor(offColor);
        setIconProgress(1f);
    }


    //    onRestoreInstanceState
    public float lerp(float a, float b, float t) {
        return a + (b - a) * t;
    }

    /**
     * dip转pix
     *
     * @param dpValue
     * @return
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    public boolean isLollipopAndAbove() {
        return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP;
    }
}

