package com.common.bluetooth.service.ble

import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.bluetooth.le.ScanResult
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.util.Log.d
import androidx.core.content.ContextCompat
import com.common.bluetooth.callback.BtScanCallBack
import com.common.bluetooth.interfaces.IBluetoothSearch
import kotlinx.coroutines.*
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.CoroutineContext

/**
 * ble搜索器
 *
 * @author Alex Wang
 * @since 2022-2-17
 */
class BtBleSearcher(
    private val mContext: Context,
    private val mBluetoothAdapter: BluetoothAdapter?
) : IBluetoothSearch, CoroutineScope {
    override val coroutineContext: CoroutineContext by lazy { Dispatchers.Default }

    companion object {
        const val TAG = "BleSearcher"
    }

    private var mScanCallback: BtScanCallBack? = null
    private val mScanning = AtomicBoolean(false)

    private fun wrapCallBack(callBack: BtScanCallBack?): BtScanCallBack {
        return object : BtScanCallBack() {
            override fun onComplete() {
                launch(Dispatchers.Main) {
                    callBack?.onComplete()
                }
            }

            override fun onError(msg: String?) {
                launch(Dispatchers.Main) {
                    callBack?.onError(msg)
                }
            }

            override fun onScanResult(callbackType: Int, result: ScanResult) {
                launch(Dispatchers.Main) {
                    callBack?.onResult(result)
                }
            }

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

    override fun startScan(delayTime: Long, callBack: BtScanCallBack?) {
        launch(Dispatchers.Main) {
            val permissionCheck1 = ContextCompat.checkSelfPermission(
                mContext,
                Manifest.permission.ACCESS_COARSE_LOCATION
            )

            val permissionCheck2 = ContextCompat.checkSelfPermission(
                mContext,
                Manifest.permission.ACCESS_FINE_LOCATION
            )

            if (permissionCheck1 or permissionCheck2 != PackageManager.PERMISSION_GRANTED
                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
            ) {
                val err = "Cannot have location permission"
                Log.e(TAG, err)
                callBack?.onError(err)
                return@launch
            }

            if (mScanning.get()) {
                stopScan()
            }

            // 将回调切换到UI线程
            mScanCallback = wrapCallBack(callBack)

            mScanning.set(true)

            // 定义一个回调接口供扫描结束处理
            // 指定扫描特定的支持service的蓝牙设备
            // call startLeScan(UUID[],    BluetoothAdapter.LeScanCallback)
            // 可以使用rssi计算蓝牙设备的距离
            // 计算公式：
            // d = 10^((abs(RSSI) - A) / (10 * n))
            // 其中：
            // d - 计算所得距离
            // RSSI - 接收信号强度（负值）
            // A - 射端和接收端相隔1米时的信号强度
            // n - 环境衰减因子

            // 定义一个回调接口供扫描结束处理
            // 指定扫描特定的支持service的蓝牙设备
            // call startLeScan(UUID[],    BluetoothAdapter.LeScanCallback)
            // 可以使用rssi计算蓝牙设备的距离
            // 计算公式：
            // d = 10^((abs(RSSI) - A) / (10 * n))
            // 其中：
            // d - 计算所得距离
            // RSSI - 接收信号强度（负值）
            // A - 射端和接收端相隔1米时的信号强度
            // n - 环境衰减因子
            mBluetoothAdapter?.bluetoothLeScanner?.startScan(mScanCallback)

            // Stops scanning after a pre-defined scan period.
            // 预先定义停止蓝牙扫描的时间（因为蓝牙扫描需要消耗较多的电量）
            notifyStopScan(delayTime)
        }
    }

    /**
     * 通知停止扫描
     */
    private suspend fun notifyStopScan(delayTime: Long) = withContext(Dispatchers.IO) {
        delay(delayTime)
        d(TAG, "StopScan")
        stopScan()
    }

    override fun stopScan() {
        if (mScanning.get()) {
            mScanning.set(false)
            launch(Dispatchers.Main) {
                mScanCallback!!.onComplete()
            }
            mBluetoothAdapter?.bluetoothLeScanner?.stopScan(mScanCallback)
            mScanCallback = null
        }
    }

    override fun isScanning(): Boolean {
        return mScanning.get()
    }
}