/*
 * 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.systemui.common.ui.view

import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import com.android.systemui.shade.TouchLogger
import kotlin.math.pow
import kotlin.math.sqrt
import kotlinx.coroutines.DisposableHandle

/**
 * View designed to handle long-presses.
 *
 * The view will not handle any long pressed by default. To set it up, set up a listener and, when
 * ready to start consuming long-presses, set [setLongPressHandlingEnabled] to `true`.
 */
class LongPressHandlingView(
    context: Context,
    attrs: AttributeSet?,
    private val longPressDuration: () -> Long,
) :
    View(
        context,
        attrs,
    ) {

    constructor(
        context: Context,
        attrs: AttributeSet?,
    ) : this(context, attrs, { ViewConfiguration.getLongPressTimeout().toLong() })

    interface Listener {
        /** Notifies that a long-press has been detected by the given view. */
        fun onLongPressDetected(
            view: View,
            x: Int,
            y: Int,
        )

        /** Notifies that the gesture was too short for a long press, it is actually a click. */
        fun onSingleTapDetected(view: View) = Unit
    }

    var listener: Listener? = null

    private val interactionHandler: LongPressHandlingViewInteractionHandler by lazy {
        LongPressHandlingViewInteractionHandler(
            postDelayed = { block, timeoutMs ->
                val dispatchToken = Any()

                handler.postDelayed(
                    block,
                    dispatchToken,
                    timeoutMs,
                )

                DisposableHandle { handler.removeCallbacksAndMessages(dispatchToken) }
            },
            isAttachedToWindow = ::isAttachedToWindow,
            onLongPressDetected = { x, y ->
                listener?.onLongPressDetected(
                    view = this,
                    x = x,
                    y = y,
                )
            },
            onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) },
            longPressDuration = longPressDuration,
        )
    }

    fun setLongPressHandlingEnabled(isEnabled: Boolean) {
        interactionHandler.isLongPressHandlingEnabled = isEnabled
    }

    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
        return TouchLogger.logDispatchTouch("long_press", event, super.dispatchTouchEvent(event))
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        return interactionHandler.onTouchEvent(event?.toModel())
    }
}

private fun MotionEvent.toModel(): LongPressHandlingViewInteractionHandler.MotionEventModel {
    return when (actionMasked) {
        MotionEvent.ACTION_DOWN ->
            LongPressHandlingViewInteractionHandler.MotionEventModel.Down(
                x = x.toInt(),
                y = y.toInt(),
            )
        MotionEvent.ACTION_MOVE ->
            LongPressHandlingViewInteractionHandler.MotionEventModel.Move(
                distanceMoved = distanceMoved(),
            )
        MotionEvent.ACTION_UP ->
            LongPressHandlingViewInteractionHandler.MotionEventModel.Up(
                distanceMoved = distanceMoved(),
                gestureDuration = gestureDuration(),
            )
        MotionEvent.ACTION_CANCEL -> LongPressHandlingViewInteractionHandler.MotionEventModel.Cancel
        else -> LongPressHandlingViewInteractionHandler.MotionEventModel.Other
    }
}

private fun MotionEvent.distanceMoved(): Float {
    return if (historySize > 0) {
        sqrt((x - getHistoricalX(0)).pow(2) + (y - getHistoricalY(0)).pow(2))
    } else {
        0f
    }
}

private fun MotionEvent.gestureDuration(): Long {
    return eventTime - downTime
}
