package llc.arma.ble.data.repository

import android.app.Application
import android.bluetooth.BluetoothGattDescriptor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import llc.arma.ble.data.repository.extensions.checkPermission
import llc.arma.ble.data.repository.extensions.get2byteShortAt
import llc.arma.ble.data.repository.extensions.get2byteUIntAt
import llc.arma.ble.data.repository.extensions.get4byteUIntAt
import llc.arma.ble.data.repository.extensions.sendData
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 no.nordicsemi.android.common.core.DataByteArray
import no.nordicsemi.android.kotlin.ble.client.main.callback.ClientBleGatt
import java.util.UUID

fun readAccelerometerSpectre(
    address: String,
    app: Application,
    accelScale: AccelScale,
    accelMode: AccelViewMode,
    fftAxis: FftAxis,
    fftMode: FftViewMode,
    frequency: FftFrequency,
): Flow<Result<ProgressState<List<Ble.Accelerometer.SpectrePoint>>, BleException>> {

    return flow {

        if(app.checkPermission()) {

            val connection =
                ClientBleGatt.connect(app, address, CoroutineScope(Dispatchers.Default))

            try {

                val service = connection.discoverServices()
                    .findService(serviceUUID) ?: throw IllegalStateException()

                val characteristic = service.findCharacteristic(accelerometerReadUUID)
                    ?: throw IllegalStateException()

                val historyCharacteristic = service.findCharacteristic(accelerometerHistoryReadUUID)
                    ?: throw IllegalStateException()

                characteristic.findDescriptor(
                    UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
                )?.write(DataByteArray(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE))
                    ?: throw IllegalStateException()

                characteristic.write(
                    DataByteArray(byteArrayOf(
                        4,
                        accelMode.sendData,
                        accelScale.sendData,
                        fftMode.sendData,
                        fftAxis.sendData,
                        frequency.sendData,
                        2
                    ))
                )

                characteristic.getNotifications().collect {

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

                    val resultAccelerometerPackage: MutableList<Float> = mutableListOf()

                    var expectedDataSize: Int? = null

                    historyCharacteristic.write(DataByteArray.from(2))

                    var value = historyCharacteristic.read().value

                    if (value.contentEquals(byteArrayOf(0, 0))) {

                        emit(Result.success(ProgressState.Finished(emptyList())))

                    } else {

                        var nextPackageDataCount = value.get2byteUIntAt(0)

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

                        historyCharacteristic.write(DataByteArray(writeData))
                        value = historyCharacteristic.read().value


                        while (nextPackageDataCount.toInt() != 0) {

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

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

                                value.asList().subList(16, value.size)

                            } else {
                                value.asList().subList(4, value.size)

                            }

                            nextPackageDataCount = value.get2byteUIntAt(2)

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

                            expectedDataSize = nextPackageDataCount.toInt() + resultAccelerometerPackage.size

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

                            historyCharacteristic.write(DataByteArray.from(5))
                            value = historyCharacteristic.read().value

                        }

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

                        )

                        characteristic.write(DataByteArray(byteArrayOf(
                            4,
                            accelMode.sendData,
                            accelScale.sendData,
                            fftMode.sendData,
                            fftAxis.sendData,
                            frequency.sendData,
                            2
                        )))

                    }

                }


            } catch (err: Throwable) {

                err.printStackTrace()
                emit(Result.failure(BleException.UnexpectedResponse))

            } finally {

                connection.close()

            }

        } else {

            emit(Result.failure(BleException.PermissionDenied))

        }

    }



}