import Taro, { useEffect, useReducer, useCallback } from '@tarojs/taro';
import { ab2str, str2ab } from '@/utils/arrayBuffer';
import Actions from '@/types/Store/Actions';

enum BlueToothError {
  BlueToothNotOpen = 'BlueToothNotOpen',
  DeviceNotFound = 'DeviceNotFound',
}
type NotifyUUIDs = {
  notifyId: string;
  writeId: string;
  readId: string;
};
type DeviceInfo = {
  deviceId: string;
  uuid: string;
  isPrimary: boolean;
  serviceId: string;
  notifyId: string;
  writeId: string;
  readId: string;
};
type StoreState = {
  bluetoothState: boolean;
  deviceDone: boolean;
  deviceInfo: DeviceInfo;
};

const initState = {
  bluetoothState: false,
  deviceDone: false,
  deviceInfo: {
    deviceId: '',
    uuid: '',
    isPrimary: false,
    serviceId: '',
    notifyId: '',
    writeId: '',
    readId: '',
  },
};

const reducer = (state: StoreState, action: Actions): StoreState => {
  console.log('action: ', action);
  switch (action.type) {
    case 'BLUETOOTH_STATE_CHANGE':
      return {
        ...state,
        bluetoothState: action.payload,
      };
    case 'GET_DEVICE_ID':
      return {
        ...state,
        deviceInfo: {
          ...state.deviceInfo,
          deviceId: action.payload,
        },
      };
    case 'GET_SERVICE_ID':
      return {
        ...state,
        deviceInfo: {
          ...state.deviceInfo,
          serviceId: action.payload,
        },
      };
    case 'GET_NOTIFY_UUID':
      return {
        ...state,
        deviceInfo: {
          ...state.deviceInfo,
          ...action.payload,
        },
      };
    case 'DEVICE_STATE_CHANGE':
      return {
        ...state,
        deviceDone: action.payload,
      };
    default:
      return state;
  }
};

const useBluetooth = ({
  getBluetoothData,
  services,
}: {
  getBluetoothData: (data: string) => void;
  services?: string[];
}) => {
  const [state, dispatch] = useReducer(reducer, initState);

  const warnBluetoothOpen = () => {
    return Taro.showModal({
      title: '警告',
      content: '为保障正常使用，请打开蓝牙功能',
    });
  };

  const devicesDiscoveryHandle = (deviceCode: string) => {
    let timer: NodeJS.Timeout | null = null;

    return openBluetoothAdapter()
      .then(
        () =>
          new Promise((resolve, reject) => {
            timer = setTimeout(() => {
              reject({ msg: BlueToothError.DeviceNotFound });
            }, 5000);
            Taro.onBluetoothDeviceFound(res => {
              res.devices.forEach(device => {
                if (!device.name && !device.localName) {
                  return;
                }
                console.log(
                  'device.name: ',
                  device.name,
                  ' deviceCode: ',
                  deviceCode
                );

                if (device.name === deviceCode) {
                  let deviceId = device.deviceId;
                  Taro.stopBluetoothDevicesDiscovery();
                  dispatch({ type: 'GET_DEVICE_ID', payload: deviceId });
                  if (timer) {
                    clearInterval(timer);
                  }
                  console.log('found device', deviceCode);
                  resolve(device.deviceId);
                }
              });
            });

            let entity =
              services && services.length
                ? {
                    services: services,
                  }
                : {};
            Taro.startBluetoothDevicesDiscovery(entity);
          })
      )
      .then(createBLEConnection)
      .then(getDeviceServices)
      .then(getDeviceCharacter)
      .then(openNotify)
      .then(() => {
        dispatch({ type: 'DEVICE_STATE_CHANGE', payload: true });
      })
      .catch(err => {
        console.error('startBluetoothDevicesDiscovery: ', err);
        Taro.stopBluetoothDevicesDiscovery();
        throw err;
      });
  };

  const createBLEConnection = (deviceId: string): Promise<string> => {
    return new Promise((resolve, reject) => {
      Taro.createBLEConnection({
        deviceId,
      });

      let abStr = '';
      Taro.onBLECharacteristicValueChange(res => {
        console.log(
          `characteristic ${res.characteristicId} has changed, now is ${res.value}`
        );
        console.log(res.value);
        let datastr = ab2str(res.value);
        console.log('获取socket消息： ', datastr);
        if (datastr === '<>') {
          console.log('结束蓝牙以及socket: ', datastr);
          // this.closeDeviceSocket();
          closeBluetoothConnection();
        } else {
          if (
            datastr.substring(0, 1) === '{' &&
            datastr.substring(datastr.length - 1, datastr.length) === '}'
          ) {
            console.log('<---发送完整数据： ', datastr);
            // if (socketTask) {
            //   socketTask.send({
            //     data: str2ab(datastr),
            //     success: msg => {
            //       console.log('发送完整数据：', msg, '--->');
            //     },
            //     fail: err => {
            //       console.log('发送完整数据：', err, '--->');
            //     },
            //   });
            // }
            getBluetoothData(datastr);
          } else if (datastr.substring(0, 1) === '{') {
            console.log('接受头部数据： ', datastr);
            abStr = datastr;
          } else if (
            abStr.length &&
            datastr.substring(datastr.length - 1, datastr.length) === '}'
          ) {
            abStr += datastr;
            console.log('接受尾部数据后： ', abStr);
            console.log('<---发送完整数据： ', abStr);
            // if (socketTask) {
            //   socketTask.send({
            //     data: str2ab(abStr),
            //     success: msg => {
            //       console.log('发送完整数据：', msg, '--->');
            //     },
            //     fail: err => {
            //       console.log('发送完整数据：', err, '--->');
            //     },
            //   });
            // }
            getBluetoothData(datastr);
            abStr = '';
          } else {
            console.log('弃掉数据：', datastr);
          }
        }
      });

      Taro.onBLEConnectionStateChange(res => {
        console.log(
          `device ${res.deviceId} state has changed, connected: ${res.connected}`
        );
        if (res.connected) {
          dispatch({ type: 'DEVICE_STATE_CHANGE', payload: true });
          resolve(res.deviceId);
        } else {
          console.log(res);
          dispatch({ type: 'DEVICE_STATE_CHANGE', payload: false });
          reject('蓝牙未成功连接');
        }
      });
    });
  };

  const closeBluetoothConnection = useCallback(() => {
    Taro.closeBLEConnection({
      deviceId: state.deviceInfo.deviceId,
    })
      .then(() => {
        dispatch({ type: 'DEVICE_STATE_CHANGE', payload: false });
      })
      .catch(err => {
        console.log('断开蓝牙连接失败！');
        console.error(err);
      });
  }, [state]);

  const getDeviceServices = (
    deviceId: string
  ): Promise<{
    deviceId: string;
    serviceId: string;
  }> => {
    return Taro.getBLEDeviceServices({
      deviceId: deviceId,
    }).then(services => {
      const serviceArr = services.services;
      let tempService = serviceArr.find(
        item =>
          item.uuid.toLocaleLowerCase() ===
          '6e401103-b5a3-f393-e0a9-e50e24dcca9e'
      );
      let service: Taro.getBLEDeviceServices.PromisedPropServicesItem = tempService
        ? tempService
        : services[0];

      const serviceId = service.uuid;
      console.log('获取全部services: ', services, '. serviceId: ', serviceId);
      dispatch({ type: 'GET_SERVICE_ID', payload: serviceId });

      return { deviceId: deviceId, serviceId: serviceId };
    });
  };

  const getDeviceCharacter = ({
    deviceId,
    serviceId,
  }: {
    deviceId: string;
    serviceId: string;
  }): Promise<
    NotifyUUIDs & {
      deviceId: string;
      serviceId: string;
    }
  > => {
    console.log('getDeviceCharacter:', deviceId, serviceId);
    const getBLEDeviceCharacteristicsParams = {
      deviceId,
      serviceId,
    };
    console.log(getBLEDeviceCharacteristicsParams);

    return Taro.getBLEDeviceCharacteristics(
      getBLEDeviceCharacteristicsParams
    ).then(res => {
      console.log('getBLEDeviceCharacteristics', res);

      let notifyId = '',
        writeId = '',
        readId = '';
      for (let i = 0; i < res.characteristics.length; i++) {
        let charc = res.characteristics[i];
        if (charc.properties.notify) {
          notifyId = charc.uuid;
        }
        if (charc.properties.write) {
          writeId = charc.uuid;
        }
        if (charc.properties.write) {
          readId = charc.uuid;
        }
      }
      dispatch({
        type: 'GET_NOTIFY_UUID',
        payload: {
          notifyId,
          writeId,
          readId,
        },
      });
      return {
        deviceId,
        serviceId,
        notifyId,
        writeId,
        readId,
      };
    });
  };

  const openNotify = useCallback(
    ({ notifyId, deviceId, serviceId }) => {
      console.log('openNotify deviceId: ', deviceId, ' serviceId: ', serviceId);

      return Taro.notifyBLECharacteristicValueChange({
        deviceId: deviceId,
        serviceId: serviceId,
        characteristicId: notifyId,
        state: true,
      }).then(res => {
        console.log(res);

        let abStr = '';
        Taro.onBLECharacteristicValueChange(res => {
          let msg = ab2str(res.value);
          console.log('获取蓝牙设备消息： ', msg);
          if (msg === '<>') {
            closeBluetoothConnection();
          } else {
            if (
              msg.substring(0, 1) === '{' &&
              msg.substring(msg.length - 1, msg.length) === '}'
            ) {
              getBluetoothData(msg);
            } else if (msg.substring(0, 1) === '{') {
              abStr = msg;
            } else if (
              abStr.length &&
              msg.substring(msg.length - 1, msg.length) === '}'
            ) {
              abStr += msg;
              getBluetoothData(abStr);
              abStr = '';
            } else {
              if (abStr.length) {
                abStr += msg;
              } else {
                console.log('弃掉数据：', msg);
              }
            }
          }
        });
      });
    },
    [state.deviceInfo.deviceId, state.deviceInfo.serviceId]
  );

  const sendMessageToDevice = useCallback(
    (msg: string) => {
      const { deviceInfo } = state;
      console.log(msg, str2ab(msg).byteLength);
      if (deviceInfo.writeId) {
        return Taro.writeBLECharacteristicValue({
          deviceId: deviceInfo.deviceId,
          serviceId: deviceInfo.serviceId,
          characteristicId: deviceInfo.writeId,
          value: str2ab(msg),
        });
      } else {
        return Promise.reject(new Error('no writeId'));
      }
    },
    [state]
  );

  const openBluetoothAdapter = () => {
    return Taro.openBluetoothAdapter()
      .then(res => {
        console.log('openBluetoothAdapter success', res);
        dispatch({ type: 'BLUETOOTH_STATE_CHANGE', payload: true });
      })
      .catch(res => {
        console.log(res);

        if (res.errCode === 10001) {
          Taro.onBluetoothAdapterStateChange(function(res) {
            console.log('onBluetoothAdapterStateChange', res);
            if (res.available) {
              dispatch({ type: 'BLUETOOTH_STATE_CHANGE', payload: true });
            } else {
              dispatch({ type: 'BLUETOOTH_STATE_CHANGE', payload: false });
            }
          });
        } else {
          warnBluetoothOpen();
        }
      });
  };

  useEffect(() => {
    openBluetoothAdapter();
    return () => {
      Taro.closeBluetoothAdapter();
    };
  }, []);

  return {
    state: state.deviceDone,
    devicesDiscoveryHandle,
    sendMessageToDevice,
  };
};

export default useBluetooth;
