/*
 * Copyright (C) 2023 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.animation.AnimatorSet
import android.graphics.Canvas
import android.view.View
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
import com.android.launcher3.views.BaseDragLayer
import com.android.systemui.animation.ViewRootSync
import java.io.PrintWriter

private const val TASKBAR_ICONS_FADE_DURATION = 300L
private const val STASHED_HANDLE_FADE_DURATION = 180L
private const val TEMP_BACKGROUND_WINDOW_TITLE = "VoiceInteractionTaskbarBackground"

/**
 * Controls Taskbar behavior while Voice Interaction Window (assistant) is showing. Specifically:
 * - We always hide the taskbar icons or stashed handle, whichever is currently showing.
 * - For persistent taskbar, we also move the taskbar background to a new window/layer
 *   (TYPE_APPLICATION_OVERLAY) which is behind the assistant.
 * - For transient taskbar, we hide the real taskbar background (if it's showing).
 */
class VoiceInteractionWindowController(val context: TaskbarActivityContext) :
    TaskbarControllers.LoggableTaskbarController, TaskbarControllers.BackgroundRendererController {

    private val isSeparateBackgroundEnabled = !context.isTransientTaskbar && !context.isPhoneMode
    private val taskbarBackgroundRenderer = TaskbarBackgroundRenderer(context)
    private val nonTouchableInsetsComputer =
        ViewTreeObserver.OnComputeInternalInsetsListener {
            it.touchableRegion.setEmpty()
            it.setTouchableInsets(TOUCHABLE_INSETS_REGION)
        }

    // Initialized in init.
    private lateinit var controllers: TaskbarControllers
    // Only initialized if isSeparateBackgroundEnabled
    private var separateWindowForTaskbarBackground: BaseDragLayer<TaskbarActivityContext>? = null
    private var separateWindowLayoutParams: WindowManager.LayoutParams? = null

    private var isVoiceInteractionWindowVisible: Boolean = false
    private var pendingAttachedToWindowListener: View.OnAttachStateChangeListener? = null

    fun init(controllers: TaskbarControllers) {
        this.controllers = controllers

        if (!isSeparateBackgroundEnabled) {
            return
        }

        separateWindowForTaskbarBackground =
            object : BaseDragLayer<TaskbarActivityContext>(context, null, 0) {
                override fun recreateControllers() {
                    mControllers = emptyArray()
                }

                override fun draw(canvas: Canvas) {
                    super.draw(canvas)
                    if (controllers.taskbarStashController.isTaskbarVisibleAndNotStashing) {
                        taskbarBackgroundRenderer.draw(canvas)
                    }
                }

                override fun onAttachedToWindow() {
                    super.onAttachedToWindow()
                    viewTreeObserver.addOnComputeInternalInsetsListener(nonTouchableInsetsComputer)
                }

                override fun onDetachedFromWindow() {
                    super.onDetachedFromWindow()
                    viewTreeObserver.removeOnComputeInternalInsetsListener(
                        nonTouchableInsetsComputer
                    )
                }
            }
        separateWindowForTaskbarBackground?.recreateControllers()
        separateWindowForTaskbarBackground?.setWillNotDraw(false)

        separateWindowLayoutParams =
            context.createDefaultWindowLayoutParams(
                TYPE_APPLICATION_OVERLAY,
                TEMP_BACKGROUND_WINDOW_TITLE,
            )
        separateWindowLayoutParams?.isSystemApplicationOverlay = true
    }

    fun onDestroy() {
        setIsVoiceInteractionWindowVisible(visible = false, skipAnim = true)
        separateWindowForTaskbarBackground?.removeOnAttachStateChangeListener(
            pendingAttachedToWindowListener
        )
    }

    fun setIsVoiceInteractionWindowVisible(visible: Boolean, skipAnim: Boolean) {
        if (isVoiceInteractionWindowVisible == visible || context.isPhoneMode) {
            return
        }
        isVoiceInteractionWindowVisible = visible

        // Fade out taskbar icons and stashed handle.
        val taskbarIconAlpha = if (isVoiceInteractionWindowVisible) 0f else 1f
        val fadeTaskbarIcons =
            controllers.taskbarViewController.taskbarIconAlpha
                .get(TaskbarViewController.ALPHA_INDEX_ASSISTANT_INVOKED)
                .animateToValue(taskbarIconAlpha)
                .setDuration(TASKBAR_ICONS_FADE_DURATION)
        val fadeStashedHandle =
            controllers.stashedHandleViewController.stashedHandleAlpha
                .get(StashedHandleViewController.ALPHA_INDEX_ASSISTANT_INVOKED)
                .animateToValue(taskbarIconAlpha)
                .setDuration(STASHED_HANDLE_FADE_DURATION)
        val animSet = AnimatorSet()
        animSet.play(fadeTaskbarIcons)
        animSet.play(fadeStashedHandle)
        if (!isSeparateBackgroundEnabled) {
            val fadeTaskbarBackground =
                controllers.taskbarDragLayerController.assistantBgTaskbar
                    .animateToValue(taskbarIconAlpha)
                    .setDuration(TASKBAR_ICONS_FADE_DURATION)
            animSet.play(fadeTaskbarBackground)
        }
        animSet.start()
        if (skipAnim) {
            animSet.end()
        }

        if (isSeparateBackgroundEnabled) {
            moveTaskbarBackgroundToAppropriateLayer(skipAnim)
        }
    }

    /**
     * Either:
     *
     * Hides the TaskbarDragLayer background and creates a new window to draw just that background.
     *
     * OR
     *
     * Removes the temporary window and show the TaskbarDragLayer background again.
     */
    private fun moveTaskbarBackgroundToAppropriateLayer(skipAnim: Boolean) {
        val moveToLowerLayer = isVoiceInteractionWindowVisible
        val onWindowsSynchronized =
            if (moveToLowerLayer) {
                // First add the temporary window, then hide the overlapping taskbar background.
                context.addWindowView(
                    separateWindowForTaskbarBackground,
                    separateWindowLayoutParams,
                );
                { controllers.taskbarDragLayerController.setIsBackgroundDrawnElsewhere(true) }
            } else {
                // First reapply the original taskbar background, then remove the temporary window.
                controllers.taskbarDragLayerController.setIsBackgroundDrawnElsewhere(false);
                { context.removeWindowView(separateWindowForTaskbarBackground) }
            }

        if (skipAnim) {
            onWindowsSynchronized()
        } else {
            separateWindowForTaskbarBackground?.runWhenAttachedToWindow {
                ViewRootSync.synchronizeNextDraw(
                    separateWindowForTaskbarBackground!!,
                    context.dragLayer,
                    onWindowsSynchronized,
                )
            }
        }
    }

    private fun View.runWhenAttachedToWindow(onAttachedToWindow: () -> Unit) {
        if (isAttachedToWindow) {
            onAttachedToWindow()
            return
        }
        removeOnAttachStateChangeListener(pendingAttachedToWindowListener)
        pendingAttachedToWindowListener =
            object : View.OnAttachStateChangeListener {
                override fun onViewAttachedToWindow(v: View) {
                    onAttachedToWindow()
                    removeOnAttachStateChangeListener(this)
                    pendingAttachedToWindowListener = null
                }

                override fun onViewDetachedFromWindow(v: View) {}
            }
        addOnAttachStateChangeListener(pendingAttachedToWindowListener)
    }

    override fun setCornerRoundness(cornerRoundness: Float) {
        if (!isSeparateBackgroundEnabled) {
            return
        }
        taskbarBackgroundRenderer.setCornerRoundness(cornerRoundness)
        separateWindowForTaskbarBackground?.invalidate()
    }

    override fun dumpLogs(prefix: String, pw: PrintWriter) {
        pw.println(prefix + "VoiceInteractionWindowController:")
        pw.println("$prefix\tisSeparateBackgroundEnabled=$isSeparateBackgroundEnabled")
        pw.println("$prefix\tisVoiceInteractionWindowVisible=$isVoiceInteractionWindowVisible")
        pw.println(
            "$prefix\tisSeparateTaskbarBackgroundAttachedToWindow=" +
                "${separateWindowForTaskbarBackground?.isAttachedToWindow}"
        )
    }
}
