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.common.ProgressState
import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.usecase.AccelScale
import llc.arma.ble.domain.usecase.AccelViewMode
import llc.arma.ble.domain.usecase.FftAxis
import llc.arma.ble.domain.usecase.FftFrequency
import llc.arma.ble.domain.usecase.FftViewMode
import java.nio.ByteBuffer
import java.nio.ByteOrder.LITTLE_ENDIAN
import java.util.UUID

fun ByteArray.get2byteShortAt(): Int {
    val shorts = ShortArray(1)
    ByteBuffer.wrap(this).order(LITTLE_ENDIAN).asShortBuffer()[shorts]
    return shorts[0].toInt()
}

class ReadAccelerometerSpectreCallback(
    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<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>) -> Unit
) : BluetoothGattCallback() {

    enum class Property {
        DATA_SIZE, PACKAGE
    }

    private fun ByteArray.get4byteUIntAt(idx: Int) =
        ((this[idx + 3].toUInt() and 0xFFu) shl 24) or
                ((this[idx + 2].toUInt() and 0xFFu) shl 16) or
                ((this[idx + 1].toUInt() and 0xFFu) shl 8) or
                (this[idx].toUInt() and 0xFFu)

    private fun ByteArray.get2byteUIntAt(idx: Int) =
        ((this[idx + 1].toUInt() and 0xFFu) shl 8) or
                (this[idx].toUInt() and 0xFFu)

    private var readProperty: Property? = null

    init {
        onResult(Result.success(ProgressState.Indeterminate))
    }

    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)

        if(status == BluetoothGatt.GATT_SUCCESS){
            enableNotifications(gatt)
        }

    }

    private fun enableNotifications(
        gatt: BluetoothGatt
    ){

        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)
                }

                Log.d("spectre", "enable notification")

                onResult(Result.success(ProgressState.Indeterminate))
                resultAccelerometerPackage.clear()

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

            return

        }

        onResult(Result.failure(BleException.UnexpectedResponse))

    }

    private var initialValue: Long? = null
    private var frequencyInterval: Long? = null

    private val resultAccelerometerPackage: MutableList<Float> = mutableListOf()

    private var expectedDataSize: Int? = null

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

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

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

    private fun onCommonCharacteristicChanged(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        value: ByteArray,
    ){

        if(characteristic.uuid == accelerometerReadUUID) {

            readProperty = Property.DATA_SIZE
            gatt.getService(serviceUUID).getCharacteristic(accelerometerHistoryReadUUID)?.let {
                gatt.writeCharacteristic(it, byteArrayOf(2))
            }

        }
    }

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

    private fun onCommonCharacteristicRead(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        value: ByteArray,
        status: Int
    ){

        if(status == BluetoothGatt.GATT_SUCCESS){
            when(readProperty){
                Property.DATA_SIZE -> {

                    if(value.contentEquals(byteArrayOf(0, 0))) {
                        onResult(
                            Result.success(
                                ProgressState.Finished(
                                    emptyList()
                                )
                            )
                        )
                        gatt.close()

                    } else {

                        val writeData = mutableListOf(
                            1.toByte(),
                            0.toByte(),
                            0.toByte()
                        ).apply {
                            addAll(value.toList())
                        }.toByteArray()

                        readProperty = Property.PACKAGE
                        gatt.writeCharacteristic(characteristic, writeData)

                    }
                }
                Property.PACKAGE -> {

                    if(value[0] == 250.toByte()){

                        initialValue = value.get4byteUIntAt(8).toLong()
                        frequencyInterval = value.get4byteUIntAt(4).toLong()

                        val accelerometerDataArray = value.asList().subList(16, value.size)

                        resultAccelerometerPackage.addAll(
                            accelerometerDataArray.chunked(2).map {
                                it.toByteArray().get2byteShortAt().toFloat()
                            }.toMutableList()
                        )

                        val nextPackageDataCount = value.get2byteUIntAt(2)
                        expectedDataSize = nextPackageDataCount.toInt() + resultAccelerometerPackage.size

                        onResult(Result.success(ProgressState.Progress(0f / expectedDataSize!!.toFloat())))
                        onResult(Result.success(ProgressState.Progress(resultAccelerometerPackage.size.toFloat() / expectedDataSize!!.toFloat())))

                        if(nextPackageDataCount != 0.toUInt()){

                            if (checkPermission()) {

                                gatt.writeCharacteristic(characteristic, byteArrayOf(5))
                                gatt.readCharacteristic(characteristic)

                            } else {

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

                            }

                        } else {

                            onResult(
                                Result.success(
                                    ProgressState.Finished(
                                        resultAccelerometerPackage.withIndex().map {
                                            Ble.Accelerometer.MeasurePoint(
                                                frequency = frequencyInterval!! * it.index + initialValue!!,
                                                value = (it.value * accelScale.k) / Short.MAX_VALUE
                                            )
                                        }
                                    )
                                )
                            )

                            start(gatt)

                        }

                    } else {

                        if (value[0] == 251.toByte()) {

                            val nextPackageDataCount = value.get2byteUIntAt(2)
                            val temperatureDataArray = value.toList().subList(4, value.size)

                            resultAccelerometerPackage.addAll(
                                temperatureDataArray.chunked(2).map {
                                    it.toByteArray().get2byteShortAt().toFloat()
                                }
                            )

                            onResult(Result.success(ProgressState.Progress(resultAccelerometerPackage.size.toFloat() / expectedDataSize!!.toFloat())))

                            if (nextPackageDataCount != 0.toUInt()) {

                                val writeData = byteArrayOf(5)

                                gatt.writeCharacteristic(characteristic, writeData)
                                gatt.readCharacteristic(characteristic)

                            } else {

                                onResult(
                                    Result.success(
                                        ProgressState.Finished(
                                            resultAccelerometerPackage.withIndex().map {
                                                Ble.Accelerometer.MeasurePoint(
                                                    frequency = frequencyInterval!! * it.index + initialValue!!,
                                                    value = (it.value * accelScale.k) / Short.MAX_VALUE
                                                )
                                            }
                                        )
                                    )
                                )

                                start(gatt)

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

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

                }

            }

        }
    }

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

        if(readProperty !== null) {

            if (status == BluetoothGatt.GATT_SUCCESS) {

                if (checkPermission()) {

                    gatt.readCharacteristic(characteristic)

                } else {

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

                }

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

    }

    override fun onDescriptorWrite(
        gatt: BluetoothGatt,
        descriptor: BluetoothGattDescriptor,
        status: Int
    ) {
        super.onDescriptorWrite(gatt, descriptor, status)
        start(gatt)
    }

    private fun start(
        gatt: BluetoothGatt,
    ){

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

            if (checkPermission()) {

                val payload = byteArrayOf(
                    4,
                    accelMode.sendData,
                    accelScale.sendData,
                    fftMode.sendData,
                    fftAxis.sendData,
                    frequency.sendData,
                    2
                )
                readProperty = null
                gatt.writeCharacteristic(it, payload)

                onResult(Result.success(ProgressState.Indeterminate))
                resultAccelerometerPackage.clear()

            } else {

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

            }

            return

        }

    }

    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)
        }

    }

}