package llc.arma.ble.data.repository

import android.app.Application
import android.util.Log
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.fromByte
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.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 no.nordicsemi.android.common.core.DataByteArray
import no.nordicsemi.android.kotlin.ble.client.main.callback.ClientBleGatt


@OptIn(ExperimentalUnsignedTypes::class)
fun getAccelerometerHistory(
    address: String,
    app: Application,
): Flow<Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, BleException>> {

    return flow {

        var lastMeasureSystemTime: Long? = null

        var bleMeasureInterval: Long? = null
        var bleRealTime: Long? = null
        var bleLastMeasureTime: Long? = null

        val resultPackage: MutableList<Float> = mutableListOf()

        val result = mutableListOf<List<UByte>>()

        var expectedDataSize: Int? = null

        if(app.checkPermission()) {

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

            try {

                val specData = connection.discoverServices()
                    .findService(serviceUUID)
                    ?.findCharacteristic(accelerometerReadUUID)
                    ?.let {
                        it.write(DataByteArray.from(4))
                        it.read()
                    } ?: throw IllegalStateException()

                val scale = AccelScale.fromByte(specData.value[1]) ?: throw IllegalStateException()
                val mode = AccelViewMode.fromByte(specData.value[0]) ?: throw IllegalStateException()

                val characteristic = connection.discoverServices()
                    .findService(serviceUUID)
                    ?.findCharacteristic(accelerometerHistoryReadUUID)

                if(characteristic != null) {

                    characteristic.write(DataByteArray.from(2))

                    var value = characteristic.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()

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


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

                            Log.d("dataTable", "next package fata size $nextPackageDataCount")

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

                                bleMeasureInterval = value.get4byteUIntAt(4).toLong()
                                bleLastMeasureTime = value.get4byteUIntAt(8).toLong()
                                bleRealTime = value.get4byteUIntAt(12).toLong()

                                lastMeasureSystemTime =
                                    System.currentTimeMillis() - ((bleRealTime - bleLastMeasureTime) * 1_000)

                                Log.d("dataTable",
                                    "bleMeasureInterval $bleMeasureInterval " +
                                            "bleLastMeasureTime $bleLastMeasureTime " +
                                            "bleRealTime $bleRealTime " +
                                            "lastMeasureSystemTime $lastMeasureSystemTime " +
                                            "data size ${value.toUByteArray().asList().subList(16, value.size).size}"
                                )

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

                            } else {

                                value.toUByteArray().asList().subList(4, value.size)

                            }

                            result.add(value.toUByteArray().toList())
                            nextPackageDataCount = value.get2byteUIntAt(2)

                            resultPackage.addAll(
                                packageDataArray.chunked(2).map {
                                    it.toUByteArray().toByteArray().get2byteShortAt().toFloat()
                                }.toMutableList()
                            )

                            expectedDataSize =
                                nextPackageDataCount.toInt() + resultPackage.size

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

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

                        }

                        emit(
                            Result.success(
                                ProgressState.Finished(
                                    when (mode) {
                                        AccelViewMode.ROTATIONS -> {
                                            resultPackage.withIndex().map {
                                                Ble.Accelerometer.HistoryPoint.Rotation(
                                                    date = lastMeasureSystemTime!! - (((resultPackage.size - 1) - it.index) * bleMeasureInterval!!),
                                                    value = it.value.toLong()
                                                )
                                            }
                                        }
                                        AccelViewMode.ACCELERATION,
                                        AccelViewMode.PEAK_ACCELERATION,
                                        AccelViewMode.RMS -> {
                                            resultPackage.chunked(3).withIndex().map {
                                                Ble.Accelerometer.HistoryPoint.Angle(
                                                    date = lastMeasureSystemTime!! - (((resultPackage.size - 1) - it.index) * bleMeasureInterval!!),
                                                    x = (it.value[0] * scale.k) / Short.MAX_VALUE,
                                                    y = (it.value[1] * scale.k) / Short.MAX_VALUE,
                                                    z = (it.value[2] * scale.k) / Short.MAX_VALUE
                                                )
                                            }
                                        }

                                        AccelViewMode.ANGLE -> {
                                            resultPackage.chunked(3).withIndex().map {
                                                Ble.Accelerometer.HistoryPoint.Angle(
                                                    date = lastMeasureSystemTime!! - (((resultPackage.size - 1) - it.index) * bleMeasureInterval!!),
                                                    x = calculateAngle(
                                                        it.value[2],
                                                        it.value[1]
                                                    ),
                                                    y = calculateAngle(
                                                        it.value[2],
                                                        it.value[0]
                                                    ),
                                                    z = calculateAngle(
                                                        it.value[0],
                                                        it.value[1]
                                                    )
                                                )
                                            }
                                        }

                                        AccelViewMode.VIBRATION -> {
                                            resultPackage.withIndex().map {
                                                Ble.Accelerometer.HistoryPoint.Vibration(
                                                    date = lastMeasureSystemTime!! - (((resultPackage.size - 1) - it.index) * bleMeasureInterval!!),
                                                    value = (it.value * scale.k) / Short.MAX_VALUE
                                                )
                                            }
                                        }
                                    }
                                )
                            )

                        )

                    }


                } else {

                    emit(Result.failure(BleException.UnexpectedResponse))

                }

            } catch (err: Throwable) {
                err.printStackTrace()
                emit(Result.failure(BleException.UnexpectedResponse))

            } finally {

                connection.close()

            }

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

        }

    }



}