/*
 * 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.view.MotionEvent
import com.android.app.animation.Interpolators.LINEAR
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.touch.SingleAxisSwipeDetector
import com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE
import com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL
import com.android.launcher3.util.TouchController
import com.android.quickstep.inputconsumers.TaskbarUnstashInputConsumer

/**
 * A helper [TouchController] for [TaskbarDragLayerController], specifically to handle touch events
 * to stash Transient Taskbar. There are two cases to handle:
 * - A touch outside of Transient Taskbar bounds will immediately stash on [MotionEvent.ACTION_DOWN]
 *   or [MotionEvent.ACTION_OUTSIDE].
 * - Touches inside Transient Taskbar bounds will stash if it is detected as a swipe down gesture.
 *
 * Note: touches to *unstash* Taskbar are handled by [TaskbarUnstashInputConsumer].
 */
class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : TouchController {

    private val activity: TaskbarActivityContext = controllers.taskbarActivityContext
    private val enabled = activity.isTransientTaskbar
    private val swipeDownDetector: SingleAxisSwipeDetector
    private val translationCallback = controllers.taskbarTranslationController.transitionCallback
    /** Interpolator to apply resistance as user swipes down to the bottom of the screen. */
    private val displacementInterpolator = LINEAR
    /** How far we can translate the TaskbarView before it's offscreen. */
    private val maxVisualDisplacement =
        activity.resources.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat()
    /** How far the swipe could go, if user swiped from the very top of TaskbarView. */
    private val maxTouchDisplacement = maxVisualDisplacement + activity.deviceProfile.taskbarHeight
    private val touchDisplacementToStash =
        activity.resources.getDimensionPixelSize(R.dimen.taskbar_to_nav_threshold).toFloat()

    /** The height of the system gesture region, so we don't stash when touching down there. */
    private var gestureHeightYThreshold = 0f

    init {
        updateGestureHeight()
        swipeDownDetector = SingleAxisSwipeDetector(activity, createSwipeListener(), VERTICAL)
        swipeDownDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false)
    }

    fun updateGestureHeight() {
        if (!enabled) return

        val gestureHeight: Int =
            ResourceUtils.getNavbarSize(
                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
                activity.resources,
            )
        gestureHeightYThreshold = (activity.deviceProfile.heightPx - gestureHeight).toFloat()
    }

    private fun createSwipeListener() =
        object : SingleAxisSwipeDetector.Listener {
            private var lastDisplacement = 0f

            override fun onDragStart(start: Boolean, startDisplacement: Float) {}

            override fun onDrag(displacement: Float): Boolean {
                lastDisplacement = displacement
                if (displacement < 0) return false
                // Apply resistance so that the visual displacement doesn't go beyond the screen.
                translationCallback.onActionMove(
                    Utilities.mapToRange(
                        displacement,
                        0f,
                        maxTouchDisplacement,
                        0f,
                        maxVisualDisplacement,
                        displacementInterpolator,
                    )
                )
                return false
            }

            override fun onDragEnd(velocity: Float) {
                val isFlingDown = swipeDownDetector.isFling(velocity) && velocity > 0
                val isSignificantDistance = lastDisplacement > touchDisplacementToStash
                if (isFlingDown || isSignificantDistance) {
                    // Successfully triggered stash.
                    controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
                }
                translationCallback.onActionEnd()
                swipeDownDetector.finishedScrolling()
            }
        }

    override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
        if (!enabled) {
            return false
        }
        val bubbleControllers = controllers.bubbleControllers.orElse(null)
        if (bubbleControllers != null && bubbleControllers.bubbleBarViewController.isExpanded) {
            return false
        }
        if (
            (bubbleControllers == null || bubbleControllers.bubbleStashController.isStashed) &&
                controllers.taskbarStashController.isStashed
        ) {
            return false
        }

        val screenCoordinatesEv = MotionEvent.obtain(ev)
        screenCoordinatesEv.setLocation(ev.rawX, ev.rawY)
        if (ev.action == MotionEvent.ACTION_OUTSIDE) {
            controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
        } else if (controllers.taskbarViewController.isEventOverAnyItem(screenCoordinatesEv)) {
            swipeDownDetector.onTouchEvent(ev)
            if (swipeDownDetector.isDraggingState) {
                return true
            }
        } else if (ev.action == MotionEvent.ACTION_DOWN) {
            val isDownOnBubbleBar =
                (bubbleControllers != null &&
                    bubbleControllers.bubbleBarViewController.isEventOverAnyItem(
                        screenCoordinatesEv
                    ))
            if (!isDownOnBubbleBar && screenCoordinatesEv.y < gestureHeightYThreshold) {
                controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
            }
        }
        return false
    }

    override fun onControllerTouchEvent(ev: MotionEvent) = swipeDownDetector.onTouchEvent(ev)
}
