package com.adealink.weparty.commonui.imageview

import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.RadialGradient
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Shader
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.util.Log
import android.widget.ImageView
import com.adealink.frame.util.runOnUiThread
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.cos
import kotlin.math.sin

class CircleDrawable(
// bitmap
    val contentWidth: Int, val contentHeight: Int,
) :
    Drawable(), Runnable {

    // private final ContentShader mContentShader;
    private val mShaderMatrix = Matrix()
    private val mContentPaint: Paint
    private val mContentRectF = RectF()
    private val mDrawableRectF = RectF()

    // 外边距
    var outerBorderColor = DEFAULT_OUTER_BORDER_COLOR
        private set
    private var mOuterBorderWidth = 0
    private val mOuterBorderRectF = RectF()

    // 内边距
    private var mInnerBorderWidth = 0
    var innerBorderColor = DEFAULT_INNER_BORDER_COLOR
        private set
    private val mInnerBorderRectF = RectF()
    private var mInnerBorderAlpha = 1.0f
    private var mEnableInnerBorder = false
    private var mShowInnerBorderAlways = false
    private val mBorderPaint: Paint
    private val mBounds = RectF()
    var scaleType = ImageView.ScaleType.CENTER_CROP
        private set
    private var mOuterBorderShader: Shader? = null

    // private Path mOuterBorderShaderPath;
    private var mOuterBorderPaint: Paint? = null
    private var mShaderOuterBorder = false
    private var mEnableOuterBorder = false
    private var mRippleWidth = 0f
    private var mRippleSpace = 0f
    private var mRippleColor = 0
    private var mRippleSpeed: Long = 0
    private var mRippleStartAlphaDuration: Long = 0
    private var mRippleEndAlphaDuration: Long = 0
    private var mRipple: Ripple? = null
    private var mInnerBorderAnimator: InnerBorderAnimator? = null
    private var mStartRipple = false
    private var mStopRipple = false
    override fun draw(canvas: Canvas) {
        // 画外边距
        drawOuterBorder(canvas)


        // 画波纹
        drawRipple(canvas)

        // 画内边距
        drawInnerBorder(canvas)
    }

    override fun setAlpha(alpha: Int) {}
    override fun setColorFilter(cf: ColorFilter?) {}
    private fun drawOuterBorder(canvas: Canvas) {
        if (!mEnableOuterBorder) {
            return
        }
        if (mShaderOuterBorder) {
            if (mOuterBorderPaint == null) {
                //正常情况下，mOuterBorderPaint不会为空
                //如果draw()方法比onBoundChange()早回调，就回导致mOuterBorderPaint没有初始化，出现这种异常case
                //目前只出现1例，加个空保护
                Log.e(TAG, "drawOuterBorder when outerBorderPaint is null, intercept.")
                return
            }
            canvas.save()
            // canvas.clipPath(mOuterBorderShaderPath, Region.Op.XOR);
            canvas.drawCircle(mBounds.centerX(),
                mBounds.centerY(),
                (mBounds.width() / 2).coerceAtMost(mBounds.height() / 2),
                mOuterBorderPaint!!)
            canvas.restore()
        } else {
            if (mOuterBorderWidth > 0) {
                mBorderPaint.strokeWidth = mOuterBorderWidth.toFloat()
                mBorderPaint.color = outerBorderColor
                canvas.drawCircle(mOuterBorderRectF.centerX(),
                    mOuterBorderRectF.centerY(),
                    mOuterBorderRectF.width().coerceAtMost(mOuterBorderRectF.height()) / 2,
                    mBorderPaint)
            }
        }
    }

    private fun drawInnerBorder(canvas: Canvas) {
        if (!mShowInnerBorderAlways) {
            if (!mEnableInnerBorder) {
                return
            }
        }
        if (mInnerBorderWidth > 0) {
            mBorderPaint.strokeWidth = mInnerBorderWidth.toFloat()
            mBorderPaint.color = innerBorderColor
            if (mEnableInnerBorder) {
                if (mInnerBorderAnimator != null && mInnerBorderAnimator!!.isAlphaing) {
                    mBorderPaint.alpha = (255 * mInnerBorderAnimator!!.alpha).toInt()
                    mInnerBorderAnimator!!.updateAlpha()
                } else {
                    mBorderPaint.alpha = (255 * mInnerBorderAlpha).toInt()
                }
            } else {
                mBorderPaint.alpha = 255
            }
            canvas.drawCircle(mInnerBorderRectF.centerX(),
                mInnerBorderRectF.centerY(),
                mInnerBorderRectF.width().coerceAtMost(mInnerBorderRectF.height()) / 2,
                mBorderPaint)
        }
    }

    private fun drawRipple(canvas: Canvas) {
        if (mRipple != null) {
            mRipple!!.draw(canvas)
            runOnUiThread({ invalidateSelf() }, 10L)
        }
    }

    override fun getIntrinsicWidth(): Int {
        return contentWidth
    }

    override fun getIntrinsicHeight(): Int {
        return contentHeight
    }

    val outerBorderWidth: Float
        get() = mOuterBorderWidth.toFloat()

    fun setOuterBorderWidth(width: Int): CircleDrawable {
        mOuterBorderWidth = width
        return this
    }

    val innerBorderWidth: Float
        get() = mInnerBorderWidth.toFloat()

    fun setInnerBorderWidth(width: Int): CircleDrawable {
        mInnerBorderWidth = width
        return this
    }

    fun setOuterBorderColor(color: Int): CircleDrawable {
        outerBorderColor = color
        return this
    }

    fun setInnerBorderColor(color: Int): CircleDrawable {
        innerBorderColor = color
        return this
    }

    fun setScaleType(scaleType: ImageView.ScaleType?): CircleDrawable {
        var scaleType = scaleType
        if (scaleType == null) {
            scaleType = ImageView.ScaleType.FIT_XY
        }
        validScaleType(scaleType)
        if (this.scaleType != scaleType) {
            this.scaleType = scaleType
            updateShaderMatrix()
        }
        return this
    }

    private fun validScaleType(scaleType: ImageView.ScaleType) {
        when (this.scaleType) {
            ImageView.ScaleType.CENTER, ImageView.ScaleType.CENTER_CROP, ImageView.ScaleType.FIT_XY -> {
            }
            else -> throw IllegalArgumentException("scaleType " + this.scaleType + ", is not suport")
        }
    }

    override fun getOpacity(): Int {
        return PixelFormat.TRANSLUCENT
    }

    override fun onBoundsChange(bounds: Rect) {
        super.onBoundsChange(bounds)
        mBounds.set(bounds)
        updateShaderMatrix()
        updateOuterBorderShader()
    }

    private fun updateShaderMatrix() {
        val scale: Float
        var dx: Float
        var dy: Float
        when (scaleType) {
            ImageView.ScaleType.CENTER -> {
                mOuterBorderRectF.set(mBounds)
                mOuterBorderRectF.inset((mOuterBorderWidth / 2).toFloat(),
                    (mOuterBorderWidth / 2).toFloat())
                mInnerBorderRectF.set(mBounds)
                mInnerBorderRectF.inset((mOuterBorderWidth - mInnerBorderWidth / 2).toFloat(),
                    (mOuterBorderWidth - mInnerBorderWidth / 2).toFloat())
                mDrawableRectF.set(mBounds)
                mDrawableRectF.inset(mOuterBorderWidth.toFloat(), mOuterBorderWidth.toFloat())
                mShaderMatrix.set(null)
                mShaderMatrix.setTranslate(((mDrawableRectF.width() - contentWidth) * 0.5f + 0.5f),
                    ((mDrawableRectF.height() - contentHeight) * 0.5f + 0.5f))
            }
            ImageView.ScaleType.CENTER_CROP -> {
                mOuterBorderRectF.set(mBounds)
                mOuterBorderRectF.inset((mOuterBorderWidth / 2).toFloat(),
                    (mOuterBorderWidth / 2).toFloat())
                mInnerBorderRectF.set(mBounds)
                mInnerBorderRectF.inset((mOuterBorderWidth - mInnerBorderWidth / 2).toFloat(),
                    (mOuterBorderWidth - mInnerBorderWidth / 2).toFloat())
                mDrawableRectF.set(mBounds)
                mDrawableRectF.inset(mOuterBorderWidth.toFloat(), mOuterBorderWidth.toFloat())
                mShaderMatrix.set(null)
                dx = 0f
                dy = 0f
                if (contentWidth * mDrawableRectF.height() > mDrawableRectF.width() * contentHeight) {
                    scale = mDrawableRectF.height() / contentHeight.toFloat()
                    dx = (mDrawableRectF.width() - contentWidth * scale) * 0.5f
                } else {
                    scale = mDrawableRectF.width() / contentWidth.toFloat()
                    dy = (mDrawableRectF.height() - contentHeight * scale) * 0.5f
                }
                mShaderMatrix.setScale(scale, scale)
                mShaderMatrix.postTranslate(((dx + 0.5f).toInt() + mOuterBorderWidth).toFloat(),
                    ((dy + 0.5f).toInt() + mOuterBorderWidth).toFloat())
            }
            ImageView.ScaleType.FIT_XY -> {
                mOuterBorderRectF.set(mBounds)
                mOuterBorderRectF.inset((mOuterBorderWidth / 2).toFloat(),
                    (mOuterBorderWidth / 2).toFloat())
                mInnerBorderRectF.set(mBounds)
                mInnerBorderRectF.inset((mOuterBorderWidth - mInnerBorderWidth / 2).toFloat(),
                    (mOuterBorderWidth - mInnerBorderWidth / 2).toFloat())
                mDrawableRectF.set(mBounds)
                mDrawableRectF.inset(mOuterBorderWidth.toFloat(), mOuterBorderWidth.toFloat())
                mShaderMatrix.set(null)
                mShaderMatrix.setRectToRect(mContentRectF, mDrawableRectF, Matrix.ScaleToFit.FILL)
            }
            else -> throw IllegalArgumentException("scaleType $scaleType, is not suport")
        }

        // mBitmapShader.setLocalMatrix(mShaderMatrix);
    }

    private fun updateOuterBorderShader() {
        if (!mShaderOuterBorder) {
            return
        }
        val transpaentColor = outerBorderColor and 0x00ffffff
        val radius = Math.min(mBounds.width() / 2, mBounds.height() / 2) - mOuterBorderWidth / 2
        if (radius > 0) {
            mOuterBorderShader = RadialGradient(mBounds.centerX(),
                mBounds.centerY(),
                radius,
                intArrayOf(transpaentColor, transpaentColor,
                    outerBorderColor,
                    transpaentColor),
                floatArrayOf(0.0f,
                    (radius - mOuterBorderWidth - 0.5f) / radius,
                    (radius - mOuterBorderWidth) / radius,
                    1.0f),
                Shader.TileMode.CLAMP)
        }
        // mOuterBorderShaderPath = new Path();
        // mOuterBorderShaderPath.addCircle(mBounds.centerX(),
        // mBounds.centerY(), mDrawableRectF.width() / 2, Direction.CCW);
        if (mOuterBorderPaint == null) {
            mOuterBorderPaint = Paint()
            mOuterBorderPaint!!.style = Paint.Style.FILL_AND_STROKE
            mOuterBorderPaint!!.isAntiAlias = true
            mOuterBorderPaint!!.strokeWidth = 0f
        }
        mOuterBorderPaint!!.shader = mOuterBorderShader
    }

    fun setShaderOuterBorder(shader: Boolean): CircleDrawable {
        mShaderOuterBorder = shader
        return this
    }

    fun setRippleWidth(rippleWidth: Float): CircleDrawable {
        mRippleWidth = rippleWidth
        return this
    }

    fun setRippleSpace(rippleSpace: Float): CircleDrawable {
        mRippleSpace = rippleSpace
        return this
    }

    val rippleColor: Float
        get() = mRippleColor.toFloat()

    fun setRippleColor(rippleColor: Int): CircleDrawable {
        mRippleColor = rippleColor
        return this
    }

    fun setRippleSpeed(rippleSpeed: Long): CircleDrawable {
        mRippleSpeed = rippleSpeed
        return this
    }

    fun setRippleStartAlphaDuration(rippleStartAlphaDuration: Long): CircleDrawable {
        mRippleStartAlphaDuration = rippleStartAlphaDuration
        return this
    }

    fun setRippleEndAlphaDuration(rippleEndAlphaDuration: Long): CircleDrawable {
        mRippleEndAlphaDuration = rippleEndAlphaDuration
        return this
    }

    fun showRipple() {
        mEnableInnerBorder = true
    }

    fun hideRipple() {
        mEnableInnerBorder = false
    }

    @Synchronized
    fun startRipple() {
        if (isRippleStarted) {
            return
        }
        if (mStopRipple && mRipple != null) {
            mRipple = null
        }
        mInnerBorderAnimator = InnerBorderAnimator(mRippleStartAlphaDuration, 0)
        mInnerBorderAnimator!!.start()
        mEnableOuterBorder = true
        mEnableInnerBorder = true
        mStartRipple = true
        mStopRipple = false
    }

    @Synchronized
    fun stopRipple() {
        if (!isRippleStarted) {
            return
        }
        mStopRipple = true
        stopRippleInternal()
    }

    private fun startRippleInternal() {
        if (mStopRipple) {
            onRippleEnd()
            return
        }
        mRipple = Ripple(mDrawableRectF.width() / 2 + mInnerBorderWidth,
            mBounds.width() / 2 - mRippleWidth,
            mRippleWidth,
            mRippleSpace,
            mRippleSpeed,
            mRippleColor)
        mRipple!!.start()
    }

    private fun stopRippleInternal() {
        if (mRipple != null) {
            mRipple!!.stop()
        }
    }

    val isRippleStarted: Boolean
        get() = mStartRipple && !mStopRipple

    private fun onStartAlphaEnd() {
        startRippleInternal()
    }

    private fun onEndAlphaEnd() {
        mEnableOuterBorder = false
        mEnableInnerBorder = false
        mStartRipple = false
        mStopRipple = false
    }

    private fun onRippleEnd() {
        mRipple = null
        mInnerBorderAnimator = InnerBorderAnimator(mRippleEndAlphaDuration, 1)
        mInnerBorderAnimator!!.start()
    }

    fun setShowInnerBorderAlways(showAlways: Boolean) {
        mShowInnerBorderAlways = showAlways
    }

    override fun run() {
        invalidateSelf()
    }

    private inner class InnerBorderAnimator(duration: Long, type: Int) {
        var mStartTime: Long = 0
        var mDuration: Long
        var alpha: Float
        var mType // 0 开始 1 结束
                : Int
        var isAlphaing = false

        fun start() {
            mStartTime = System.currentTimeMillis()
            isAlphaing = true
            invalidateSelf()
        }

        fun updateAlpha() {
            val dtTime = System.currentTimeMillis() - mStartTime
            if (mType == 0) {
                if (dtTime > mDuration) {
                    alpha = 1.0f
                    mInnerBorderAlpha = alpha
                    onStartAlphaEnd()
                    isAlphaing = false
                    return
                }
                val angel = (270 * dtTime / mDuration * Math.PI / 180).toFloat()
                alpha = abs(sin(angel.toDouble())).toFloat()
                invalidateSelf()
            } else {
                if (dtTime > mDuration) {
                    alpha = 0.0f
                    mInnerBorderAlpha = alpha
                    onEndAlphaEnd()
                    isAlphaing = false
                    invalidateSelf()
                    return
                }
                val angel = (90 * dtTime / mDuration * Math.PI / 180).toFloat()
                alpha = cos(angel.toDouble()).toFloat()
                invalidateSelf()
            }
        }

        init {
            alpha = if (type == 0) 0.0f else 1.0f
            mDuration = duration
            mType = type
        }
    }

    private inner class Ripple internal constructor(
// 波纹开始半径
        var mStartRadius: Float,
        endRadius: Float,
        rippleWidth: Float,
        rippleSpace: Float,
        speadPeriod: Long,
        rippleColor: Int,
    ) {
        //		float mEndRadius;
        var mSpeadPeriod = Companion.DEFAULT_RERIOD // 间隔
            .toLong()
        var mPeriodDistance: Float
        var mStartTime: Long = 0
        var mEndTime: Long = 0
        var mDtRadius: Float
        var mPaint = Paint()
        var mRippleColor: Int
        var mRippleWidth: Float
        var mStatus // 0 开始 1 结束
                = 0

        fun start() {
            mStatus = 0
            mStartTime = System.currentTimeMillis()
            invalidateSelf()
        }

        fun stop() {
            mStatus = 1
            mEndTime = System.currentTimeMillis()
        }

        val isStart: Boolean
            get() = mStartTime > 0

        fun draw(canvas: Canvas) {
            if (!isStart) {
                return
            }
            val currentTime = System.currentTimeMillis()
            val dtTime: Long = ((currentTime - mStartTime) % mSpeadPeriod)
            val round =
                ceil(((currentTime - mStartTime).toFloat() / mSpeadPeriod).toDouble()).toInt()
            var index = 0
            val startDistance = dtTime.toFloat() / mSpeadPeriod * mPeriodDistance
            var radius = 0F
            if (mStatus == 0) {
                // 画每一轮
                while (index < round && getRoundDistance(index++, startDistance).also {
                        radius = it
                    } <= mDtRadius) {
                    drawSingleRipple(canvas,
                        radius / mDtRadius,
                        radius + mStartRadius + mRippleWidth / 2)
                }
            } else {
                // 画每一轮
                index = (mDtRadius / mPeriodDistance).coerceAtMost(round.toFloat()).toInt()
                val endDtTime = currentTime - mEndTime
                val minDistance = endDtTime * mPeriodDistance / mSpeadPeriod
                if (minDistance >= mDtRadius) {
                    onRippleEnd()
                    return
                }
                while (index >= 0 && getRoundDistance(--index, startDistance).also {
                        radius = it
                    } >= minDistance) {
                    drawSingleRipple(canvas,
                        radius / mDtRadius,
                        radius + mStartRadius + mRippleWidth / 2)
                }
            }
        }

        fun getRoundDistance(index: Int, startDistance: Float): Float {
            return startDistance + index * mPeriodDistance
        }

        fun drawSingleRipple(canvas: Canvas, radio: Float, radius: Float) {
            val alpha = (255 * (1 - radio)).toInt()
            mPaint.alpha = alpha
            mPaint.strokeWidth = mRippleWidth
            canvas.drawCircle(mBounds.centerX(), mBounds.centerY(), radius, mPaint)
        }

        init {
            mDtRadius = endRadius - mStartRadius
            mSpeadPeriod = speadPeriod
            mRippleWidth = rippleWidth
            mRippleColor = rippleColor
            mPeriodDistance = rippleSpace
            mPaint.style = Paint.Style.STROKE
            mPaint.isAntiAlias = true
            mPaint.color = mRippleColor
        }
    }

    companion object {
        const val DEFAULT_RERIOD = 1000
        const val DEFAULT_OUTER_BORDER_COLOR = 0x1f1AE3ED
        const val DEFAULT_INNER_BORDER_COLOR = -0x39bd60

        const val TAG = "CircleDrawable"

        fun fromSize(width: Int, height: Int): CircleDrawable {
            return CircleDrawable(width, height)
        }

        fun drawableToBitmap(drawable: Drawable): Bitmap? {
            if (drawable is BitmapDrawable) {
                return drawable.bitmap
            }
            var bitmap: Bitmap?
            val width = drawable.intrinsicWidth.coerceAtLeast(1)
            val height = drawable.intrinsicHeight.coerceAtLeast(1)
            try {
                bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
                val canvas = Canvas(bitmap)
                drawable.setBounds(0, 0, canvas.width, canvas.height)
                drawable.draw(canvas)
            } catch (e: Exception) {
                e.printStackTrace()
                bitmap = null
            }
            return bitmap
        }
    }

    init {
        mContentRectF[0f, 0f, contentWidth.toFloat()] = contentHeight.toFloat()
        mContentPaint = Paint()
        mContentPaint.style = Paint.Style.FILL
        mContentPaint.isAntiAlias = true
        mBorderPaint = Paint()
        mBorderPaint.style = Paint.Style.STROKE
        mBorderPaint.isAntiAlias = true
    }
}