/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.systemui.biometrics

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Point
import android.util.AttributeSet
import android.view.View
import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils
import com.android.app.animation.Interpolators
import com.android.systemui.surfaceeffects.ripple.RippleShader

private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f

/**
 * Handles two ripple effects: dwell ripple and unlocked ripple
 * Dwell Ripple:
 *     - startDwellRipple: dwell ripple expands outwards around the biometric area
 *     - retractDwellRipple: retracts the dwell ripple to radius 0 to signal a failure
 *     - fadeDwellRipple: fades the dwell ripple away to alpha 0
 * Unlocked ripple:
 *     - startUnlockedRipple: ripple expands from biometric auth location to the edges of the screen
 */
class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f)

    private val dwellPulseDuration = 100L
    private val dwellExpandDuration = 2000L - dwellPulseDuration

    private var drawDwell: Boolean = false
    private var drawRipple: Boolean = false

    private var lockScreenColorVal = Color.WHITE
    private val fadeDuration = 83L
    private val retractDuration = 400L
    private val dwellShader = DwellRippleShader()
    private val dwellPaint = Paint()
    private val rippleShader = RippleShader()
    private val ripplePaint = Paint()
    private var unlockedRippleAnimator: Animator? = null
    private var fadeDwellAnimator: Animator? = null
    private var retractDwellAnimator: Animator? = null
    private var dwellPulseOutAnimator: Animator? = null
    private var dwellRadius: Float = 0f
        set(value) {
            dwellShader.maxRadius = value
            field = value
        }
    private var dwellOrigin: Point = Point()
        set(value) {
            dwellShader.origin = value
            field = value
        }
    private var radius: Float = 0f
        set(value) {
            field = value * .9f
            rippleShader.rippleSize.setMaxSize(field * 2f, field * 2f)
        }
    private var origin: Point = Point()
        set(value) {
            rippleShader.setCenter(value.x.toFloat(), value.y.toFloat())
            field = value
        }

    init {
        rippleShader.rawProgress = 0f
        rippleShader.pixelDensity = resources.displayMetrics.density
        rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
        updateRippleFadeParams()
        ripplePaint.shader = rippleShader
        setLockScreenColor(0xffffffff.toInt()) // default color

        dwellShader.color = 0xffffffff.toInt() // default color
        dwellShader.progress = 0f
        dwellShader.distortionStrength = .4f
        dwellPaint.shader = dwellShader
        visibility = GONE
    }

    fun setSensorLocation(location: Point) {
        origin = location
        radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat()
    }

    fun setFingerprintSensorLocation(location: Point, sensorRadius: Float) {
        origin = location
        radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat()
        dwellOrigin = location
        dwellRadius = sensorRadius * 1.5f
    }

    /**
     * Animate dwell ripple inwards back to radius 0
     */
    fun retractDwellRipple() {
        if (retractDwellAnimator?.isRunning == true || fadeDwellAnimator?.isRunning == true) {
            return // let the animation finish
        }

        if (dwellPulseOutAnimator?.isRunning == true) {
            val retractDwellRippleAnimator = ValueAnimator.ofFloat(dwellShader.progress, 0f)
                    .apply {
                interpolator = retractInterpolator
                duration = retractDuration
                addUpdateListener { animator ->
                    val now = animator.currentPlayTime
                    dwellShader.progress = animator.animatedValue as Float
                    dwellShader.time = now.toFloat()

                    invalidate()
                }
            }

            val retractAlphaAnimator = ValueAnimator.ofInt(255, 0).apply {
                interpolator = Interpolators.LINEAR
                duration = retractDuration
                addUpdateListener { animator ->
                    dwellShader.color = ColorUtils.setAlphaComponent(
                            dwellShader.color,
                            animator.animatedValue as Int
                    )
                    invalidate()
                }
            }

            retractDwellAnimator = AnimatorSet().apply {
                playTogether(retractDwellRippleAnimator, retractAlphaAnimator)
                addListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationStart(animation: Animator) {
                        dwellPulseOutAnimator?.cancel()
                        drawDwell = true
                    }

                    override fun onAnimationEnd(animation: Animator) {
                        drawDwell = false
                        resetDwellAlpha()
                    }
                })
                start()
            }
        }
    }

    /**
     * Animate ripple fade to alpha=0
     */
    fun fadeDwellRipple() {
        if (fadeDwellAnimator?.isRunning == true) {
            return // let the animation finish
        }

        if (dwellPulseOutAnimator?.isRunning == true || retractDwellAnimator?.isRunning == true) {
            fadeDwellAnimator = ValueAnimator.ofInt(Color.alpha(dwellShader.color), 0).apply {
                interpolator = Interpolators.LINEAR
                duration = fadeDuration
                addUpdateListener { animator ->
                    dwellShader.color = ColorUtils.setAlphaComponent(
                            dwellShader.color,
                            animator.animatedValue as Int
                    )
                    invalidate()
                }
                addListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationStart(animation: Animator) {
                        retractDwellAnimator?.cancel()
                        dwellPulseOutAnimator?.cancel()
                        drawDwell = true
                    }

                    override fun onAnimationEnd(animation: Animator) {
                        drawDwell = false
                        resetDwellAlpha()
                    }
                })
                start()
            }
        }
    }

    /**
     * Plays a ripple animation that grows to the dwellRadius with distortion.
     */
    fun startDwellRipple(isDozing: Boolean) {
        if (unlockedRippleAnimator?.isRunning == true || dwellPulseOutAnimator?.isRunning == true) {
            return
        }

        updateDwellRippleColor(isDozing)

        val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(0f, .8f).apply {
            interpolator = Interpolators.LINEAR
            duration = dwellPulseDuration
            addUpdateListener { animator ->
                val now = animator.currentPlayTime
                dwellShader.progress = animator.animatedValue as Float
                dwellShader.time = now.toFloat()

                invalidate()
            }
        }

        // slowly animate outwards until we receive a call to retractRipple or startUnlockedRipple
        val expandDwellRippleAnimator = ValueAnimator.ofFloat(.8f, 1f).apply {
            interpolator = Interpolators.LINEAR_OUT_SLOW_IN
            duration = dwellExpandDuration
            addUpdateListener { animator ->
                val now = animator.currentPlayTime
                dwellShader.progress = animator.animatedValue as Float
                dwellShader.time = now.toFloat()

                invalidate()
            }
        }

        dwellPulseOutAnimator = AnimatorSet().apply {
            playSequentially(
                    dwellPulseOutRippleAnimator,
                    expandDwellRippleAnimator
            )
            addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animation: Animator) {
                    retractDwellAnimator?.cancel()
                    fadeDwellAnimator?.cancel()
                    visibility = VISIBLE
                    drawDwell = true
                }

                override fun onAnimationEnd(animation: Animator) {
                    drawDwell = false
                }
            })
            start()
        }
    }

    /**
     * Ripple that bursts outwards from the position of the sensor to the edges of the screen
     */
    fun startUnlockedRipple(onAnimationEnd: Runnable?) {
        unlockedRippleAnimator?.cancel()

        val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
            duration = AuthRippleController.RIPPLE_ANIMATION_DURATION
            addUpdateListener { animator ->
                val now = animator.currentPlayTime
                rippleShader.rawProgress = animator.animatedValue as Float
                rippleShader.time = now.toFloat()

                invalidate()
            }
        }

        unlockedRippleAnimator = rippleAnimator.apply {
            addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animation: Animator) {
                    drawRipple = true
                    visibility = VISIBLE
                }

                override fun onAnimationEnd(animation: Animator) {
                    onAnimationEnd?.run()
                    drawRipple = false
                    visibility = GONE
                    unlockedRippleAnimator = null
                }
            })
        }
        unlockedRippleAnimator?.start()
    }

    fun setLockScreenColor(color: Int) {
        lockScreenColorVal = color
        rippleShader.color = ColorUtils.setAlphaComponent(
                lockScreenColorVal,
                62
        )
    }

    fun updateDwellRippleColor(isDozing: Boolean) {
        if (isDozing) {
            dwellShader.color = Color.WHITE
        } else {
            dwellShader.color = lockScreenColorVal
        }
        resetDwellAlpha()
    }

    fun resetDwellAlpha() {
        dwellShader.color = ColorUtils.setAlphaComponent(
                dwellShader.color,
                255
        )
    }

    private fun updateRippleFadeParams() {
        with(rippleShader) {
            baseRingFadeParams.fadeInStart = 0f
            baseRingFadeParams.fadeInEnd = .2f
            baseRingFadeParams.fadeOutStart = .2f
            baseRingFadeParams.fadeOutEnd = 1f

            centerFillFadeParams.fadeInStart = 0f
            centerFillFadeParams.fadeInEnd = .15f
            centerFillFadeParams.fadeOutStart = .15f
            centerFillFadeParams.fadeOutEnd = .56f
        }
    }

    override fun onDraw(canvas: Canvas) {
        // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
        // the active effect area. Values here should be kept in sync with the
        // animation implementation in the ripple shader. (Twice bigger)
        if (drawDwell) {
            val maskRadius = (1 - (1 - dwellShader.progress) * (1 - dwellShader.progress) *
                    (1 - dwellShader.progress)) * dwellRadius * 2f
            canvas?.drawCircle(dwellOrigin.x.toFloat(), dwellOrigin.y.toFloat(),
                    maskRadius, dwellPaint)
        }

        if (drawRipple) {
            canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(),
                    rippleShader.rippleSize.currentWidth, ripplePaint)
        }
    }
}
