/*
 * 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.biometrics.udfps

import android.graphics.Point
import android.graphics.Rect
import android.util.Log
import com.android.systemui.biometrics.EllipseOverlapDetectorParams
import com.android.systemui.dagger.SysUISingleton
import kotlin.math.cos
import kotlin.math.sin

private enum class SensorPixelPosition {
    OUTSIDE, // Pixel that falls outside of sensor circle
    SENSOR, // Pixel within sensor circle
    TARGET // Pixel within sensor center target
}

private const val isDebug = false
private const val TAG = "EllipseOverlapDetector"

/**
 * Approximates the touch as an ellipse and determines whether the ellipse has a sufficient overlap
 * with the sensor.
 */
@SysUISingleton
class EllipseOverlapDetector(private val params: EllipseOverlapDetectorParams) : OverlapDetector {
    override fun isGoodOverlap(
        touchData: NormalizedTouchData,
        nativeSensorBounds: Rect,
        nativeOverlayBounds: Rect,
    ): Boolean {
        // First, check if touch is within bounding box to exit early
        if (touchData.isWithinBounds(nativeSensorBounds)) {
            return true
        }

        // Check touch is within overlay bounds, not worth checking if outside
        if (!touchData.isWithinBounds(nativeOverlayBounds)) {
            return false
        }

        var isTargetTouched = false
        var sensorPixels = 0
        var coveredPixels = 0
        for (y in nativeSensorBounds.top..nativeSensorBounds.bottom step params.stepSize) {
            for (x in nativeSensorBounds.left..nativeSensorBounds.right step params.stepSize) {
                // Check where pixel is within the sensor TODO: (b/265836919) This could be improved
                // by precomputing these points
                val pixelPosition =
                    isPartOfSensorArea(
                        x,
                        y,
                        nativeSensorBounds.centerX(),
                        nativeSensorBounds.centerY(),
                        nativeSensorBounds.width() / 2
                    )
                if (pixelPosition != SensorPixelPosition.OUTSIDE) {
                    sensorPixels++

                    // Check if this pixel falls within ellipse touch
                    if (checkPoint(Point(x, y), touchData)) {
                        coveredPixels++

                        // Check that at least one covered pixel is within sensor target
                        isTargetTouched =
                            isTargetTouched or (pixelPosition == SensorPixelPosition.TARGET)
                    }
                }
            }
        }

        val percentage: Float = coveredPixels.toFloat() / sensorPixels
        if (isDebug) {
            Log.d(
                TAG,
                "covered: $coveredPixels, sensor: $sensorPixels, " +
                    "percentage: $percentage, isCenterTouched: $isTargetTouched"
            )
        }

        return percentage >= params.minOverlap && isTargetTouched
    }

    /** Checks if point is in the sensor center target circle, outer circle, or outside of sensor */
    private fun isPartOfSensorArea(x: Int, y: Int, cX: Int, cY: Int, r: Int): SensorPixelPosition {
        val dx = cX - x
        val dy = cY - y

        val disSquared = dx * dx + dy * dy

        return if (disSquared <= (r * params.targetSize) * (r * params.targetSize)) {
            SensorPixelPosition.TARGET
        } else if (disSquared <= r * r) {
            SensorPixelPosition.SENSOR
        } else {
            SensorPixelPosition.OUTSIDE
        }
    }

    private fun checkPoint(point: Point, touchData: NormalizedTouchData): Boolean {
        // Calculate if sensor point is within ellipse
        // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE -
        // yS))^2 / b^2) <= 1
        val a: Float = cos(touchData.orientation) * (point.x - touchData.x)
        val b: Float = sin(touchData.orientation) * (point.y - touchData.y)
        val c: Float = sin(touchData.orientation) * (point.x - touchData.x)
        val d: Float = cos(touchData.orientation) * (point.y - touchData.y)
        val result =
            (a + b) * (a + b) / ((touchData.minor / 2) * (touchData.minor / 2)) +
                (c - d) * (c - d) / ((touchData.major / 2) * (touchData.major / 2))

        return result <= 1
    }
}
