import Taro from '@tarojs/taro'
import React, { useRef, useState } from 'react'
import { Command } from '@/common/bluetooth/command'
import { uint8ArrayToFloat32, uint8ArrayToHex, waitFor } from '@/common/bluetooth/utils'

interface params {
  init: () => void
  state: Object
  startScan: () => void
  measureAndGetLab: () => any
  getAdapterState: () => void
  connect: (any) => void
  disconnect: () => void
}
const Context = React.createContext<params | unknown>(null)

interface stateStype {
  listeners: any
  discovering: boolean
  available: boolean
  connected: any
  connecting: any
  serviceRule: any
  serviceId: any
  characteristicRule: any
  characteristicId: any

  /** 正在执行的命令 */
  command: any
  responseResolve: any
  responseReject: any
  responseTimer: any

  /** 是否显示蓝牙调试信息 */
  debug: any
  // 搜索到的设备
  devices: any
  // 取色仪主动返回的数据
  deviceLab: any
}

const stateObj: stateStype = {
  /** 事件监听器 */
  listeners: new Set(),
  /** 正在扫描设备 */
  discovering: false,
  /** 蓝牙是否可用 */
  available: true,
  /** 当前连接的设备 */
  connected: null,
  /** 正在连接的设备 */
  connecting: null,

  serviceRule: /^0000FFE0/,
  serviceId: null,
  characteristicRule: /^0000FFE1/,
  characteristicId: null,

  /** 正在执行的命令 */
  command: null,
  responseResolve: null,
  responseReject: null,
  responseTimer: null,

  /** 是否显示蓝牙调试信息 */
  debug: true,
  // 搜索到的设备
  devices: [],
  // 取色仪主动返回的数据
  deviceLab: null,
}
interface PropsType {
  children?: React.ReactNode
}
const BlueToothCopy = (props: PropsType) => {
  const refStatus = useRef(stateObj)
  const [state, setState] = useState(refStatus.current)

  const changeStatus = (obj: Object): void => {
    refStatus.current = { ...refStatus.current, ...obj }
    setState({ ...refStatus.current })
  }
  /** 打开蓝牙适配器 */
  const openAdapter = () => {
    return new Promise((resolve, reject) => {
      Taro.openBluetoothAdapter({
        success: resolve,
        fail: reject,
      })
    })
  }
  const subscribe = (cb) => {
    if (cb) {
      changeStatus({
        listeners: refStatus.current.listeners.add(cb),
      })
    }
  }
  /**
   * 推送事件
   * @param {{type: string; data: any}} event
   */
  const emit = (event) => {
    refStatus.current.listeners.forEach((cb) => {
      cb && cb(event)
    })
  }

  const init = async() => {
    try {
      await openAdapter()
    }
    catch (e) {
      changeStatus({ available: false })
    }

    // 绑定事件通知
    Taro.onBluetoothAdapterStateChange((res) => {
      emit({ type: 'stateUpdate', detail: res })
    })
    Taro.onBLEConnectionStateChange((res) => {
      emit({ type: res.connected ? 'connected' : 'disconnect', detail: res })
    })
    Taro.onBLECharacteristicValueChange(({ value }) => notifySubscriber(value))
    subscribe(async(ev) => {
      if (ev.type === 'stateUpdate') {
        // 蓝牙状态发生的变化
        changeStatus({ discovering: ev.detail.discovering, available: ev.detail.available })
      }
      else if (ev.type === 'disconnect' && refStatus.current.connected && refStatus.current.connected.deviceId === ev.detail.deviceId) {
        // 断开连接
        changeStatus({
          connected: null,
          serviceId: null,
          characteristicId: null,
          deviceLab: null,
          devices: [],
        })
        Taro.showToast({ icon: 'none', title: '蓝牙连接已断开' })
      }
      else if (ev.type === 'connected' && refStatus.current.connecting) {
        // 连接成功
        changeStatus({ connected: refStatus.current.connecting, connecting: null })
        Taro.showToast({ title: '蓝牙已连接' })
      }
      else if (ev.type === 'measure') {
        // 监听取色仪主动推送lab
        await measureAndGetLab()
      }
    })
  }

  /**
   * 获取蓝牙适配器状态
   * @returns {Promise<{discovering: boolean; available: boolean}>}
   */
  const getAdapterState = () => {
    return new Promise((resolve, reject) => {
      Taro.getBluetoothAdapterState({
        success: resolve,
        fail: reject,
      })
    })
  }

  // 获取搜索到的设备
  const getDevices = (res) => {
    res.devices.forEach((device) => {
      // 排除掉已搜索到的设备和名称不合法的设备, 将新发现的设备添加到列表中
      if (/^CM/.test(device.name) && !refStatus.current.devices.find(i => i.deviceId === device.deviceId)) {
        changeStatus({ devices: [...refStatus.current.devices, device] })
      }
    })
  }
  /**
   * 启动设备扫描
   * @param {(res: { devices: { name: string, deviceId: string, RSSI: number }[] }) => void} cb
   * @param {number} duration
   */
  const startScan = (duration = 30000) => {
    console.log('开始寻找')
    changeStatus({ devices: [] })
    Taro.onBluetoothDeviceFound(getDevices)
    return new Promise((resolve, reject) => {
      Taro.startBluetoothDevicesDiscovery({
        allowDuplicatesKey: true,
        success: resolve,
        fail: reject,
      })

      if (duration > 0) {
        setTimeout(() => {
          Taro.offBluetoothDeviceFound(getDevices)
          Taro.stopBluetoothDevicesDiscovery()
          console.log('停止搜索')
        }, duration)
      }
    })
  }
  /**
   * 连接设备
   * @param {{ name: string, deviceId: string, RSSI: number }} device
   */
  const connect = async(device) => {
    try {
      changeStatus({ connecting: device })
      console.log('connecting::', device)
      await createConnection(device.deviceId)
      await discoverService(device.deviceId)
      await discoverCharacteristic(device.deviceId)
      await notifyCharacteristicValueChange(device.deviceId)
    }
    catch (e) {
      changeStatus({ connecting: null })
      Taro.showToast({ icon: 'none', title: '蓝牙连接失败' })
      throw e
    }
  }

  /** 断开当前连接的设备 */
  const disconnect = async() => {
    if (!refStatus.current.connected && !refStatus.current.connecting) { return }
    if (refStatus.current.connected) {
      await closeConnection(refStatus.current.connected.deviceId)
      resetCommand()
      changeStatus({
        connected: null,
        serviceId: null,
        characteristicId: null,
        devices: [],
        deviceLab: null,
      })
    }

    if (refStatus.current.connecting) {
      await closeConnection(refStatus.current.connecting.deviceId)
      changeStatus({ connecting: null })
    }
  }

  /** 创建 BLE 连接 */
  function createConnection(deviceId) {
    return new Promise((resolve, reject) => {
      Taro.createBLEConnection({
        deviceId,
        timeout: 2000,
        success: resolve,
        fail: reject,
      })
    })
  }

  /** 关闭 BLE 连接 */
  function closeConnection(deviceId) {
    return new Promise((resolve, reject) => {
      Taro.closeBLEConnection({
        deviceId,
        success: resolve,
        fail: reject,
      })
    })
  }

  /** 搜索服务 */
  function discoverService(deviceId) {
    return new Promise((resolve, reject) => {
      Taro.getBLEDeviceServices({
        deviceId,
        success: ({ services }) => {
          const service = services.find(i => refStatus.current.serviceRule.test(i.uuid))
          if (!service) {
            reject(new Error('服务不可用'))
          }
          else {
            changeStatus({ serviceId: service.uuid })
            resolve(service)
          }
        },
        fail: reject,
      })
    })
  }

  /** 搜索特征 */
  function discoverCharacteristic(deviceId) {
    return new Promise((resolve, reject) => {
      Taro.getBLEDeviceCharacteristics({
        deviceId,
        serviceId: refStatus.current.serviceId,
        success: ({ characteristics }) => {
          const characteristic = characteristics.find(i => refStatus.current.characteristicRule.test(i.uuid))
          if (!characteristic) {
            reject(new Error('特征不可用'))
          }
          else {
            changeStatus({ characteristicId: characteristic.uuid })
            resolve(characteristic)
          }
        },
        fail: reject,
      })
    })
  }

  /** 启动特征通知 */
  function notifyCharacteristicValueChange(deviceId, stateParm = true) {
    return new Promise((resolve, reject) => {
      Taro.notifyBLECharacteristicValueChange({
        deviceId,
        serviceId: refStatus.current.serviceId,
        characteristicId: refStatus.current.characteristicId,
        state: stateParm,
        success: resolve,
        fail: reject,
      })
    })
  }

  /**
   * 通知订阅器
   * @param {ArrayBuffer} buffer
   */
  function notifySubscriber(buffer) {
    if (refStatus.current.command) {
      if (refStatus.current.debug) {
        console.log(`[BLE RESP] ${uint8ArrayToHex(new Uint8Array(buffer))}`)
      }
      refStatus.current.command.fillResponse(buffer)
      if (refStatus.current.command.isComplete) {
        if (refStatus.current.command.isValid && refStatus.current.responseResolve) {
          refStatus.current.responseResolve(refStatus.current.command.response)
        }
        else if (!refStatus.current.command.isValid) {
          refStatus.current.responseReject(new Error('无效数据'))
        }
        resetCommand()
      }
    }
    else {
      const uint8Array = new Uint8Array(buffer)
      if (uint8Array[0] === 0xBB && uint8Array[1] === 1 && uint8Array[3] === 0) {
        const ev = { type: 'measure', detail: { mode: uint8Array[2] } }
        emit(ev)
      }
    }
  }

  /**
   * 发送命令
   * @param {Command}} command
   * @returns {Promise<Uint8Array>}
   */
  function exec(command) {
    // eslint-disable-next-line no-async-promise-executor
    return new Promise(async(resolve, reject) => {
      if (refStatus.current.command) {
        reject(new Error('正在执行其他命令'))
      }
      else {
        try {
          refStatus.current.command = command
          const data = command.data
          for (let i = 0; i < data.length; i++) {
            await sendData(data[i])
          }

          if (command.responseSize <= 0) {
            resolve(true)
            resetCommand()
          }
          else {
            refStatus.current.responseReject = reject
            refStatus.current.responseResolve = resolve
            refStatus.current.responseTimer = setTimeout(() => {
              reject(new Error('命令响应超时'))
              resetCommand()
            }, command.timeout)
          }
        }
        catch (e) {
          reject(e)
        }
      }
    })
  }

  /**
   * 发送命令
   * @param {ArrayBuffer} buffer
   */
  function sendData(buffer) {
    if (refStatus.current.debug) {
      console.log(`[BLE SEND] ${uint8ArrayToHex(new Uint8Array(buffer))}`)
    }
    return new Promise((resolve, reject) => {
      console.log('current:::', refStatus.current)
      Taro.writeBLECharacteristicValue({
        deviceId: refStatus.current.connected.deviceId,
        serviceId: refStatus.current.serviceId,
        characteristicId: refStatus.current.characteristicId,
        value: buffer,
        success: resolve,
        fail: reject,
      })
    })
  }

  function resetCommand() {
    if (refStatus.current.responseTimer) {
      clearTimeout(refStatus.current.responseTimer)
    }
    changeStatus({
      command: null,
      responseResolve: null,
      responseReject: null,
      responseTimer: null,
    })
  }

  /**
   * 测量
   * @param {number} mode
   * @returns {Promise}
   */
  async function measure(mode = 0) {
    console.log('current1:::', Command.WakeUp)
    await exec(Command.WakeUp)
    console.log('current2:::', Command.WakeUp)
    await waitFor(50)
    console.log('current3:::', Command.WakeUp)
    return await exec(Command.measure(mode))
  }

  /**
   * 获取测量的 lab 值
   * @param {number} mode
   * @returns {Promise<{ L: number, a: number, b: number }>}
   */
  async function getLab(mode = 0) {
    await exec(Command.WakeUp)
    await waitFor(50)
    const data: any = await exec(Command.getLab(mode))
    return {
      L: uint8ArrayToFloat32(data.slice(5, 9)),
      a: uint8ArrayToFloat32(data.slice(9, 13)),
      b: uint8ArrayToFloat32(data.slice(13, 17)),
    }
  }

  /**
   * 测量并获取 lab 值
   * @param {number} mode
   * @returns {Promise<{L: number, a: number, b: number}>}
   */
  async function measureAndGetLab(mode = 0) {
    await measure(mode)
    await waitFor(50)
    const lab = await getLab(mode)
    console.log('lab2::', lab)
    changeStatus({ deviceLab: lab })
    return lab
  }

  return (
    <Context.Provider
      children={props.children}
      value={{
        init,
        state,
        startScan,
        measureAndGetLab,
        getAdapterState,
        connect,
        disconnect,
      }}
    />
  )
}
export default BlueToothCopy

export const useBluetoothTwo = () => {
  const res = React.useContext<any>(Context)
  if (res) {
    return { ...res }
  }
  else {
    return {}
  }
}
