/*
 * 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.systemui.statusbar.gesture

import android.annotation.CallSuper
import android.os.Looper
import android.view.Choreographer
import android.view.InputEvent
import android.view.MotionEvent
import com.android.systemui.shared.system.InputChannelCompat
import com.android.systemui.shared.system.InputMonitorCompat

/**
 * An abstract class to help detect gestures that occur anywhere on the display (not specific to a
 * certain view).
 *
 * This class handles starting/stopping the gesture detection system as well as
 * registering/unregistering callbacks for when gestures occur. Note that the class will only listen
 * for gestures when there's at least one callback registered.
 *
 * Subclasses should implement [onInputEvent] to detect their specific gesture. Once a specific
 * gesture is detected, they should call [onGestureDetected] (which will notify the callbacks).
 */
abstract class GenericGestureDetector(
    private val tag: String,
    private val displayId: Int,
) {
    /**
     * Active callbacks, each associated with a tag. Gestures will only be monitored if
     * [callbacks.size] > 0.
     */
    private val callbacks: MutableMap<String, (MotionEvent) -> Unit> = mutableMapOf()

    private var inputMonitor: InputMonitorCompat? = null
    private var inputReceiver: InputChannelCompat.InputEventReceiver? = null

    /**
     * Adds a callback that will be triggered when the tap gesture is detected.
     *
     * The callback receive the last motion event in the gesture.
     */
    fun addOnGestureDetectedCallback(tag: String, callback: (MotionEvent) -> Unit) {
        val callbacksWasEmpty = callbacks.isEmpty()
        callbacks[tag] = callback
        if (callbacksWasEmpty) {
            startGestureListening()
        }
    }

    /** Removes the callback. */
    fun removeOnGestureDetectedCallback(tag: String) {
        callbacks.remove(tag)
        if (callbacks.isEmpty()) {
            stopGestureListening()
        }
    }

    /** Triggered each time a touch event occurs (and at least one callback is registered). */
    abstract fun onInputEvent(ev: InputEvent)

    /**
     * Should be called by subclasses when their specific gesture is detected with the last motion
     * event in the gesture.
     */
    internal fun onGestureDetected(e: MotionEvent) {
        callbacks.values.forEach { it.invoke(e) }
    }

    /** Start listening to touch events. */
    @CallSuper
    internal open fun startGestureListening() {
        stopGestureListening()

        inputMonitor = InputMonitorCompat(tag, displayId).also {
            inputReceiver = it.getInputReceiver(
                Looper.getMainLooper(),
                Choreographer.getInstance(),
                this::onInputEvent
            )
        }
    }

    /** Stop listening to touch events. */
    @CallSuper
    internal open fun stopGestureListening() {
        inputMonitor?.let {
            inputMonitor = null
            it.dispose()
        }
        inputReceiver?.let {
            inputReceiver = null
            it.dispose()
        }
    }
}
