text: const formatMessage = require('format-message');
  
  const ArgumentType = require('../../extension-support/argument-type');
  const BlockType = require('../../extension-support/block-type');
  const ProgramModeType = require('../../extension-support/program-mode-type');
  
  const ArduinoPeripheral = require('../arduinoCommen/arduino-peripheral');
  
  /**
   * The list of USB device filters.
   * @readonly
   */
  const PNPID_LIST = [
      // CH340
      'USB\\VID_1A86&PID_7523',
      // PL2303
      'USB\\VID_067B&PID_2303',
      // FT232
      'USB\\VID_0403&PID_6001',
      // CP2102
      'USB\\VID_10C4&PID_EA60'
  ];
  
  /**
   * Configuration of serialport
   * @readonly
   */
  const SERIAL_CONFIG = {
      baudRate: 57600,
      dataBits: 8,
      stopBits: 1
  };
  
  /**
   * Configuration for arduino-cli.
   * @readonly
   */
  const DIVECE_OPT = {
      type: 'arduino',
      fqbn: 'arduino:avr:mini:cpu=atmega328',
      firmware: 'arduinoUno.standardFirmata.ino.hex'
  };
  
  const Pins = {
      D0: '0',
      D1: '1',
      D2: '2',
      D3: '3',
      D4: '4',
      D5: '5',
      D6: '6',
      D7: '7',
      D8: '8',
      D9: '9',
      D10: '10',
      D11: '11',
      D12: '12',
      D13: '13',
      A0: 'A0',
      A1: 'A1',
      A2: 'A2',
      A3: 'A3',
      A4: 'A4',
      A5: 'A5'
  };
  
  
  const Level = {
      High: 'HIGH',
      Low: 'LOW'
  };
  
  const Buadrate = {
      B4800: '4800',
      B9600: '9600',
      B19200: '19200',
      B38400: '38400',
      B57600: '57600',
      B115200: '115200'
  };
  
  const Eol = {
      Warp: 'warp',
      NoWarp: 'noWarp'
  };
  
  const Mode = {
      Input: 'INPUT',
      Output: 'OUTPUT',
      InputPullup: 'INPUT_PULLUP'
  };
  
  const InterrupMode = {
      Rising: 'RISING',
      Falling: 'FALLING',
      Change: 'CHANGE',
      Low: 'LOW'
  };
  
  const DataType = {
      WholeNumber: 'WHOLE_NUMBER',
      Decimal: 'DECIMAL',
      String: 'STRING'
  };
  
  /**
   * Manage communication with a Arduino Mini peripheral over a OpenBlock Link client socket.
   */
  class ArduinoMini extends ArduinoPeripheral{
      /**
       * Construct a Arduino communication object.
       * @param {Runtime} runtime - the OpenBlock runtime
       * @param {string} deviceId - the id of the extension
       * @param {string} originalDeviceId - the original id of the peripheral, like xxx_arduinoUno
       */
      constructor (runtime, deviceId, originalDeviceId) {
          super(runtime, deviceId, originalDeviceId, PNPID_LIST, SERIAL_CONFIG, DIVECE_OPT);
      }
  }
  
  /**
   * OpenBlock blocks to interact with a Arduino Mini peripheral.
   */
  class OpenBlockArduinoMiniDevice {
      /**
       * @return {string} - the ID of this extension.
       */
      static get DEVICE_ID () {
          return 'arduinoMini';
      }
  
      get PINS_MENU () {
          return [
              {
                  text: '0',
                  value: Pins.D0
              },
              {
                  text: '1',
                  value: Pins.D1
              },
              {
                  text: '2',
                  value: Pins.D2
              },
              {
                  text: '3',
                  value: Pins.D3
              },
              {
                  text: '4',
                  value: Pins.D4
              },
              {
                  text: '5',
                  value: Pins.D5
              },
              {
                  text: '6',
                  value: Pins.D6
              },
              {
                  text: '7',
                  value: Pins.D7
              },
              {
                  text: '8',
                  value: Pins.D8
              },
              {
                  text: '9',
                  value: Pins.D9
              },
              {
                  text: '10',
                  value: Pins.D10
              },
              {
                  text: '11',
                  value: Pins.D11
              },
              {
                  text: '12',
                  value: Pins.D12
              },
              {
                  text: '13',
                  value: Pins.D13
              },
              {
                  text: 'A0',
                  value: Pins.A0
              },
              {
                  text: 'A1',
                  value: Pins.A1
              },
              {
                  text: 'A2',
                  value: Pins.A2
              },
              {
                  text: 'A3',
                  value: Pins.A3
              },
              {
                  text: 'A4',
                  value: Pins.A4
              },
              {
                  text: 'A5',
                  value: Pins.A5
              }
          ];
      }
  
      get MODE_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'arduinoUno.modeMenu.input',
                      default: 'input',
                      description: 'label for input pin mode'
                  }),
                  value: Mode.Input
              },
              {
                  text: formatMessage({
                      id: 'arduinoUno.modeMenu.output',
                      default: 'output',
                      description: 'label for output pin mode'
                  }),
                  value: Mode.Output
              },
              {
                  text: formatMessage({
                      id: 'arduinoUno.modeMenu.inputPullup',
                      default: 'input-pullup',
                      description: 'label for input-pullup pin mode'
                  }),
                  value: Mode.InputPullup
              }
          ];
      }
  
      get DIGITAL_PINS_MENU () {
          return [
              {
                  text: '0',
                  value: Pins.D0
              },
              {
                  text: '1',
                  value: Pins.D1
              },
              {
                  text: '2',
                  value: Pins.D2
              },
              {
                  text: '3',
                  value: Pins.D3
              },
              {
                  text: '4',
                  value: Pins.D4
              },
              {
                  text: '5',
                  value: Pins.D5
              },
              {
                  text: '6',
                  value: Pins.D6
              },
              {
                  text: '7',
                  value: Pins.D7
              },
              {
                  text: '8',
                  value: Pins.D8
              },
              {
                  text: '9',
                  value: Pins.D9
              },
              {
                  text: '10',
                  value: Pins.D10
              },
              {
                  text: '11',
                  value: Pins.D11
              },
              {
                  text: '12',
                  value: Pins.D12
              },
              {
                  text: '13',
                  value: Pins.D13
              }
          ];
      }
  
      get ANALOG_PINS_MENU () {
          return [
              {
                  text: 'A0',
                  value: Pins.A0
              },
              {
                  text: 'A1',
                  value: Pins.A1
              },
              {
                  text: 'A2',
                  value: Pins.A2
              },
              {
                  text: 'A3',
                  value: Pins.A3
              },
              {
                  text: 'A4',
                  value: Pins.A4
              },
              {
                  text: 'A5',
                  value: Pins.A5
              }
          ];
      }
  
      get LEVEL_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'arduinoUno.levelMenu.high',
                      default: 'high',
                      description: 'label for high level'
                  }),
                  value: Level.High
              },
              {
                  text: formatMessage({
                      id: 'arduinoUno.levelMenu.low',
                      default: 'low',
                      description: 'label for low level'
                  }),
                  value: Level.Low
              }
          ];
      }
  
      get PWM_PINS_MENU () {
          return [
              {
                  text: '3',
                  value: Pins.D3
              },
              {
                  text: '5',
                  value: Pins.D5
              },
              {
                  text: '6',
                  value: Pins.D6
              },
              {
                  text: '9',
                  value: Pins.D9
              },
              {
                  text: '10',
                  value: Pins.D10
              },
              {
                  text: '11',
                  value: Pins.D11
              }
          ];
      }
  
      get INTERRUPT_PINS_MENU () {
          return [
              {
                  text: '2',
                  value: Pins.D2
              },
              {
                  text: '3',
                  value: Pins.D3
              }
          ];
      }
  
      get INTERRUP_MODE_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'arduinoUno.InterrupModeMenu.risingEdge',
                      default: 'rising edge',
                      description: 'label for rising edge interrup'
                  }),
                  value: InterrupMode.Rising
              },
              {
                  text: formatMessage({
                      id: 'arduinoUno.InterrupModeMenu.fallingEdge',
                      default: 'falling edge',
                      description: 'label for falling edge interrup'
                  }),
                  value: InterrupMode.Falling
              },
              {
                  text: formatMessage({
                      id: 'arduinoUno.InterrupModeMenu.changeEdge',
                      default: 'change edge',
                      description: 'label for change edge interrup'
                  }),
                  value: InterrupMode.Change
              },
              {
                  text: formatMessage({
                      id: 'arduinoUno.InterrupModeMenu.low',
                      default: 'low',
                      description: 'label for low interrup'
                  }),
                  value: InterrupMode.Low
              }
          ];
      }
  
      get BAUDTATE_MENU () {
          return [
              {
                  text: '4800',
                  value: Buadrate.B4800
              },
              {
                  text: '9600',
                  value: Buadrate.B9600
              },
              {
                  text: '19200',
                  value: Buadrate.B19200
              },
              {
                  text: '38400',
                  value: Buadrate.B38400
              },
              {
                  text: '57600',
                  value: Buadrate.B57600
              },
              {
                  text: '115200',
                  value: Buadrate.B115200
              }
          ];
      }
  
      get EOL_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'arduinoUno.eolMenu.warp',
                      default: 'warp',
                      description: 'label for warp print'
                  }),
                  value: Eol.Warp
              },
              {
                  text: formatMessage({
                      id: 'arduinoUno.eolMenu.noWarp',
                      default: 'no-warp',
                      description: 'label for no warp print'
                  }),
                  value: Eol.NoWarp
              }
          ];
      }
  
      get DATA_TYPE_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'arduinoUno.dataTypeMenu.wholeNumber',
                      default: 'whole number',
                      description: 'label for whole number'
                  }),
                  value: DataType.WholeNumber
              },
              {
                  text: formatMessage({
                      id: 'arduinoUno.dataTypeMenu.decimal',
                      default: 'decimal',
                      description: 'label for decimal number'
                  }),
                  value: DataType.Decimal
              },
              {
                  text: formatMessage({
                      id: 'arduinoUno.dataTypeMenu.string',
                      default: 'string',
                      description: 'label for string'
                  }),
                  value: DataType.String
              }
          ];
      }
  
      /**
       * Construct a set of Arduino blocks.
       * @param {Runtime} runtime - the OpenBlock runtime.
       * @param {string} originalDeviceId - the original id of the peripheral, like xxx_arduinoUno
       */
      constructor (runtime, originalDeviceId) {
          /**
           * The OpenBlock runtime.
           * @type {Runtime}
           */
          this.runtime = runtime;
  
          // Create a new Arduino mini peripheral instance
          this._peripheral = new ArduinoMini(this.runtime, OpenBlockArduinoMiniDevice.DEVICE_ID, originalDeviceId);
      }
  
      /**
       * @returns {Array.<object>} metadata for this extension and its blocks.
       */
      getInfo () {
          return [
              {
                  id: 'pin',
                  name: formatMessage({
                      id: 'arduinoUno.category.pins',
                      default: 'Pins',
                      description: 'The name of the arduino uno device pin category'
                  }),
                  color1: '#4C97FF',
                  color2: '#3373CC',
                  color3: '#3373CC',
  
                  blocks: [
                      {
                          opcode: 'setPinMode',
                          text: formatMessage({
                              id: 'arduinoUno.pins.setPinMode',
                              default: 'set pin [PIN] mode [MODE]',
                              description: 'arduinoUno set pin mode'
                          }),
                          blockType: BlockType.COMMAND,
                          arguments: {
                              PIN: {
                                  type: ArgumentType.STRING,
                                  menu: 'pins',
                                  defaultValue: Pins.D0
                              },
                              MODE: {
                                  type: ArgumentType.STRING,
                                  menu: 'mode',
                                  defaultValue: Mode.Input
                              }
                          }
                      },
                      {
                          opcode: 'setDigitalOutput',
                          text: formatMessage({
                              id: 'arduinoUno.pins.setDigitalOutput',
                              default: 'set digital pin [PIN] out [LEVEL]',
                              description: 'arduinoUno set digital pin out'
                          }),
                          blockType: BlockType.COMMAND,
                          arguments: {
                              PIN: {
                                  type: ArgumentType.STRING,
                                  menu: 'pins',
                                  defaultValue: Pins.D0
                              },
                              LEVEL: {
                                  type: ArgumentType.STRING,
                                  menu: 'level',
                                  defaultValue: Level.High
                              }
                          }
                      },
                      {
  
                          opcode: 'setPwmOutput',
                          text: formatMessage({
                              id: 'arduinoUno.pins.setPwmOutput',
                              default: 'set pwm pin [PIN] out [OUT]',
                              description: 'arduinoUno set pwm pin out'
                          }),
                          blockType: BlockType.COMMAND,
                          arguments: {
                              PIN: {
                                  type: ArgumentType.STRING,
                                  menu: 'pwmPins',
                                  defaultValue: Pins.D3
                              },
                              OUT: {
                                  type: ArgumentType.UINT8_NUMBER,
                                  defaultValue: '255'
                              }
                          }
                      },
                      '---',
                      {
                          opcode: 'readDigitalPin',
                          text: formatMessage({
                              id: 'arduinoUno.pins.readDigitalPin',
                              default: 'read digital pin [PIN]',
                              description: 'arduinoUno read digital pin'
                          }),
                          blockType: BlockType.BOOLEAN,
                          arguments: {
                              PIN: {
                                  type: ArgumentType.STRING,
                                  menu: 'digitalPins',
                                  defaultValue: Pins.D0
                              }
                          }
                      },
                      {
                          opcode: 'readAnalogPin',
                          text: formatMessage({
                              id: 'arduinoUno.pins.readAnalogPin',
                              default: 'read analog pin [PIN]',
                              description: 'arduinoUno read analog pin'
                          }),
                          blockType: BlockType.REPORTER,
                          arguments: {
                              PIN: {
                                  type: ArgumentType.STRING,
                                  menu: 'analogPins',
                                  defaultValue: Pins.A0
                              }
                          }
                      },
                      '---',
                      {
  
                          opcode: 'setServoOutput',
                          text: formatMessage({
                              id: 'arduinoUno.pins.setServoOutput',
                              default: 'set servo pin [PIN] out [OUT]',
                              description: 'arduinoUno set servo pin out'
                          }),
                          blockType: BlockType.COMMAND,
                          arguments: {
                              PIN: {
                                  type: ArgumentType.STRING,
                                  menu: 'pwmPins',
                                  defaultValue: Pins.D3
                              },
                              OUT: {
                                  type: ArgumentType.ANGLE,
                                  defaultValue: '90'
                              }
                          }
                      },
                      '---',
                      {
  
                          opcode: 'attachInterrupt',
                          text: formatMessage({
                              id: 'arduinoUno.pins.attachInterrupt',
                              default: 'attach interrupt pin [PIN] mode [MODE] executes',
                              description: 'arduinoUno attach interrupt'
                          }),
                          blockType: BlockType.CONDITIONAL,
                          arguments: {
                              PIN: {
                                  type: ArgumentType.STRING,
                                  menu: 'interruptPins',
                                  defaultValue: Pins.D3
                              },
                              MODE: {
                                  type: ArgumentType.STRING,
                                  menu: 'interruptMode',
                                  defaultValue: InterrupMode.Rising
                              }
                          },
                          programMode: [ProgramModeType.UPLOAD]
                      },
                      {
  
                          opcode: 'detachInterrupt',
                          text: formatMessage({
                              id: 'arduinoUno.pins.detachInterrupt',
                              default: 'detach interrupt pin [PIN]',
                              description: 'arduinoUno detach interrupt'
                          }),
                          blockType: BlockType.COMMAND,
                          arguments: {
                              PIN: {
                                  type: ArgumentType.STRING,
                                  menu: 'interruptPins',
                                  defaultValue: Pins.D3
                              }
                          },
                          programMode: [ProgramModeType.UPLOAD]
                      }
                  ],
                  menus: {
                      pins: {
                          items: this.PINS_MENU
                      },
                      mode: {
                          items: this.MODE_MENU
                      },
                      digitalPins: {
                          items: this.DIGITAL_PINS_MENU
                      },
                      analogPins: {
                          items: this.ANALOG_PINS_MENU
                      },
                      level: {
                          acceptReporters: true,
                          items: this.LEVEL_MENU
                      },
                      pwmPins: {
                          items: this.PWM_PINS_MENU
                      },
                      interruptPins: {
                          items: this.INTERRUPT_PINS_MENU
                      },
                      interruptMode: {
                          items: this.INTERRUP_MODE_MENU
                      }
                  }
              },
              {
                  id: 'serial',
                  name: formatMessage({
                      id: 'arduinoUno.category.serial',
                      default: 'Serial',
                      description: 'The name of the arduino uno device serial category'
                  }),
                  color1: '#9966FF',
                  color2: '#774DCB',
                  color3: '#774DCB',
  
                  blocks: [
                      {
                          opcode: 'serialBegin',
                          text: formatMessage({
                              id: 'arduinoUno.serial.serialBegin',
                              default: 'serial begin baudrate [VALUE]',
                              description: 'arduinoUno serial begin'
                          }),
                          blockType: BlockType.COMMAND,
                          arguments: {
                              VALUE: {
                                  type: ArgumentType.STRING,
                                  menu: 'baudrate',
                                  defaultValue: Buadrate.B9600
                              }
                          },
                          programMode: [ProgramModeType.UPLOAD]
                      },
                      {
                          opcode: 'serialPrint',
                          text: formatMessage({
                              id: 'arduinoUno.serial.serialPrint',
                              default: 'serial print [VALUE] [EOL]',
                              description: 'arduinoUno serial print'
                          }),
                          blockType: BlockType.COMMAND,
                          arguments: {
                              VALUE: {
                                  type: ArgumentType.STRING,
                                  defaultValue: 'Hello OpenBlock'
                              },
                              EOL: {
                                  type: ArgumentType.STRING,
                                  menu: 'eol',
                                  defaultValue: Eol.Warp
                              }
                          },
                          programMode: [ProgramModeType.UPLOAD]
                      },
                      {
                          opcode: 'serialAvailable',
                          text: formatMessage({
                              id: 'arduinoUno.serial.serialAvailable',
                              default: 'serial available data length',
                              description: 'arduinoUno serial available data length'
                          }),
                          blockType: BlockType.REPORTER,
                          disableMonitor: true,
                          programMode: [ProgramModeType.UPLOAD]
                      },
                      {
                          opcode: 'serialReadData',
                          text: formatMessage({
                              id: 'arduinoUno.serial.serialReadData',
                              default: 'serial read data',
                              description: 'arduinoUno serial read data'
                          }),
                          blockType: BlockType.REPORTER,
                          disableMonitor: true,
                          programMode: [ProgramModeType.UPLOAD]
                      }
                  ],
                  menus: {
                      baudrate: {
                          items: this.BAUDTATE_MENU
                      },
                      eol: {
                          items: this.EOL_MENU
                      }
                  }
              },
              {
                  id: 'data',
                  name: formatMessage({
                      id: 'arduinoUno.category.data',
                      default: 'Data',
                      description: 'The name of the arduino uno device data category'
                  }),
                  color1: '#CF63CF',
                  color2: '#C94FC9',
                  color3: '#BD42BD',
                  blocks: [
                      {
                          opcode: 'dataMap',
                          text: formatMessage({
                              id: 'arduinoUno.data.dataMap',
                              default: 'map [DATA] from ([ARG0], [ARG1]) to ([ARG2], [ARG3])',
                              description: 'arduinoUno data map'
                          }),
                          blockType: BlockType.REPORTER,
                          arguments: {
                              DATA: {
                                  type: ArgumentType.NUMBER,
                                  defaultValue: '50'
                              },
                              ARG0: {
                                  type: ArgumentType.NUMBER,
                                  defaultValue: '1'
                              },
                              ARG1: {
                                  type: ArgumentType.NUMBER,
                                  defaultValue: '100'
                              },
                              ARG2: {
                                  type: ArgumentType.NUMBER,
                                  defaultValue: '1'
                              },
                              ARG3: {
                                  type: ArgumentType.NUMBER,
                                  defaultValue: '1000'
                              }
                          },
                          programMode: [ProgramModeType.UPLOAD]
                      },
                      '---',
                      {
                          opcode: 'dataConstrain',
                          text: formatMessage({
                              id: 'arduinoUno.data.dataConstrain',
                              default: 'constrain [DATA] between ([ARG0], [ARG1])',
                              description: 'arduinoUno data constrain'
                          }),
                          blockType: BlockType.REPORTER,
                          arguments: {
                              DATA: {
                                  type: ArgumentType.NUMBER,
                                  defaultValue: '50'
                              },
                              ARG0: {
                                  type: ArgumentType.NUMBER,
                                  defaultValue: '1'
                              },
                              ARG1: {
                                  type: ArgumentType.NUMBER,
                                  defaultValue: '100'
                              }
                          },
                          programMode: [ProgramModeType.UPLOAD]
                      },
                      {
                          opcode: 'dataConvert',
                          text: formatMessage({
                              id: 'arduinoUno.data.dataConvert',
                              default: 'convert [DATA] to [TYPE]',
                              description: 'arduinoUno data convert'
                          }),
                          blockType: BlockType.REPORTER,
                          arguments: {
                              DATA: {
                                  type: ArgumentType.STRING,
                                  defaultValue: '123'
                              },
                              TYPE: {
                                  type: ArgumentType.STRING,
                                  menu: 'dataType',
                                  defaultValue: DataType.WholeNumber
                              }
                          },
                          programMode: [ProgramModeType.UPLOAD]
                      },
                      {
                          opcode: 'dataConvertASCIICharacter',
                          text: formatMessage({
                              id: 'arduinoUno.data.dataConvertASCIICharacter',
                              default: 'convert [DATA] to ASCII character',
                              description: 'arduinoUno data convert to ASCII character'
                          }),
                          blockType: BlockType.REPORTER,
                          arguments: {
                              DATA: {
                                  type: ArgumentType.NUMBER,
                                  defaultValue: '97'
                              }
                          },
                          programMode: [ProgramModeType.UPLOAD]
                      },
                      {
                          opcode: 'dataConvertASCIINumber',
                          text: formatMessage({
                              id: 'arduinoUno.data.dataConvertASCIINumber',
                              default: 'convert [DATA] to ASCII nubmer',
                              description: 'arduinoUno data convert to ASCII nubmer'
                          }),
                          blockType: BlockType.REPORTER,
                          arguments: {
                              DATA: {
                                  type: ArgumentType.STRING,
                                  defaultValue: 'a'
                              }
                          },
                          programMode: [ProgramModeType.UPLOAD]
                      }
                  ],
                  menus: {
                      dataType: {
                          items: this.DATA_TYPE_MENU
                      }
                  }
              }
          ];
      }
  
      /**
       * Set pin mode.
       * @param {object} args - the block's arguments.
       * @return {Promise} - a Promise that resolves after the set pin mode is done.
       */
      setPinMode (args) {
          this._peripheral.setPinMode(args.PIN, args.MODE);
          return Promise.resolve();
      }
  
      /**
       * Set pin digital out level.
       * @param {object} args - the block's arguments.
       * @return {Promise} - a Promise that resolves after the set pin digital out level is done.
       */
      setDigitalOutput (args) {
          this._peripheral.setDigitalOutput(args.PIN, args.LEVEL);
          return Promise.resolve();
      }
  
      /**
       * Set pin pwm out value.
       * @param {object} args - the block's arguments.
       * @return {Promise} - a Promise that resolves after the set pin pwm out value is done.
       */
      setPwmOutput (args) {
          this._peripheral.setPwmOutput(args.PIN, args.OUT);
          return Promise.resolve();
      }
  
      /**
       * Read pin digital level.
       * @param {object} args - the block's arguments.
       * @return {boolean} - true if read high level, false if read low level.
       */
      readDigitalPin (args) {
          return this._peripheral.readDigitalPin(args.PIN);
      }
  
      /**
       * Read analog pin.
       * @param {object} args - the block's arguments.
       * @return {number} - analog value fo the pin.
       */
      readAnalogPin (args) {
          return this._peripheral.readAnalogPin(args.PIN);
      }
  
      /**
       * Set servo out put.
       * @param {object} args - the block's arguments.
       * @return {Promise} - a Promise that resolves after the set servo out value is done.
       */
      setServoOutput (args) {
          this._peripheral.setServoOutput(args.PIN, args.OUT);
          return Promise.resolve();
      }
  }
  
  module.exports = OpenBlockArduinoMiniDevice;
  