// BLE commands: writing commands and high-level device operations

var protocol = require('./protocol')
var connection = require('./connection')

var _cmdSeq = 0
var _pendingAcks = {}

function nextSeq() {
  _cmdSeq = (_cmdSeq + 1) % 256
  return _cmdSeq
}

function getPendingAcks() {
  return _pendingAcks
}

function clearPendingAcks() {
  _pendingAcks = {}
}

// --- low-level write ---

function writeCommand(type, payload) {
  return new Promise(function (resolve, reject) {
    if (!connection.isConnected()) {
      reject({ error_code: 0x0C, error_msg: 'ERR_BLE_DISCONNECTED' })
      return
    }
    var chars = connection.getChars()
    if (!chars.command) {
      reject({ error_code: 0xFF, error_msg: 'command characteristic not found' })
      return
    }

    var seq = nextSeq()
    var payloadWithSeq = (payload || []).concat([seq])
    var buffer = protocol.buildFrame(type, payloadWithSeq)

    _pendingAcks[seq] = { resolve: resolve, reject: reject }

    setTimeout(function () {
      if (_pendingAcks[seq]) {
        _pendingAcks[seq].reject({ error_code: 0xFF, error_msg: 'ACK timeout' })
        delete _pendingAcks[seq]
      }
    }, 5000)

    var deviceId = connection.getDeviceId()
    wx.writeBLECharacteristicValue({
      deviceId: deviceId,
      serviceId: chars.command.serviceId,
      characteristicId: chars.command.uuid,
      value: buffer,
      success: function () {},
      fail: function () {
        delete _pendingAcks[seq]
        reject({ error_code: 0x0C, error_msg: 'ERR_BLE_DISCONNECTED' })
      }
    })
  })
}

function writeRawCommand(bytes) {
  return new Promise(function (resolve, reject) {
    if (!connection.isConnected()) {
      reject({ error_code: 0x0C, error_msg: 'ERR_BLE_DISCONNECTED' })
      return
    }
    var chars = connection.getChars()
    if (!chars.command) {
      reject({ error_code: 0xFF, error_msg: 'command characteristic not found' })
      return
    }
    console.log('[BLE] FFE1 发送', bytes.length + '字节', '| hex:', protocol.bytesToHex(bytes))
    wx.writeBLECharacteristicValue({
      deviceId: connection.getDeviceId(),
      serviceId: chars.command.serviceId,
      characteristicId: chars.command.uuid,
      value: protocol.bytesToBuffer(bytes),
      success: function () {
        resolve({ message: 'success', bytes: bytes })
      },
      fail: function (err) {
        console.error('[BLE] FFE1 发送失败', err)
        reject({ error_code: 0x0C, error_msg: 'ERR_BLE_DISCONNECTED' })
      }
    })
  })
}

function writeCommandWithRetry(type, payload, maxRetries) {
  if (maxRetries === undefined) maxRetries = 3
  var attempt = 0
  function tryOnce() {
    attempt++
    return writeCommand(type, payload).catch(function (err) {
      if (err && err.error_code === 0x0C) {
        return Promise.reject(err)
      }
      if (attempt >= maxRetries) {
        return Promise.reject(err)
      }
      return new Promise(function (resolve) {
        setTimeout(resolve, 500)
      }).then(function () {
        return tryOnce()
      })
    })
  }
  return tryOnce()
}

// --- high-level commands ---

function readDeviceInfo() {
  return new Promise(function (resolve, reject) {
    var chars = connection.getChars()
    var deviceId = connection.getDeviceId()
    if (!connection.isConnected() || !chars.deviceInfo) {
      reject({ msg: '设备未连接或特征值未就绪' })
      return
    }
    wx.readBLECharacteristicValue({
      deviceId: deviceId,
      serviceId: chars.deviceInfo.serviceId,
      characteristicId: chars.deviceInfo.uuid,
      success: function () {},
      fail: function () { reject({ msg: '读取设备信息失败' }) }
    })

    var handler = function (res) {
      if (res.characteristicId.toUpperCase().indexOf(protocol.CHAR.DEVICE_INFO) !== -1) {
        wx.offBLECharacteristicValueChange(handler)
        var bytes = protocol.bufferToBytes(res.value)
        if (bytes.length >= 14) {
          resolve({
            hw_version: (bytes[0] << 8) | bytes[1],
            fw_version: (bytes[2] << 8) | bytes[3],
            device_type: (bytes[4] << 8) | bytes[5],
            device_id: protocol.bytesToHex(bytes.slice(6, 14))
          })
        } else {
          reject({ msg: '设备信息格式错误' })
        }
      }
    }
    wx.onBLECharacteristicValueChange(handler)
  })
}

function setParams(options) {
  if (protocol.PROTOCOL_MODE === 'vendor_33') {
    return writeRawCommand(protocol.buildVendorCommand33(options || {}))
  }

  var regionMask = options.region_mask || protocol.REGION.FULL_FACE
  var wavelength = options.wavelength || protocol.WAVELENGTH.R
  var brightness = options.brightness || 200
  var durationMs = options.duration_ms || 600000
  var mode = options.mode !== undefined ? options.mode : protocol.TREAT_MODE.NORMAL

  var payload = [
    regionMask,
    wavelength,
    brightness,
    protocol.uint32ToBytes(durationMs),
    mode
  ].reduce(function (a, b) {
    return a.concat(Array.isArray(b) ? b : [b])
  }, [])

  return writeCommandWithRetry(protocol.CMD.SET_PARAMS, payload)
}

function startTreatment(regionMask) {
  if (protocol.PROTOCOL_MODE === 'vendor_33') {
    return Promise.resolve({ message: 'vendor_33 uses setParams write as start', region_mask: regionMask })
  }
  var mask = regionMask || protocol.REGION.FULL_FACE
  return writeCommandWithRetry(protocol.CMD.START, [mask])
}

function stopTreatment() {
  if (protocol.PROTOCOL_MODE === 'vendor_33') {
    return writeRawCommand(protocol.buildVendorCommand({
      region_mask: 0,
      wavelength: protocol.WAVELENGTH.R,
      brightness: 0,
      current_gain: 0,
      hold_time: 0,
      control: protocol.CONTROL.IDLE
    }))
  }
  return writeCommandWithRetry(protocol.CMD.STOP, [])
}

function queryStatus() {
  if (protocol.PROTOCOL_MODE === 'vendor_33') {
    return Promise.resolve({ message: 'query status not defined by vendor_33 protocol' })
  }
  return writeCommandWithRetry(protocol.CMD.QUERY_STATUS, [])
}

function bindDevice(userId, bindToken) {
  if (protocol.PROTOCOL_MODE === 'vendor_33') {
    setTimeout(function () {
      connection.emit('bind_result', { success: true, skipped_ble_bind: true })
    }, 0)
    return Promise.resolve({ message: 'BLE bind not defined by vendor_33 protocol', user_id: userId, bind_token: bindToken })
  }

  var userBytes = protocol.uint32ToBytes(parseInt(userId, 10))
  var tokenBytes = protocol.hexToBytes(bindToken)
  var ts = Math.floor(Date.now() / 1000)
  var tsBytes = protocol.uint32ToBytes(ts)

  var payload = [0x01].concat(userBytes).concat(tokenBytes).concat(tsBytes)
  return writeCommandWithRetry(protocol.CMD.BIND, payload)
}

function unbindDevice(userId) {
  if (protocol.PROTOCOL_MODE === 'vendor_33') {
    return Promise.resolve({ message: 'BLE unbind not defined by vendor_33 protocol', user_id: userId })
  }
  var userBytes = protocol.uint32ToBytes(parseInt(userId, 10))
  var payload = [0x02].concat(userBytes)
  return writeCommandWithRetry(protocol.CMD.UNBIND, payload)
}

module.exports = {
  getPendingAcks: getPendingAcks,
  clearPendingAcks: clearPendingAcks,

  writeCommand: writeCommand,
  writeRawCommand: writeRawCommand,
  writeCommandWithRetry: writeCommandWithRetry,

  readDeviceInfo: readDeviceInfo,
  setParams: setParams,
  startTreatment: startTreatment,
  stopTreatment: stopTreatment,
  queryStatus: queryStatus,
  bindDevice: bindDevice,
  unbindDevice: unbindDevice
}
