/*
 * Copyright (C) 2022 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.launcher3.taskbar

import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import com.android.app.animation.Interpolators
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapRange
import com.android.launcher3.Utilities.mapToRange
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_PERSISTENT
import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_TRANSIENT
import kotlin.math.min

/** Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners. */
class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {

    private val isInSetup: Boolean = !context.isUserSetupComplete

    private val maxTransientTaskbarHeight =
        context.transientTaskbarDeviceProfile.taskbarHeight.toFloat()
    private val maxPersistentTaskbarHeight =
        context.persistentTaskbarDeviceProfile.taskbarHeight.toFloat()
    var backgroundProgress =
        if (context.isTransientTaskbar) {
            PINNING_TRANSIENT
        } else {
            PINNING_PERSISTENT
        }

    var isAnimatingPinning = false
    var isAnimatingPersistentTaskbar = false
    var isAnimatingTransientTaskbar = false

    val paint = Paint()
    private val strokePaint = Paint()
    val lastDrawnTransientRect = RectF()
    var backgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
    var translationYForSwipe = 0f
    var translationYForStash = 0f
    var translationXForBubbleBar = 0f

    private val transientBackgroundBounds = context.transientTaskbarBounds

    private val shadowAlpha: Float
    private val strokeAlpha: Int
    private var shadowBlur = 0f
    private var keyShadowDistance = 0f
    private var bottomMargin = 0

    private val fullCornerRadius: Float
    private var cornerRadius = 0f
    private var widthInsetPercentage = 0f
    private val square = Path()
    private val circle = Path()
    private val invertedLeftCornerPath = Path()
    private val invertedRightCornerPath = Path()

    private var stashedHandleWidth =
        context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width)

    private val stashedHandleHeight =
        context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height)

    init {
        paint.color = context.getColor(R.color.taskbar_background)
        paint.flags = Paint.ANTI_ALIAS_FLAG
        paint.style = Paint.Style.FILL
        strokePaint.color = context.getColor(R.color.taskbar_stroke)
        strokePaint.flags = Paint.ANTI_ALIAS_FLAG
        strokePaint.style = Paint.Style.STROKE
        strokePaint.strokeWidth =
            context.resources.getDimension(R.dimen.transient_taskbar_stroke_width)
        if (Utilities.isDarkTheme(context)) {
            strokeAlpha = DARK_THEME_STROKE_ALPHA
            shadowAlpha = DARK_THEME_SHADOW_ALPHA
        } else {
            strokeAlpha = LIGHT_THEME_STROKE_ALPHA
            shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
        }

        fullCornerRadius = context.cornerRadius.toFloat()
        cornerRadius = fullCornerRadius
        if (!context.isInDesktopMode()) {
            setCornerRoundness(MAX_ROUNDNESS)
        }
    }

    fun updateStashedHandleWidth(context: TaskbarActivityContext, res: Resources) {
        stashedHandleWidth =
            res.getDimensionPixelSize(
                if (context.isPhoneMode || context.isTinyTaskbar || context.isBubbleBarOnPhone) {
                    R.dimen.taskbar_stashed_small_screen
                } else {
                    R.dimen.taskbar_stashed_handle_width
                }
            )
    }

    /**
     * Sets the roundness of the round corner above Taskbar. No effect on transient Taskbar.
     *
     * @param cornerRoundness 0 has no round corner, 1 has complete round corner.
     */
    fun setCornerRoundness(cornerRoundness: Float) {
        if (context.isTransientTaskbar && !transientBackgroundBounds.isEmpty) {
            return
        }

        cornerRadius = fullCornerRadius * cornerRoundness

        // Create the paths for the inverted rounded corners above the taskbar. Start with a filled
        // square, and then subtract out a circle from the appropriate corner.
        square.reset()
        square.addRect(0f, 0f, cornerRadius, cornerRadius, Path.Direction.CW)
        circle.reset()
        circle.addCircle(cornerRadius, 0f, cornerRadius, Path.Direction.CW)
        invertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE)

        circle.reset()
        circle.addCircle(0f, 0f, cornerRadius, Path.Direction.CW)
        invertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE)
    }

    /** Draws the background with the given paint and height, on the provided canvas. */
    fun draw(canvas: Canvas) {
        if (isInSetup) return
        val isTransientTaskbar = context.isTransientTaskbar
        canvas.save()
        if (!isTransientTaskbar || transientBackgroundBounds.isEmpty || isAnimatingPinning) {
            drawPersistentBackground(canvas)
        }
        canvas.restore()
        canvas.save()
        if (isAnimatingPinning || isTransientTaskbar) {
            drawTransientBackground(canvas)
        }
        canvas.restore()
    }

    private fun drawPersistentBackground(canvas: Canvas) {
        if (isAnimatingPinning || isAnimatingPersistentTaskbar) {
            val persistentTaskbarHeight = maxPersistentTaskbarHeight * backgroundProgress
            canvas.translate(0f, canvas.height - persistentTaskbarHeight)
            // Draw the background behind taskbar content.
            canvas.drawRect(0f, 0f, canvas.width.toFloat(), persistentTaskbarHeight, paint)
        } else {
            val persistentTaskbarHeight = min(maxPersistentTaskbarHeight, backgroundHeight)
            canvas.translate(0f, canvas.height - persistentTaskbarHeight)
            // Draw the background behind taskbar content.
            canvas.drawRect(0f, 0f, canvas.width.toFloat(), persistentTaskbarHeight, paint)
        }

        // Draw the inverted rounded corners above the taskbar.
        canvas.translate(0f, -cornerRadius)
        canvas.drawPath(invertedLeftCornerPath, paint)
        canvas.translate(0f, cornerRadius)
        canvas.translate(canvas.width - cornerRadius, -cornerRadius)
        canvas.drawPath(invertedRightCornerPath, paint)
    }

    private fun drawTransientBackground(canvas: Canvas) {
        val res = context.resources
        val transientTaskbarHeight = maxTransientTaskbarHeight * (1f - backgroundProgress)
        val isAnimating = isAnimatingPinning || isAnimatingTransientTaskbar
        val heightProgressWhileAnimating =
            if (isAnimating) transientTaskbarHeight else backgroundHeight

        var progress = heightProgressWhileAnimating / maxTransientTaskbarHeight
        progress = Math.round(progress * 100f) / 100f
        if (isAnimating) {
            var scale = transientTaskbarHeight / maxTransientTaskbarHeight
            scale = Math.round(scale * 100f) / 100f
            bottomMargin =
                mapRange(
                        scale,
                        0f,
                        res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat(),
                    )
                    .toInt()
            shadowBlur =
                mapRange(scale, 0f, res.getDimension(R.dimen.transient_taskbar_shadow_blur))
            keyShadowDistance =
                mapRange(scale, 0f, res.getDimension(R.dimen.transient_taskbar_key_shadow_distance))
        } else {
            bottomMargin = res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin)
            shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
            keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
        }

        // At progress 0, we draw the background as the stashed handle.
        // At progress 1, we draw the background as the full taskbar.
        // Min height capped to max persistent taskbar height for animation
        val backgroundHeightWhileAnimating =
            if (isAnimatingPinning) maxPersistentTaskbarHeight else stashedHandleHeight.toFloat()
        val newBackgroundHeight =
            mapRange(progress, backgroundHeightWhileAnimating, maxTransientTaskbarHeight)
        val fullWidth = transientBackgroundBounds.width()
        val animationWidth = context.currentTaskbarWidth
        val backgroundWidthWhileAnimating =
            if (isAnimatingPinning) animationWidth else stashedHandleWidth.toFloat()

        val newWidth = mapRange(progress, backgroundWidthWhileAnimating, fullWidth.toFloat())
        val halfWidthDelta = (fullWidth - newWidth) / 2f
        val radius = newBackgroundHeight / 2f
        val bottomMarginProgress = bottomMargin * ((1f - progress) / 2f)

        // Aligns the bottom with the bottom of the stashed handle.
        val bottom =
            canvas.height - bottomMargin +
                bottomMarginProgress +
                translationYForSwipe +
                translationYForStash +
                -mapRange(
                    1f - progress,
                    0f,
                    if (isAnimatingPinning) 0f else stashedHandleHeight / 2f,
                )

        // Draw shadow.
        val newShadowAlpha =
            mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
        paint.setShadowLayer(
            shadowBlur,
            0f,
            keyShadowDistance,
            setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha)),
        )
        strokePaint.alpha = (paint.alpha * strokeAlpha) / 255
        val currentTranslationX = translationXForBubbleBar * progress
        lastDrawnTransientRect.set(
            transientBackgroundBounds.left + halfWidthDelta + currentTranslationX,
            bottom - newBackgroundHeight,
            transientBackgroundBounds.right - halfWidthDelta + currentTranslationX,
            bottom,
        )
        val horizontalInset = fullWidth * widthInsetPercentage
        lastDrawnTransientRect.inset(horizontalInset, 0f)

        canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint)
        canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, strokePaint)
    }

    /**
     * Sets the width percentage to inset the transient taskbar's background from the left and from
     * the right.
     */
    fun setBackgroundHorizontalInsets(insetPercentage: Float) {
        widthInsetPercentage = insetPercentage
    }

    companion object {
        const val MAX_ROUNDNESS = 1f
        private const val DARK_THEME_STROKE_ALPHA = 51
        private const val LIGHT_THEME_STROKE_ALPHA = 41
        private const val DARK_THEME_SHADOW_ALPHA = 51f
        private const val LIGHT_THEME_SHADOW_ALPHA = 25f
    }
}
