package llc.arma.ble.data

import android.Manifest
import android.app.Application
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.usecase.AccelScale
import llc.arma.ble.domain.usecase.AccelViewMode
import llc.arma.ble.domain.usecase.MeasureData
import llc.arma.ble.domain.usecase.FftAxis
import llc.arma.ble.domain.usecase.FftFrequency
import llc.arma.ble.domain.usecase.FftViewMode
import java.util.UUID
import kotlin.math.PI
import kotlin.math.atan
import kotlin.math.pow
import kotlin.math.sqrt


class ReadAccelerometerCallback(
    private val app: Application,
    private val accelScale: AccelScale,
    private val accelMode: AccelViewMode,
    private val fftAxis: FftAxis,
    private val fftMode: FftViewMode,
    private val frequency: FftFrequency,
    private val onResult: (Result<MeasureData, BleException>) -> Unit
) : BluetoothGattCallback() {

    override fun onConnectionStateChange(
        gatt: BluetoothGatt,
        status: Int,
        newState: Int
    ) {
        super.onConnectionStateChange(gatt, status, newState)

        if(status == BluetoothGatt.GATT_SUCCESS){

            if(newState == BluetoothGatt.STATE_CONNECTED){

                if (checkPermission()) {
                    gatt.discoverServices()

                } else {
                    onResult(Result.failure(BleException.UnexpectedResponse))
                    gatt.close()
                }

            }

        } else {

            onResult(Result.failure(BleException.UnexpectedResponse))
            gatt.close()

        }

    }

    override fun onServicesDiscovered(
        gatt: BluetoothGatt,
        status: Int
    ) {
        super.onServicesDiscovered(gatt, status)

        Log.d("accel", "onServicesDiscovered")

        if(status == BluetoothGatt.GATT_SUCCESS){

            gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {

                if (checkPermission()) {

                    val payload = byteArrayOf(
                        4,
                        accelMode.sendData,
                        accelScale.sendData,
                        fftMode.sendData,
                        fftAxis.sendData,
                        frequency.sendData,
                        1
                    )

                    gatt.writeCharacteristic(it, payload)

                } else {

                    onResult(Result.failure(BleException.PermissionDenied))
                    gatt.close()

                }

            }

        }

    }

    override fun onCharacteristicWrite(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        status: Int
    ) {
        super.onCharacteristicWrite(gatt, characteristic, status)

        Log.d("accel", "request written")

        if(status == BluetoothGatt.GATT_SUCCESS){

            gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {

                if (checkPermission()) {

                    gatt.setCharacteristicNotification(it, true)

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                        gatt.writeDescriptor(it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
                    } else {
                        val descriptor = it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
                        descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
                        gatt.writeDescriptor(descriptor)
                    }

                } else {

                    onResult(Result.failure(BleException.PermissionDenied))
                    gatt.close()

                }

            }

        }

    }

    @Deprecated("Deprecated in Java")
    override fun onCharacteristicChanged(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic
    ) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
            super.onCharacteristicChanged(gatt, characteristic)
            onCommonCharacteristicRead(characteristic.value)
        }
    }

    override fun onCharacteristicChanged(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        value: ByteArray
    ) {
        super.onCharacteristicChanged(gatt, characteristic, value)
        onCommonCharacteristicRead(value)
    }

    private fun onCommonCharacteristicRead(
        value: ByteArray,
    ){



        val result = if(accelMode == AccelViewMode.VIBRATION){

            MeasureData.Vibration((value.get2byteShortAt().toFloat() * accelScale.k) / Short.MAX_VALUE)

        } else {

            val data = value.toList().chunked(2).map {
                it.toByteArray().get2byteShortAt()
            }

            Log.d("accel", "x: ${data[0]} y: ${data[1]} z: ${data[2]} bytes: ${value.joinToString { it.toString() }}")

            val x: Float
            val y: Float
            val z: Float

            if (accelMode == AccelViewMode.ANGLE) {

                x = calculateZAngle(data[2].toFloat(), data[1].toFloat()) * 180f / Math.PI.toFloat()
                y = calculateZAngle(data[2].toFloat(), data[0].toFloat()) * 180f / Math.PI.toFloat()
                z = calculateZAngle(data[0].toFloat(), data[1].toFloat()) * 180f / Math.PI.toFloat()

            } else {

                x = (data[0].toFloat() * accelScale.k) / Short.MAX_VALUE
                y = (data[1].toFloat() * accelScale.k) / Short.MAX_VALUE
                z = (data[2].toFloat() * accelScale.k) / Short.MAX_VALUE

            }

            val state = MeasureData.Accelerate(
                x = x,
                y = y,
                z = z
            )

            state

        }

        onResult(Result.success(result))

    }

    private fun checkPermission(): Boolean {

        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
                    PackageManager.PERMISSION_GRANTED &&
                    ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
                    PackageManager.PERMISSION_GRANTED
        } else {
            return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
                    PackageManager.PERMISSION_GRANTED &&
                    ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
                    PackageManager.PERMISSION_GRANTED
        }
    }

    private fun BluetoothGatt.writeCharacteristic(
        characteristic: BluetoothGattCharacteristic,
        data: ByteArray
    ): Result<Unit, BleException>{

        return if(checkPermission()){

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
            }else{
                characteristic.value = data
                writeCharacteristic(characteristic)
            }

            Result.success(Unit)

        } else {
            Result.failure(BleException.PermissionDenied)
        }

    }

}

fun calculateAngle(
    targetAxis: Float,
    firstAxis: Float,
    secondAxis: Float
): Float {

    return atan(targetAxis.div(sqrt(firstAxis.pow(2) + secondAxis.pow(2))))

}

public fun calculateZAngle(
    x: Float,
    y: Float
): Float {

    var x = x

    if(x == 0f && y == 0f){
        x = 0.0001f
    }

    if(x > 0){
        return atan(y/x)
    }

    if(x < 0 && y >= 0){
        return atan(y/x) + PI.toFloat()
    }

    if(x < 0 && y < 0){
        return atan(y/x) - PI.toFloat()
    }

    if(x == 0f && y > 0){
        return PI.toFloat() / 2f
    }

    if(x == 0f && y < 0){
        return -PI.toFloat() / 2f
    }

    return 0f

}