package com.example.cmpt362_group30_maze_game.game

import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import kotlin.math.abs

class TiltController(context: Context) : SensorEventListener {

    private val sm = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
    private val sensor: Sensor? =
        sm.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR)
            ?: sm.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)

    // Reusable buffers (avoid GC)
    private val R = FloatArray(9)
    private val euler = FloatArray(3)

    // Current orientation in degrees
    private var yawDeg: Float = 0f
    private var pitchDeg: Float = 0f
    private var rollDeg: Float = 0f

    // For detecting “big turns” (so GameActivity can momentarily lock movement if desired)
    var facingIndex: Int = 0
        private set
    var onTurn: ((Int) -> Unit)? = null

    // How much yaw change counts as a “turn” (degrees)
    private val turnStepDeg = 90f
    private var lastTurnYawDeg: Float = 0f

    // Simple low-pass filter for smoother motion (can tweak if you want snappier/slower tilt)
    var alpha = 0.35f   // higher = more responsive
    var deadZoneDeg = 1.5f

    fun start() {
        sensor?.let {
            sm.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME)
        }
    }

    fun stop() {
        sm.unregisterListener(this)
    }

    fun getCameraYawDeg(): Float = yawDeg

    fun getPitchRollDeg(): Pair<Float, Float> = pitchDeg to rollDeg

    override fun onSensorChanged(event: SensorEvent?) {
        if (event == null || event.sensor != sensor) return

        // Convert rotation vector -> rotation matrix -> orientation (radians)
        SensorManager.getRotationMatrixFromVector(R, event.values)
        SensorManager.getOrientation(R, euler)

        // Yaw (azimuth), pitch, roll (in radians -> degrees)
        var newYaw = Math.toDegrees(euler[0].toDouble()).toFloat()
        var newPitch = Math.toDegrees(euler[1].toDouble()).toFloat()
        var newRoll = Math.toDegrees(euler[2].toDouble()).toFloat()

        // Normalise yaw to [-180, 180] for nicer behaviour
        if (newYaw > 180f) newYaw -= 360f
        if (newYaw < -180f) newYaw += 360f

        // Low-pass filter so motion is smooth, but still responsive
        if (abs(newYaw - yawDeg) > deadZoneDeg) {
            yawDeg = lerp(yawDeg, newYaw, alpha)
        }
        if (abs(newPitch - pitchDeg) > deadZoneDeg) {
            pitchDeg = lerp(pitchDeg, newPitch, alpha)
        }
        if (abs(newRoll - rollDeg) > deadZoneDeg) {
            rollDeg = lerp(rollDeg, newRoll, alpha)
        }

        // Detect “big turns” roughly every 90 degrees, for onTurn callback
        val diff = yawDeg - lastTurnYawDeg
        if (abs(diff) > turnStepDeg / 2f) {
            // Update facingIndex: 0,1,2,3 (N,E,S,W style, but you can ignore if you want)
            val steps = (diff / turnStepDeg).toInt()
            facingIndex = (facingIndex + steps).floorMod(4)
            lastTurnYawDeg = yawDeg
            onTurn?.invoke(facingIndex)
        }
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
        // Not needed
    }

    private fun lerp(old: Float, new: Float, a: Float): Float {
        return old + a * (new - old)
    }

    private fun Int.floorMod(mod: Int): Int {
        val r = this % mod
        return if (r < 0) r + mod else r
    }
}
