package llc.arma.ble.data.repository

import android.app.Application
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.sendData
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
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.AccelViewMode.ACCELERATION
import llc.arma.ble.domain.usecase.AccelViewMode.ANGLE
import llc.arma.ble.domain.usecase.AccelViewMode.PEAK_ACCELERATION
import llc.arma.ble.domain.usecase.AccelViewMode.RMS
import llc.arma.ble.domain.usecase.AccelViewMode.ROTATIONS
import llc.arma.ble.domain.usecase.AccelViewMode.VIBRATION
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

fun getAccelerometerRealtimeData(
    app: Application,
    serial: String,
    accelScale: AccelScale,
    accelMode: AccelViewMode,
    fftAxis: FftAxis,
    fftMode: FftViewMode,
    frequency: FftFrequency,
): Flow<Result<Ble.Accelerometer.RealtimePoint, BleException>> {

    return flow {

        if(app.checkPermission()) {

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

            try {

                val services = connection.discoverServices()

                val characteristic =
                    services.findService(serviceUUID)
                        ?.findCharacteristic(accelerometerReadUUID)
                        ?: throw IllegalStateException()

                characteristic.write(
                    DataByteArray.from(
                        4,
                        accelMode.sendData,
                        accelScale.sendData,
                        fftMode.sendData,
                        fftAxis.sendData,
                        frequency.sendData,
                        1
                    )
                )

                characteristic.getNotifications().collect {

                    val value = it.value

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

                    val result = when(accelMode){
                        VIBRATION -> {
                            Ble.Accelerometer.RealtimePoint.Vibration(
                                (value.get2byteShortAt()
                                    .toFloat() * accelScale.k) / Short.MAX_VALUE
                            )
                        }
                        ANGLE -> {
                            Ble.Accelerometer.RealtimePoint.Angle(
                                x = calculateAngle(
                                    data[2].toFloat(),
                                    data[1].toFloat()
                                ),
                                y = calculateAngle(
                                    data[2].toFloat(),
                                    data[0].toFloat()
                                ),
                                z = calculateAngle(
                                    data[0].toFloat(),
                                    data[1].toFloat()
                                )
                            )
                        }
                        ROTATIONS -> {
                            Ble.Accelerometer.RealtimePoint.Rotation(
                                angle = ((360f / 8f) * ((data[0] / 100f) + 1f)) - 45f,
                                tmp = data[1].toFloat(),
                                turnovers = data[2]
                            )
                        }
                        ACCELERATION,
                        PEAK_ACCELERATION,
                        RMS -> {
                            Ble.Accelerometer.RealtimePoint.Common(
                                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,
                            )
                        }
                    }

                    emit(Result.success(result))

                }


            } catch (err: Exception) {

                err.printStackTrace()

                emit(Result.failure(BleException.UnexpectedResponse))

            } finally {

                connection.disconnect()
                connection.close()

            }

        } else {

            emit(Result.failure(BleException.PermissionDenied))

        }

    }

}