package llc.arma.ble.data

import android.Manifest
import android.app.Application
import android.bluetooth.*
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.pm.PackageManager
import android.os.Build
import android.os.SystemClock
import android.util.Log
import androidx.core.app.ActivityCompat
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
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.model.BleInfo
import llc.arma.ble.domain.model.ConnectedBleInfo
import llc.arma.ble.domain.repository.BleRepository
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.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume


val FftFrequency.sendData: Byte
    get() {
        return when(this){
            FftFrequency.OFF -> 0
            FftFrequency.F_1 -> 1
            FftFrequency.F_10 -> 2
            FftFrequency.F_25 -> 3
            FftFrequency.F_50 -> 4
            FftFrequency.F_100 -> 5
            FftFrequency.F_200 -> 6
            FftFrequency.F_400 -> 7
            FftFrequency.F_1620 -> 8
            FftFrequency.F_1344 -> 9
        }
    }

val FftAxis.sendData: Byte
    get() {
        return when(this){
            FftAxis.AUTO -> 0
            FftAxis.X -> 1
            FftAxis.Y -> 2
            FftAxis.Z -> 3
        }
    }

val FftViewMode.sendData: Byte
    get() {
        return when(this){
            FftViewMode.SPECTRE -> 0
            FftViewMode.X -> 1
            FftViewMode.Y -> 2
            FftViewMode.Z -> 3
        }
    }

val AccelViewMode.sendData: Byte
    get() {
        return when(this){
            AccelViewMode.ACCELERATION -> 0
            AccelViewMode.PEAK_ACCELERATION -> 1
            AccelViewMode.RMS -> 2
            AccelViewMode.VIBRATION -> 3
            AccelViewMode.ANGLE -> 0
        }
    }

val AccelScale.sendData: Byte
    get() {
        return when(this){
            AccelScale.S_2 -> 0
            AccelScale.S_4 -> 1
            AccelScale.S_8 -> 2
            AccelScale.S_16 -> 3
        }
    }

val serviceUUID: UUID = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002")

val accelerometerReadUUID: UUID = UUID.fromString("00002713-0000-1000-8000-00805f9b34fb")
val temperatureHistoryReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
val accelerometerHistoryReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
val temperatureReadUUID: UUID = UUID.fromString("00002a6e-0000-1000-8000-00805f9b34fb")
val intervalReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
val intervalWriteUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002")
val saveEnabledWriteUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002")
val passwordWriteUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002")
val txWriteUUID: UUID = UUID.fromString("00002a07-0000-1000-8000-00805f9b34fb")
val flashWriteUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002")

@OptIn(ExperimentalUnsignedTypes::class)
fun UByteArray.toTemperature(): Float {
    val uShort = (this[0] + this[1] * 256u).toUShort()

    val result = if (uShort > Short.MAX_VALUE.toUShort()) {
        ((uShort.inv() + 1u).toFloat().unaryMinus()) / 100f
    } else {
        uShort.toFloat() / 100f
    }

    return result
}

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

@Singleton
class BleRepositoryImpl @Inject constructor(
    private val app: Application
) : BleRepository {

    private val ScanResult.timerEnabled: Boolean
        get() {
            return scanRecord?.manufacturerSpecificData?.get(89)?.get(2) == 1.toByte()
        }

    private val ScanResult.info: BleInfo
        get() {
            return BleInfo(
                name = scanRecord?.deviceName ?: "",
                serial = device.address,
                batteryLevel = batteryLevel ?: 0,
                rssi = rssi,
                type = type,
                scanTime = timestampNanos / 1_000_000,
                tx = scanRecord?.txPowerLevel ?: 0,
                recordEnabled = timerEnabled
            )
        }

    private val ScanResult.batteryLevel: Int?
        get() {
            return scanRecord?.manufacturerSpecificData?.get(89)?.get(1)
                ?.toUByte()?.toInt()
        }

    private val ScanResult.type: BleInfo.Type
        get() {
            return when(scanRecord?.manufacturerSpecificData?.get(89)?.get(0)?.toUByte()?.toInt()){
                1 -> BleInfo.Type.BEACON
                2 -> BleInfo.Type.THERMOMETER
                else -> BleInfo.Type.ACCELEROMETER
            }
        }

    private fun ByteArray.getUIntAt(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 val deviceCache = mutableMapOf<String, ScanResult>()
    val resultList: MutableMap<String, BleInfo> = Collections.synchronizedMap(mutableMapOf<String, BleInfo>())

    override fun getConnectedBle(): List<ConnectedBleInfo> {

        if(checkPermission()) {

            return app.getSystemService(BluetoothManager::class.java)
                .getConnectedDevices(BluetoothProfile.GATT)
                .filter {
                    it.name.contains("arma", true)
                }
                .map {
                    ConnectedBleInfo(
                        name = it.name,
                        serial = it.address
                    )
            }

        }else{
            return emptyList()
        }

    }

    override fun getBleAroundFlow(): Flow<Result<List<BleInfo>, BleException>> {

        return if(checkPermission()){

            callbackFlow {

                val bleCallback = object : ScanCallback() {

                    override fun onScanResult(
                        callbackType: Int,
                        result: ScanResult
                    ) {

                        super.onScanResult(callbackType, result)

                        if (checkPermission()) {

                            if (result.scanRecord?.deviceName?.contains("ArmA") == true) {

                                resultList[result.device.address] = result.info

                                deviceCache[result.device.address] = result

                            }

                        } else {
                            CoroutineScope(Dispatchers.IO).launch {
                                send(
                                    Result.failure(BleException.PermissionDenied)
                                )
                            }
                        }

                    }

                    override fun onBatchScanResults(results: MutableList<ScanResult>) {
                        super.onBatchScanResults(results)

                        if (checkPermission()) {

                            results.forEach { result ->

                                if (result.scanRecord?.deviceName?.contains("ArmA") == true) {

                                    resultList[result.device.address] = result.info

                                    deviceCache[result.device.address] = result

                                }

                            }

                        } else {
                            CoroutineScope(Dispatchers.IO).launch {
                                send(
                                    Result.failure(BleException.PermissionDenied)
                                )
                            }
                        }

                    }
                }

                val bleScanner =
                    app.getSystemService(BluetoothManager::class.java).adapter.bluetoothLeScanner

                bleScanner.startScan(
                    listOf(),
                    ScanSettings.Builder()
                        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                        .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                        .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
                        .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
                        .setReportDelay(0L)
                        .build(),
                    bleCallback)

                val timer = Timer().apply {
                    schedule(object : TimerTask() {
                        override fun run() {
                            CoroutineScope(Dispatchers.IO).launch {
                                send(Result.success(resultList.values.toList()))
                            }
                        }
                    }, 500, 500)
                }

                awaitClose {
                    bleScanner.stopScan(bleCallback)
                    timer.cancel()
                }

            }

        } else {

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

        }

    }

    override suspend fun getBleBySerial(
        serial: String
    ): Result<Flow<Ble>, BleException> {

        deviceCache[serial]?.let { result ->

            return when(result.info.type) {
                BleInfo.Type.ACCELEROMETER -> {

                    val tState = suspendCancellableCoroutine {

                        CoroutineScope(Dispatchers.IO).launch {

                            it.resume(readAccelState(result))

                        }

                    }.fold(
                        onFailure = {
                            return Result.failure(it)
                        },
                        onSuccess = {
                            it
                        }
                    )

                    Result.success(
                        flow {

                            while (true) {

                                deviceCache[serial]?.let { newResult ->

                                    emit(

                                        Ble.Accelerometer(
                                            info = newResult.info.copy(
                                                rssi = if((SystemClock.elapsedRealtimeNanos() - newResult.timestampNanos) >  15_000_000_000) {
                                                    null
                                                } else {
                                                    newResult.rssi
                                                }
                                            ),
                                            state = Ble.BleState(
                                                tx = when (result.scanRecord?.txPowerLevel) {
                                                    -40 -> Ble.BleState.TX.MINUS_40
                                                    -20 -> Ble.BleState.TX.MINUS_20
                                                    -16 -> Ble.BleState.TX.MINUS_16
                                                    -12 -> Ble.BleState.TX.MINUS_12
                                                    -8 -> Ble.BleState.TX.MINUS_8
                                                    -4 -> Ble.BleState.TX.MINUS_4
                                                    3 -> Ble.BleState.TX.PLUS_3
                                                    4 -> Ble.BleState.TX.PLUS_4
                                                    else -> Ble.BleState.TX.ZERO
                                                }
                                            ),
                                            accelerometerState = tState
                                        )

                                    )

                                }

                                delay(1_000)

                            }

                        }
                    )
                }
                BleInfo.Type.BEACON -> {
                    Result.success(
                        flow {

                            while (true) {

                                deviceCache[serial]?.let { newResult ->

                                    val state = Ble.BleState(
                                        tx = when (result.scanRecord?.txPowerLevel) {
                                            -40 -> Ble.BleState.TX.MINUS_40
                                            -20 -> Ble.BleState.TX.MINUS_20
                                            -16 -> Ble.BleState.TX.MINUS_16
                                            -12 -> Ble.BleState.TX.MINUS_12
                                            -8 -> Ble.BleState.TX.MINUS_8
                                            -4 -> Ble.BleState.TX.MINUS_4
                                            3 -> Ble.BleState.TX.PLUS_3
                                            4 -> Ble.BleState.TX.PLUS_4
                                            else -> Ble.BleState.TX.ZERO
                                        }
                                    )

                                    emit(

                                        Ble.Beacon(
                                            info = newResult.info.copy(
                                                rssi = if((SystemClock.elapsedRealtimeNanos() - newResult.timestampNanos) >  15_000_000_000) {
                                                    null
                                                } else {
                                                    newResult.rssi
                                                }

                                            ),
                                            state = state
                                        )

                                    )

                                }

                                delay(1_000)

                            }

                        }
                    )
                }
                BleInfo.Type.THERMOMETER -> {

                    val tState = suspendCancellableCoroutine {

                        CoroutineScope(Dispatchers.IO).launch {

                            it.resume(readThermometerState(result))

                        }

                    }.fold(
                        onFailure = {
                            return Result.failure(it)
                        },
                        onSuccess = {
                            it
                        }
                    )

                    Result.success(
                        flow {

                            while (true) {

                                deviceCache[serial]?.let { newResult ->

                                    val state = Ble.BleState(
                                        tx = when (result.scanRecord?.txPowerLevel) {
                                            -40 -> Ble.BleState.TX.MINUS_40
                                            -20 -> Ble.BleState.TX.MINUS_20
                                            -16 -> Ble.BleState.TX.MINUS_16
                                            -12 -> Ble.BleState.TX.MINUS_12
                                            -8 -> Ble.BleState.TX.MINUS_8
                                            -4 -> Ble.BleState.TX.MINUS_4
                                            3 -> Ble.BleState.TX.PLUS_3
                                            4 -> Ble.BleState.TX.PLUS_4
                                            else -> Ble.BleState.TX.ZERO
                                        }
                                    )

                                    emit(

                                        Ble.Thermometer(
                                            info = newResult.info.copy(
                                                rssi = if((SystemClock.elapsedRealtimeNanos() - newResult.timestampNanos) >  15_000_000_000) {
                                                    null
                                                } else {
                                                    newResult.rssi
                                                }

                                            ),
                                            state = state,
                                            thermometerState = tState
                                        )

                                    )

                                }

                                delay(1_000)

                            }

                        }
                    )

                }
            }

        }

        return Result.failure(BleException.UnexpectedResponse)

    }

    private suspend fun readThermometerState(
        record: ScanResult
    ): Result<Ble.Thermometer.ThermometerState, BleException> {

        val temperature = readTemperature(record).fold(
            onFailure = {
                return Result.failure(it)
            },
            onSuccess = { return@fold it }
        )

        val history = readHistoryInterval(record).fold(
            onFailure = {
                return Result.failure(it)
            },
            onSuccess = { return@fold it }
        )

        return Result.success(
            Ble.Thermometer.ThermometerState(
                temperature = temperature,
                saveHistory = record.timerEnabled,
                historyInterval = history
            )
        )

    }

    private suspend fun readAccelState(
        record: ScanResult
    ): Result<Ble.Accelerometer.AccelerometerState, BleException> {

        val history = readHistoryInterval(record).fold(
            onFailure = {
                return Result.failure(it)
            },
            onSuccess = { return@fold it }
        )

        val historyParams = when(record.timerEnabled){
            true -> {

                writeCharacteristic(
                    device = record.device,
                    serviceId = serviceUUID,
                    characteristicId = accelerometerReadUUID,
                    writeData = byteArrayOf(4)
                ).onFailure {
                    return Result.failure(BleException.UnexpectedResponse)
                }

                readCharacteristic(
                    device = record.device,
                    serviceId = serviceUUID,
                    characteristicId = accelerometerReadUUID,
                ).fold(
                    onSuccess = {
                        Log.d("history", it.joinToString { it.toString() })
                        val scale = when(it[1].toInt()){
                            0 -> AccelScale.S_2
                            1 -> AccelScale.S_4
                            2 -> AccelScale.S_8
                            3 -> AccelScale.S_16
                            else -> {
                                return Result.failure(BleException.UnexpectedResponse)
                            }
                        }
                        val mode = when(it[0].toInt()){
                            0 -> AccelViewMode.ACCELERATION
                            1 -> AccelViewMode.PEAK_ACCELERATION
                            2 -> AccelViewMode.RMS
                            3 -> AccelViewMode.VIBRATION
                            4 -> AccelViewMode.ANGLE
                            else -> {
                                return Result.failure(BleException.UnexpectedResponse)
                            }
                        }
                        Ble.Accelerometer.History.Enabled(
                            scale = scale,
                            mode = mode,
                            detailed = false //TODO
                        )
                    },
                    onFailure = {
                        return Result.failure(BleException.UnexpectedResponse)
                    }
                )
            }
            false -> Ble.Accelerometer.History.Disabled
        }

        return Result.success(
            Ble.Accelerometer.AccelerometerState(
                saveHistory = historyParams,
                historyInterval = history
            )
        )

    }

    @OptIn(ExperimentalUnsignedTypes::class)
    private suspend fun readTemperature(
        record: ScanResult
    ): Result<Float, BleException> {

        writeCharacteristic(
            device = record.device,
            serviceId = serviceUUID,
            characteristicId = temperatureReadUUID,
            writeData = byteArrayOf(1, 1)
        ).onFailure {
            return Result.failure(it)
        }

        delay(2_000)

        val dataResult = readCharacteristic(
            device = record.device,
            serviceId = serviceUUID,
            characteristicId = temperatureReadUUID
        ).fold(
            onFailure = {
                return Result.failure(it)
            },
            onSuccess = { return@fold it }
        )

        return Result.success(dataResult.toUByteArray().toTemperature())

    }

    private suspend fun readHistoryInterval(
        record: ScanResult
    ): Result<Long, BleException>  {

        writeCharacteristic(
            device = record.device,
            serviceId = serviceUUID,
            characteristicId = intervalReadUUID,
            writeData = byteArrayOf(3, 0, 0, 0, 0)
        ).onFailure {
            return Result.failure(it)
        }

        val dataResult = readCharacteristic(
            device = record.device,
            serviceId = serviceUUID,
            characteristicId = intervalReadUUID
        ).fold(
            onFailure = {
                return Result.failure(it)
            },
            onSuccess = { return@fold it }
        )

        return Result.success(
            if(dataResult.size == 4){
                dataResult.getUIntAt(0).toLong()
            }else{
                0
            }
        )

    }

    override suspend fun getAccelerometerSpectreBySerial(
        serial: String,
        accelScale: AccelScale,
        accelMode: AccelViewMode,
        fftAxis: FftAxis,
        fftMode: FftViewMode,
        frequency: FftFrequency
    ): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {

        var gatt: BluetoothGatt? = null

        return callbackFlow {

            deviceCache[serial]?.device?.let {

                if (checkPermission()) {

                    gatt = it.connectGatt(app, false, ReadAccelerometerSpectreCallback(
                        app, accelScale, accelMode, fftAxis, fftMode, frequency
                    ) {
                        CoroutineScope(Dispatchers.IO).launch {
                            send(it)
                        }
                    })

                } else {

                    CoroutineScope(Dispatchers.IO).launch {
                        send(Result.failure(BleException.PermissionDenied))
                    }

                    return@callbackFlow

                }

            }

            awaitClose {
                Log.d("measure", "close")
                gatt?.close()
            }

        }

    }

    override suspend fun getTemperatureHistoryBySerial(
        serial: String
    ): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>> {

        var gatt: BluetoothGatt? = null

        return callbackFlow {

            deviceCache[serial]?.device?.let {

                if (checkPermission()) {

                    gatt = it.connectGatt(app, false, ReadTemperatureHistoryCallback(app) {
                        CoroutineScope(Dispatchers.IO).launch {
                            send(it)
                        }
                    })

                } else {

                    CoroutineScope(Dispatchers.IO).launch {
                        send(Result.failure(BleException.PermissionDenied))
                    }

                    return@callbackFlow

                }

            }

            awaitClose {
                gatt?.close()
            }

        }

    }

    override suspend fun getAccelerometerHistoryBySerial(
        serial: String
    ): Flow<Result<ProgressState<List<Ble.Accelerometer.HistoryPoint>>, BleException>> {

        var gatt: BluetoothGatt? = null

        return callbackFlow {

            deviceCache[serial]?.let { result ->

                val device = result.device

                if (checkPermission()) {

                    val state = readAccelState(result).fold(
                        onSuccess = {

                        },
                        onFailure = {
                            null
                        }
                    )

                    var scale: AccelScale? = null
                    var mode: AccelViewMode? = null

                    writeCharacteristic(
                        device = device,
                        serviceId = serviceUUID,
                        characteristicId = accelerometerReadUUID,
                        writeData = byteArrayOf(4)
                    ).fold(
                        onSuccess = {
                            readCharacteristic(
                                device = device,
                                serviceId = serviceUUID,
                                characteristicId = accelerometerReadUUID,
                            ).fold(
                                onSuccess = {
                                    scale = when(it[1].toInt()){
                                        0 -> AccelScale.S_2
                                        1 -> AccelScale.S_4
                                        2 -> AccelScale.S_8
                                        3 -> AccelScale.S_16
                                        else -> null
                                    }
                                    mode = when(it[0].toInt()){
                                        0 -> AccelViewMode.ACCELERATION
                                        1 -> AccelViewMode.PEAK_ACCELERATION
                                        2 -> AccelViewMode.RMS
                                        3 -> AccelViewMode.VIBRATION
                                        4 -> AccelViewMode.ANGLE
                                        else -> null
                                    }
                                },
                                onFailure = { null }
                            )
                        },
                        onFailure = {
                            null
                        }
                    )

                    if(scale != null && mode != null) {

                        gatt = device.connectGatt(
                            app,
                            false,
                            ReadAccelerometerHistoryCallback(mode!!, scale!!, app) {
                                CoroutineScope(Dispatchers.IO).launch {
                                    send(it)
                                }
                            })

                    } else {
                        CoroutineScope(Dispatchers.IO).launch {
                            send(Result.failure(BleException.UnexpectedResponse))
                        }
                    }

                } else {

                    CoroutineScope(Dispatchers.IO).launch {
                        send(Result.failure(BleException.PermissionDenied))
                    }

                }

            }

            awaitClose {
                gatt?.close()
            }

        }

    }

    override suspend fun writeBle(
        serial: String,
        request: Ble.Thermometer.WriteRequest
    ): Result<Unit, BleException> = suspendCancellableCoroutine {

        deviceCache[serial]?.let { scanResult ->

            if(checkPermission()) {

                var gatt: BluetoothGatt? = null

                val callback = WriteThermometerCallback(app, request) { result ->

                    gatt?.close()

                    result.onSuccess {
                        deviceCache.remove(serial)
                        resultList.remove(serial)
                    }

                    it.resume(result)

                }

                gatt = scanResult.device.connectGatt(app, false, callback)

            } else {

                it.resume(Result.failure(BleException.PermissionDenied))

            }

        }

    }

    override suspend fun writeBle(
        serial: String,
        request: Ble.Beacon.WriteRequest
    ): Result<Unit, BleException> = suspendCancellableCoroutine {

        deviceCache[serial]?.let { scanResult ->

            if(checkPermission()) {

                var gatt: BluetoothGatt? = null

                val callback = WriteBeaconCallback(app, request) { result ->

                    gatt?.close()

                    result.onSuccess {
                        deviceCache.remove(serial)
                        resultList.remove(serial)
                    }

                    it.resume(result)

                }

                gatt = scanResult.device.connectGatt(app, false, callback)

            } else {

                it.resume(Result.failure(BleException.PermissionDenied))

            }

        }

    }

    override suspend fun writeBle(
        serial: String,
        request: Ble.Accelerometer.WriteRequest
    ): Result<Unit, BleException> = suspendCancellableCoroutine {

        deviceCache[serial]?.let { scanResult ->

            if(checkPermission()) {

                var gatt: BluetoothGatt? = null

                val callback = WriteAccelerometerCallback(app, request) { result ->

                    gatt?.close()

                    result.onSuccess {
                        deviceCache.remove(serial)
                        resultList.remove(serial)
                    }

                    it.resume(result)

                }

                gatt = scanResult.device.connectGatt(app, false, callback)

            } else {

                it.resume(Result.failure(BleException.PermissionDenied))

            }

        }

    }

    override suspend fun changeBlePassword(
        password: String,
        serial: String
    ): Result<Unit, BleException> {
        deviceCache[serial]?.device?.let {
            return writeCharacteristic(
                device = it,
                serviceId = serviceUUID,
                characteristicId = passwordWriteUUID,
                writeData = mutableListOf(8.toByte()).apply {
                    addAll(password.toByteArray(Charsets.US_ASCII).toList())
                }.toByteArray()
            ).fold(
                onFailure = {
                    Result.failure(it)
                },
                onSuccess = {
                    Result.success(Unit)
                }
            )
        }
        return Result.success(Unit)
    }

    override fun getAccelerometerMeasureBySerialFlow(
        serial: String,
        accelScale: AccelScale,
        accelMode: AccelViewMode,
        fftAxis: FftAxis,
        fftMode: FftViewMode,
        frequency: FftFrequency,
    ): Flow<Result<MeasureData, BleException>> {

        return callbackFlow {

            var gatt: BluetoothGatt? = null

            if(checkPermission()) {

                deviceCache[serial]?.let {

                    gatt = it.device.connectGatt(
                        app,
                        false,
                        ReadAccelerometerCallback(
                            app = app,
                            accelScale = accelScale,
                            accelMode = accelMode,
                            fftAxis = fftAxis,
                            fftMode = fftMode,
                            frequency = frequency
                        ) { result ->
                            CoroutineScope(Dispatchers.IO).launch {
                                send(result)
                            }
                        }
                    )

                }

                awaitClose {
                    gatt?.close()
                }

            } else {
                CoroutineScope(Dispatchers.IO).launch {

                    send(Result.failure(BleException.PermissionDenied))
                }

            }

        }


    }

    private suspend fun readCharacteristic(
        device: BluetoothDevice,
        serviceId: UUID,
        characteristicId: UUID
    ): Result<ByteArray, BleException> = suspendCancellableCoroutine {

        var result: ByteArray?

        val callback = object : BluetoothGattCallback() {

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

                if(status == BluetoothGatt.GATT_SUCCESS) {

                    if (newState == BluetoothProfile.STATE_CONNECTED) {

                        if (checkPermission()) {

                            gatt.discoverServices()

                        } else {
                            it.resume(Result.failure(BleException.PermissionDenied))
                            gatt.disconnect()
                        }

                    } else {
                        gatt.close()
                    }

                } else {

                    it.resume(Result.failure(BleException.PermissionDenied))
                    gatt.disconnect()

                }

            }

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

                if (status == BluetoothGatt.GATT_SUCCESS) {

                    gatt.services?.firstOrNull { service ->
                        service.uuid == serviceId
                    }?.characteristics?.firstOrNull { characteristic ->
                        characteristic.uuid == characteristicId
                    }?.let { char ->

                        if (checkPermission()) {

                            gatt.readCharacteristic(char)

                        } else {

                            gatt.disconnect()
                            it.resume(Result.failure(BleException.PermissionDenied))

                        }

                        return

                    }

                    gatt.disconnect()
                    it.resume(Result.failure(BleException.UnexpectedResponse))

                } else {

                    gatt.disconnect()
                    it.resume(Result.failure(BleException.UnexpectedResponse))

                }

            }

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

            }

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

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

                Log.d("read", "onCharacteristicRead $status")

                if(status == BluetoothGatt.GATT_SUCCESS) {

                    if (checkPermission()) {
                        result = value
                        it.resume(Result.success(result!!))

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

                    }
                    gatt.disconnect()
                } else {

                    it.resume(Result.failure(BleException.UnexpectedResponse))
                    gatt.disconnect()
                }

            }

        }

        if (checkPermission()) {
            device.connectGatt(app, false, callback)
        } else {
            it.resume(Result.failure(BleException.PermissionDenied))
        }

    }

    private suspend fun writeCharacteristic(
        device: BluetoothDevice,
        serviceId: UUID,
        characteristicId: UUID,
        writeData: ByteArray
    ): Result<Unit, BleException> = suspendCancellableCoroutine {

        var bleGatt: BluetoothGatt? = null

        val callback = object : BluetoothGattCallback() {

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

                if (status == BluetoothGatt.GATT_SUCCESS) {

                    if (newState == BluetoothProfile.STATE_CONNECTED) {

                        if (checkPermission()) {

                            gatt.discoverServices()

                        } else {

                            bleGatt?.disconnect()
                            it.resume(Result.failure(BleException.PermissionDenied))

                        }

                    } else {

                        bleGatt?.close()

                    }

                } else {

                    bleGatt?.disconnect()
                    it.resume(Result.failure(BleException.UnexpectedResponse))

                }

            }

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

                if (status == BluetoothGatt.GATT_SUCCESS) {

                    gatt.services?.firstOrNull { service ->
                        service.uuid == serviceId
                    }?.characteristics?.firstOrNull { characteristic ->
                        characteristic.uuid == characteristicId
                    }?.let { char ->

                        if (checkPermission()) {

                            gatt.writeCharacteristic(char, writeData)

                        } else {

                            bleGatt?.disconnect()
                            it.resume(Result.failure(BleException.PermissionDenied))

                        }

                        return

                    }

                    Log.d("write", "service not found")

                    bleGatt?.disconnect()
                    it.resume(Result.failure(BleException.UnexpectedResponse))

                } else {

                    bleGatt?.disconnect()
                    it.resume(Result.failure(BleException.UnexpectedResponse))

                }

            }

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

                if (checkPermission()) {

                    if(status == BluetoothGatt.GATT_SUCCESS) {

                        it.resume(Result.success(Unit))

                    } else {

                        it.resume(Result.failure(BleException.UnexpectedResponse))

                    }

                } else {

                    it.resume(Result.failure(BleException.PermissionDenied))

                }

                gatt.disconnect()

            }

        }

        if (checkPermission()) {

            bleGatt = device.connectGatt(app, false, callback)

        } else {

            it.resume(Result.failure(BleException.PermissionDenied))

        }

    }

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

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

    }

}