package com.kaixed.kchat.ui.widget

import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.util.TypedValue
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.View
import android.view.animation.DecelerateInterpolator
import androidx.core.content.ContextCompat
import com.kaixed.kchat.R

/**
 * @Author: 7heaven
 * @Date: 2024/10/23 19:29
 */
class ShSwitchView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) :
    View(context, attrs, defStyle) {
    private var innerContentAnimator: ValueAnimator? = null
    private var knobExpandAnimator: ValueAnimator? = null
    private var knobMoveAnimator: ValueAnimator? = null

    private val gestureDetector: GestureDetector
    private val gestureListener: SimpleOnGestureListener = object : SimpleOnGestureListener() {
        override fun onDown(event: MotionEvent): Boolean {
            if (!isEnabled) {
                return false
            }

            preIsOn = isOn

            innerContentAnimator!!.setFloatValues(innerContentRate, 0.0f)
            innerContentAnimator!!.start()

            knobExpandAnimator!!.setFloatValues(knobExpandRate, 1.0f)
            knobExpandAnimator!!.start()

            return true
        }

        override fun onShowPress(event: MotionEvent) {
        }

        override fun onSingleTapUp(event: MotionEvent): Boolean {
            isOn = knobState

            if (preIsOn == isOn) {
                isOn = !isOn
                knobState = !knobState
            }

            if (knobState) {
                knobMoveAnimator!!.setFloatValues(knobMoveRate, 1.0f)
                knobMoveAnimator!!.start()

                innerContentAnimator!!.setFloatValues(innerContentRate, 0.0f)
                innerContentAnimator!!.start()
            } else {
                knobMoveAnimator!!.setFloatValues(knobMoveRate, 0.0f)
                knobMoveAnimator!!.start()

                innerContentAnimator!!.setFloatValues(innerContentRate, 1.0f)
                innerContentAnimator!!.start()
            }

            knobExpandAnimator!!.setFloatValues(knobExpandRate, 0.0f)
            knobExpandAnimator!!.start()

            if (this@ShSwitchView.onSwitchStateChangeListener != null && isOn != preIsOn) {
                onSwitchStateChangeListener!!.onSwitchStateChange(isOn)
            }

            return true
        }

        override fun onScroll(
            e1: MotionEvent?,
            e2: MotionEvent,
            distanceX: Float,
            distanceY: Float
        ): Boolean {
            if (e2.x > centerX) {
                if (!knobState) {
                    knobState = !knobState

                    knobMoveAnimator!!.setFloatValues(knobMoveRate, 1.0f)
                    knobMoveAnimator!!.start()

                    innerContentAnimator!!.setFloatValues(innerContentRate, 0.0f)
                    innerContentAnimator!!.start()
                }
            } else {
                if (knobState) {
                    knobState = !knobState

                    knobMoveAnimator!!.setFloatValues(knobMoveRate, 0.0f)
                    knobMoveAnimator!!.start()
                }
            }
            return true
        }
    }

    private var width = 0
    private var height = 0

    private var centerX = 0
    private var centerY = 0

    private var cornerRadius = 0f

    private val shadowSpace: Int
    private val outerStrokeWidth: Int

    private val shadowDrawable: Drawable

    private val knobBound: RectF
    private var knobMaxExpandWidth = 0f
    private var intrinsicKnobWidth = 0f
    private var knobExpandRate = 0f
    private var knobMoveRate = 0f

    private var knobState = false
    private var isOn = false
    private var preIsOn = false

    private val innerContentBound: RectF
    private var innerContentRate = 1.0f
    private var intrinsicInnerWidth = 0f
    private var intrinsicInnerHeight = 0f

    private var tintColor: Int

    private var tempTintColor: Int

    private var colorStep = backgroundColor
    private val paint: Paint

    private val ovalForPath: RectF
    private val roundRectPath: Path

    private val tempForRoundRect: RectF

    private var dirtyAnimation = false
    private var isAttachedToWindow = false

    interface OnSwitchStateChangeListener {
        fun onSwitchStateChange(isOn: Boolean)
    }

    var onSwitchStateChangeListener: OnSwitchStateChangeListener? = null

    init {
        val ta = context.obtainStyledAttributes(attrs, R.styleable.ShSwitchView)

        tintColor = ta.getColor(R.styleable.ShSwitchView_tintColor, -0x6316b7)
        tempTintColor = tintColor

        val defaultOuterStrokeWidth = TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            1.5f,
            context.resources.displayMetrics
        ).toInt()
        val defaultShadowSpace = TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            5f,
            context.resources.displayMetrics
        ).toInt()

        outerStrokeWidth = ta.getDimensionPixelOffset(
            R.styleable.ShSwitchView_outerStrokeWidth,
            defaultOuterStrokeWidth
        )
        shadowSpace =
            ta.getDimensionPixelOffset(R.styleable.ShSwitchView_shadowSpace, defaultShadowSpace)

        ta.recycle()

        knobBound = RectF()
        innerContentBound = RectF()
        ovalForPath = RectF()

        tempForRoundRect = RectF()

        paint = Paint(Paint.ANTI_ALIAS_FLAG)
        roundRectPath = Path()

        gestureDetector = GestureDetector(context, gestureListener)
        gestureDetector.setIsLongpressEnabled(false)

        setLayerType(LAYER_TYPE_SOFTWARE, null)

        initAnimators()

        shadowDrawable = ContextCompat.getDrawable(context, R.drawable.shadow)!!

    }

    private fun initAnimators() {
        innerContentAnimator = ValueAnimator.ofFloat(innerContentRate, 1.0f).apply {
            duration = COMMON_DURATION
            interpolator = DecelerateInterpolator()
            addUpdateListener { animation ->
                (animation as? ValueAnimator)?.let {
                    setInnerContentRate(it.animatedValue as Float)
                }
            }
        }

        knobExpandAnimator = ValueAnimator.ofFloat(knobExpandRate, 1.0f).apply {
            duration = COMMON_DURATION
            interpolator = DecelerateInterpolator()
            addUpdateListener { animation ->
                (animation as? ValueAnimator)?.let {
                    setKnobExpandRate(it.animatedValue as Float)
                }
            }
        }

        knobMoveAnimator = ValueAnimator.ofFloat(knobMoveRate, 1.0f).apply {
            duration = COMMON_DURATION
            interpolator = DecelerateInterpolator()
            addUpdateListener { animation ->
                (animation as? ValueAnimator)?.let {
                    setKnobMoveRate(it.animatedValue as Float)
                }
            }
        }
    }


    fun setInnerContentRate(rate: Float) {
        this.innerContentRate = rate

        invalidate()
    }

    fun getInnerContentRate(): Float {
        return this.innerContentRate
    }

    fun setKnobExpandRate(rate: Float) {
        this.knobExpandRate = rate

        invalidate()
    }

    fun getKnobExpandRate(): Float {
        return this.knobExpandRate
    }

    fun setKnobMoveRate(rate: Float) {
        this.knobMoveRate = rate

        invalidate()
    }

    fun getKnobMoveRate(): Float {
        return knobMoveRate
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        isAttachedToWindow = true

        if (dirtyAnimation) {
            knobState = this.isOn
            if (knobState) {
                knobMoveAnimator!!.setFloatValues(knobMoveRate, 1.0f)
                knobMoveAnimator!!.start()

                innerContentAnimator!!.setFloatValues(innerContentRate, 0.0f)
                innerContentAnimator!!.start()
            } else {
                knobMoveAnimator!!.setFloatValues(knobMoveRate, 0.0f)
                knobMoveAnimator!!.start()

                innerContentAnimator!!.setFloatValues(innerContentRate, 1.0f)
                innerContentAnimator!!.start()
            }

            knobExpandAnimator!!.setFloatValues(knobExpandRate, 0.0f)
            knobExpandAnimator!!.start()

            if (this@ShSwitchView.onSwitchStateChangeListener != null && isOn != preIsOn) {
                onSwitchStateChangeListener!!.onSwitchStateChange(isOn)
            }

            dirtyAnimation = false
        }
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        isAttachedToWindow = false
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var widthMeasureSpec = widthMeasureSpec
        var heightMeasureSpec = heightMeasureSpec
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        width = MeasureSpec.getSize(widthMeasureSpec)
        height = MeasureSpec.getSize(heightMeasureSpec)

        //make sure widget remain in a good appearance
        if (height.toFloat() / width.toFloat() < 0.33333f) {
            height = (width.toFloat() * 0.33333f).toInt()

            widthMeasureSpec =
                MeasureSpec.makeMeasureSpec(width, MeasureSpec.getMode(widthMeasureSpec))
            heightMeasureSpec =
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec))

            super.setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
        }

        centerX = width / 2
        centerY = height / 2

        cornerRadius = (centerY - shadowSpace).toFloat()

        innerContentBound.left = (outerStrokeWidth + shadowSpace).toFloat()
        innerContentBound.top = (outerStrokeWidth + shadowSpace).toFloat()
        innerContentBound.right = (width - outerStrokeWidth - shadowSpace).toFloat()
        innerContentBound.bottom = (height - outerStrokeWidth - shadowSpace).toFloat()

        intrinsicInnerWidth = innerContentBound.width()
        intrinsicInnerHeight = innerContentBound.height()

        knobBound.left = (outerStrokeWidth + shadowSpace).toFloat()
        knobBound.top = (outerStrokeWidth + shadowSpace).toFloat()
        knobBound.right = (height - outerStrokeWidth - shadowSpace).toFloat()
        knobBound.bottom = (height - outerStrokeWidth - shadowSpace).toFloat()

        intrinsicKnobWidth = knobBound.height()
        knobMaxExpandWidth = width.toFloat() * 0.7f
        if (knobMaxExpandWidth > knobBound.width() * 1.25f) {
            knobMaxExpandWidth = knobBound.width() * 1.25f
        }
    }

    fun isOn(): Boolean {
        return this.isOn
    }

    fun setOn(on: Boolean) {
        setOn(on, false)
    }

    fun setOn(on: Boolean, animated: Boolean) {
        if (this.isOn == on) {
            return
        }

        if (!isAttachedToWindow && animated) {
            dirtyAnimation = true
            this.isOn = on

            return
        }

        this.isOn = on
        knobState = this.isOn

        if (!animated) {
            if (on) {
                setKnobMoveRate(1.0f)
                setInnerContentRate(0.0f)
            } else {
                setKnobMoveRate(0.0f)
                setInnerContentRate(1.0f)
            }

            setKnobExpandRate(0.0f)
        } else {
            if (knobState) {
                knobMoveAnimator!!.setFloatValues(knobMoveRate, 1.0f)
                knobMoveAnimator!!.start()

                innerContentAnimator!!.setFloatValues(innerContentRate, 0.0f)
                innerContentAnimator!!.start()
            } else {
                knobMoveAnimator!!.setFloatValues(knobMoveRate, 0.0f)
                knobMoveAnimator!!.start()

                innerContentAnimator!!.setFloatValues(innerContentRate, 1.0f)
                innerContentAnimator!!.start()
            }

            knobExpandAnimator!!.setFloatValues(knobExpandRate, 0.0f)
            knobExpandAnimator!!.start()
        }

        if (this@ShSwitchView.onSwitchStateChangeListener != null && isOn != preIsOn) {
            onSwitchStateChangeListener!!.onSwitchStateChange(isOn)
        }
    }

    fun setTintColor(tintColor: Int) {
        this.tintColor = tintColor
        tempTintColor = this.tintColor
    }

    fun getTintColor(): Int {
        return this.tintColor
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (!isEnabled) {
            return false
        }

        when (event.action) {
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                if (!knobState) {
                    innerContentAnimator!!.setFloatValues(innerContentRate, 1.0f)

                    innerContentAnimator!!.start()
                }

                knobExpandAnimator!!.setFloatValues(knobExpandRate, 0.0f)

                knobExpandAnimator!!.start()

                isOn = knobState

                if (this@ShSwitchView.onSwitchStateChangeListener != null && isOn != preIsOn) {
                    onSwitchStateChangeListener!!.onSwitchStateChange(isOn)
                }
            }
        }
        return gestureDetector.onTouchEvent(event)
    }

    override fun setEnabled(enabled: Boolean) {
        super.setEnabled(enabled)

        if (enabled) {
            this.tintColor = tempTintColor
        } else {
            this.tintColor = this.RGBColorTransform(0.5f, tempTintColor, -0x1)
        }
    }

    public override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        //innerContentCalculation begin
        var w = intrinsicInnerWidth / 2.0f * innerContentRate
        val h = intrinsicInnerHeight / 2.0f * innerContentRate

        innerContentBound.left = centerX - w
        innerContentBound.top = centerY - h
        innerContentBound.right = centerX + w
        innerContentBound.bottom = centerY + h

        //innerContentCalculation end

        //knobExpandCalculation begin
        w = intrinsicKnobWidth + (knobMaxExpandWidth - intrinsicKnobWidth) * knobExpandRate

        val left = knobBound.left + knobBound.width() / 2 > centerX

        if (left) {
            knobBound.left = knobBound.right - w
        } else {
            knobBound.right = knobBound.left + w
        }

        //knobExpandCalculation end

        //knobMoveCalculation begin
        val kw = knobBound.width()
        w = (width - kw - ((shadowSpace + outerStrokeWidth) * 2)) * knobMoveRate

        this.colorStep = RGBColorTransform(knobMoveRate, backgroundColor, tintColor)


        knobBound.left = shadowSpace + outerStrokeWidth + w
        knobBound.right = knobBound.left + kw

        //knobMoveCalculation end

        //background
        paint.color = colorStep
        paint.style = Paint.Style.FILL

        drawRoundRect(
            shadowSpace.toFloat(),
            shadowSpace.toFloat(),
            (width - shadowSpace).toFloat(),
            (height - shadowSpace).toFloat(),
            cornerRadius,
            canvas,
            paint
        )

        //innerContent
        paint.color = Color.WHITE
        canvas.drawRoundRect(
            innerContentBound,
            innerContentBound.height() / 2,
            innerContentBound.height() / 2,
            paint
        )

        //knob
//        shadowDrawable.setBounds((int) (knobBound.left - shadowSpace), (int) (knobBound.top - shadowSpace), (int) (knobBound.right + shadowSpace), (int) (knobBound.bottom + shadowSpace));
//        shadowDrawable.draw(canvas);
        paint.setShadowLayer(
            2f,
            0f,
            (shadowSpace / 2).toFloat(),
            if (isEnabled) 0x20000000 else 0x10000000
        )

        //        paint.setColor(isEnabled() ? 0x20000000 : 0x10000000);
//        drawRoundRect(knobBound.left, knobBound.top + shadowSpace / 2, knobBound.right, knobBound.bottom + shadowSpace / 2, cornerRadius - outerStrokeWidth, canvas, paint);
//
//        paint.setColor(foregroundColor);
        canvas.drawRoundRect(
            knobBound,
            cornerRadius - outerStrokeWidth,
            cornerRadius - outerStrokeWidth,
            paint
        )
        paint.setShadowLayer(0f, 0f, 0f, 0)

        paint.color = backgroundColor
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 1f

        canvas.drawRoundRect(
            knobBound,
            cornerRadius - outerStrokeWidth,
            cornerRadius - outerStrokeWidth,
            paint
        )
    }

    private fun drawRoundRect(
        left: Float,
        top: Float,
        right: Float,
        bottom: Float,
        radius: Float,
        canvas: Canvas,
        paint: Paint
    ) {
        tempForRoundRect.left = left
        tempForRoundRect.top = top
        tempForRoundRect.right = right
        tempForRoundRect.bottom = bottom

        canvas.drawRoundRect(tempForRoundRect, radius, radius, paint)
    }

    //seperate RGB channels and calculate new value for each channel
    //ignore alpha channel
    private fun RGBColorTransform(progress: Float, fromColor: Int, toColor: Int): Int {
        val or = (fromColor shr 16) and 0xFF
        val og = (fromColor shr 8) and 0xFF
        val ob = fromColor and 0xFF

        val nr = (toColor shr 16) and 0xFF
        val ng = (toColor shr 8) and 0xFF
        val nb = toColor and 0xFF

        val rGap = ((nr - or).toFloat() * progress).toInt()
        val gGap = ((ng - og).toFloat() * progress).toInt()
        val bGap = ((nb - ob).toFloat() * progress).toInt()

        return -0x1000000 or ((or + rGap) shl 16) or ((og + gGap) shl 8) or (ob + bGap)
    }

    companion object {
        private const val COMMON_DURATION = 300L

        private const val intrinsicWidth = 0
        private const val intrinsicHeight = 0

        private const val backgroundColor = -0x333334
        private const val foregroundColor = -0xa0a0b
    }
}
