#include "_Plugin_Helper.h"
#ifdef USES_P079

# include "src/PluginStructs/P079_data_struct.h"

// #######################################################################################################
// ################################## Plugin 079: Wemos / Lolin Motorshield ##############################
// #######################################################################################################

// Wemos/Lolin Motorshield:
// I2C control for two Brushed DC motors. Supports Wemos V1 and Lolin V2 Motor Shields.
// Plugin written by Susanne Jaeckel + TungstenE2, Merged Aug-2018 ESPEasy Mega.
// Plugin Updated by ThomasB (ThomasTech), Sep-18-2020: Added support for Lolin V2.0 Motor Shield.
//   Change Log, Sep-22-2020:
//      Added:  Support for Lolin V2.0 Motor Shield. Plugin's Web GUI uses form selector to choose board type.
//      Added:  Command syntax checking. Basic Error msg is reported in browser. Expanded msg in info log.
//      Added:  LolinMotorShieldCMD command keyword is alias to WemosMotorShieldCMD.
//      Added:  Standby command (put board into low power idle).
//      Added:  Brake command (short brake).
//      Added:  I2C Communication test for Lolin V2; Use command "LolinMotorShieldCMD" without parameters.
//      Added:  Use default device name if user forgets to populate the name field on device creation.
//      Added:  New device now defaults I2C address field to "0x30" rather than "0x".
//      Fixed:  Eliminated "Invalid character in names" error message that appears in new plugin.
//      Change: Renamed plugin's title to "Motor - Wemos/Lolin Motorshield" (was "Motor - Wemos Motorshield").
//
// This ESPEasy plugin has incorporated source code from the official motorshield libraries:
//   -> https://github.com/wemos/WEMOS_Motor_Shield_Arduino_Library
//   -> https://github.com/wemos/LOLIN_I2C_MOTOR_Library
//
// Wemos Motor Shield V1.0 requires updated firmware to prevent i2c bus lockups.
// See: https://hackaday.io/project/18439-motor-shield-reprogramming
//
// Note: see wiki for Wemos V1 setup. motor_shield.bin needs to be flashed first!!!
// see wiki: https://www.letscontrolit.com/wiki/index.php?title=WemosMotorshield
//
// Board Docs:
//   Wemos V1.0 Motor Shield: https://diyprojects.io/test-shield-motor-i2c-wemos-d1-mini-pro-drive-2-motors-15vdc
//   Lolin V2.0 Motor Shield: https://docs.wemos.cc/en/latest/d1_mini_shiled/motor.html
//   TB6612FNG DC Motor Driver: https://toshiba.semicon-storage.com/info/docget.jsp?did=10660&prodName=TB6612FNG
//
// Command Syntax (WemosMotorShieldCMD and LolinMotorShieldCMD keywords are interchangeable):
//   WemosMotorShieldCMD,<Motornumber [0,1]>,<Forward/Backward/Stop/Standby/Brake>,<Speed [0-100]>
//   LolinMotorShieldCMD,<Motornumber [0,1]>,<Forward/Backward/Stop/Standby/Brake>,<Speed [0-100]>
// Command Examples:
//   WemosMotorShieldCMD,0,Forward,100
//   WemosMotorShieldCMD,1,Stop
//   LolinMotorShieldCMD,0,Reverse,25
//   LolinMotorShieldCMD,1,Standby
//   LolinMotorShieldCMD,1,Brake
// Command Example for i2c Communication test (Lolin V2 board only)
//   LolinMotorShieldCMD
//
// ************************************************************************************************

# define PLUGIN_079
# define PLUGIN_ID_079         79
# define PLUGIN_NAME_079       "Motor - Wemos/Lolin Motorshield"
# define PLUGIN_VALUENAME1_079 "Motorshield"
# define PLUGIN_DEF_NAME1_079  "Wemos_DC_Motor"
# define PLUGIN_DEF_NAME2_079  "Lolin_DC_Motor"

# define I2C_ADDR_PCFG_P079   PCONFIG(0)
# define SHIELD_VER_PCFG_P079 PCONFIG(1)
# define Plugin_079_MotorShield_type  static_cast<P079_BoardType>(SHIELD_VER_PCFG_P079)


// Command keywords: The two Command Names are interchangeable.
# define CMD_NAME_LOLIN "LolinMotorShieldCMD"
# define CMD_NAME_WEMOS "WemosMotorShieldCMD"


// Compiler Options
// #define VERBOSE_P079                 // Uncomment to enable Verbose info log status messages.

// ************************************************************************************************

boolean Plugin_079(uint8_t function, struct EventStruct *event, String& string)
{
  boolean success        = false;
  MOTOR_STATES motor_dir = MOTOR_STATES::MOTOR_FWD;
  uint8_t motor_number   = P079_MOTOR_A;
  int16_t motor_speed    = 100;


  switch (function) {
    case PLUGIN_DEVICE_ADD: {
      auto& dev = Device[++deviceCount];
      dev.Number   = PLUGIN_ID_079;
      dev.Type     = DEVICE_TYPE_I2C;
      dev.VType    = Sensor_VType::SENSOR_TYPE_NONE;
      break;
    }

    case PLUGIN_GET_DEVICENAME: {
      string = F(PLUGIN_NAME_079);
      break;
    }

    case PLUGIN_GET_DEVICEVALUENAMES: {
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_079));
      break;
    }

    case PLUGIN_SET_DEFAULTS:
    {
      I2C_ADDR_PCFG_P079   = DEF_I2C_ADDRESS_079;
      SHIELD_VER_PCFG_P079 = static_cast<int>(P079_BoardType::WemosMotorshield);
      break;
    }

    case PLUGIN_WEBFORM_SHOW_I2C_PARAMS:
    {
      if ((I2C_ADDR_PCFG_P079 < 0x01) || (I2C_ADDR_PCFG_P079 > 0x7f)) { // Validate I2C Addr.
        I2C_ADDR_PCFG_P079 = DEF_I2C_ADDRESS_079;
      }
      addFormTextBox(F("I2C Address (Hex)"), F("i2c_addr"), formatToHex(I2C_ADDR_PCFG_P079), 4);

      {
        const __FlashStringHelper *options[] = {
          F("WEMOS V1.0"),
          F("LOLIN V2.0")
        };
        const int indices[] = {
          static_cast<int>(P079_BoardType::WemosMotorshield),
          static_cast<int>(P079_BoardType::LolinMotorshield)
        };
        constexpr size_t optionCount = NR_ELEMENTS(indices);
        const FormSelectorOptions selector(optionCount, options, indices);
        selector.addFormSelector(F("Motor Shield Type"), F("shield_type"), SHIELD_VER_PCFG_P079);
      }

      if (Plugin_079_MotorShield_type == P079_BoardType::WemosMotorshield) {
        addFormNote(F("WEMOS V1.0 Motor Shield requires updated firmware, "
                      "see <a href='https://www.letscontrolit.com/wiki/index.php?title=WemosMotorshield'>wiki</a>"));
      }

      break;
    }

    # if FEATURE_I2C_GET_ADDRESS
    case PLUGIN_I2C_GET_ADDRESS:
    {
      event->Par1 = I2C_ADDR_PCFG_P079;
      success     = true;
      break;
    }
    # endif // if FEATURE_I2C_GET_ADDRESS

    case PLUGIN_WEBFORM_LOAD: {
      success = true;
      break;
    }

    case PLUGIN_WEBFORM_SAVE: {
      String i2c_address = webArg(F("i2c_addr"));
      I2C_ADDR_PCFG_P079   = (int)strtol(i2c_address.c_str(), 0, 16);
      SHIELD_VER_PCFG_P079 = getFormItemInt(F("shield_type"));

      // Check to see if user entered device name.
      if (getTaskDeviceName(event->TaskIndex).isEmpty()) {
        switch (Plugin_079_MotorShield_type) {
          case P079_BoardType::WemosMotorshield:
            // Name missing, populate default name.
            safe_strncpy(ExtraTaskSettings.TaskDeviceName, F(PLUGIN_DEF_NAME1_079), sizeof(ExtraTaskSettings.TaskDeviceName));
            break;
          case P079_BoardType::LolinMotorshield:
            // Name missing, populate default name.
            safe_strncpy(ExtraTaskSettings.TaskDeviceName, F(PLUGIN_DEF_NAME2_079), sizeof(ExtraTaskSettings.TaskDeviceName));
            break;
        }
      }

      success = true;
      break;
    }

    case PLUGIN_INIT: {
      // FIXME: This validation seems to be redundant...
      // Validate Shield Type.
      bool valid = false;

      switch (Plugin_079_MotorShield_type) {
        case P079_BoardType::WemosMotorshield:
        case P079_BoardType::LolinMotorshield:
          valid = true;
          break;

          // TD-er: Do not use default here, as the compiler can now warn if we're missing a newly added option
      }

      if (!valid) {
        SHIELD_VER_PCFG_P079 = static_cast<int>(P079_BoardType::WemosMotorshield);
      }

      success = true;
      break;
    }

    case PLUGIN_READ: {
      success = false;
      break;
    }

    case PLUGIN_WRITE: {
      # if FEATURE_I2C_DEVICE_CHECK

      if (!I2C_deviceCheck(I2C_ADDR_PCFG_P079, event->TaskIndex, 10, PLUGIN_I2C_GET_ADDRESS)) {
        break; // Will return the default false for success
      }
      # endif // if FEATURE_I2C_DEVICE_CHECK
      uint8_t parse_error = false;
      String  ModeStr;

      String cmd = parseString(string, 1);

      if (cmd.equalsIgnoreCase(F(CMD_NAME_WEMOS)) || cmd.equalsIgnoreCase(F(CMD_NAME_LOLIN))) {
        switch (Plugin_079_MotorShield_type) {
          case P079_BoardType::WemosMotorshield:
            ModeStr = F("WemosV1");
            break;
          case P079_BoardType::LolinMotorshield:
            ModeStr = F("LolinV2");
            break;
        }
        ModeStr += F(" MotorShield");

        String paramMotor     = parseString(string, 2); // Motor 0 or 1
        String paramDirection = parseString(string, 3); // Direction, Forward/Backward/Stop
        String paramSpeed     = parseString(string, 4); // Speed, 0-100

        if ((paramMotor.isEmpty()) && (paramDirection.isEmpty()) && (paramSpeed.isEmpty())) {
          switch (Plugin_079_MotorShield_type) {
            case P079_BoardType::WemosMotorshield:
              # ifdef VERBOSE_P079
              ModeStr += F(": Unknown CMD");
              # else // ifdef VERBOSE_P079
              ModeStr += F(": ?");
              # endif // ifdef VERBOSE_P079
              break;
            case P079_BoardType::LolinMotorshield:
            {
              LOLIN_I2C_MOTOR lolin(I2C_ADDR_PCFG_P079);
              lolin.getInfo();

              if (lolin.PRODUCT_ID != PRODUCT_ID_I2C_LOLIN) {
                ModeStr += F(": Fail");
              }
              else {
                ModeStr += F(": Pass, ID=");
                ModeStr += lolin.PRODUCT_ID;
                ModeStr += F(", Ver=");
                ModeStr += lolin.VERSION_ID;
              }
              break;
            }
          }
          addLog(LOG_LEVEL_INFO, ModeStr);
          SendStatus(event, ModeStr + F(" <br>")); // Reply (echo) to sender. This will print message on browser.
          return true;                             // Exit now. Info Log shows Lolin Info.
        }
        else {
          if ((equals(paramMotor, '0')) || (equals(paramMotor, '1'))) {
            motor_number = paramMotor.toInt();
          }
          else {
            if (paramMotor.isEmpty()) {
              paramMotor = '?';
            }
            motor_number = 0;
            parse_error  = true;
          }

          if (equals(paramDirection, F("stop"))) {
            motor_dir = MOTOR_STATES::MOTOR_STOP;
          }
          else if (equals(paramDirection, F("forward"))) {
            motor_dir = MOTOR_STATES::MOTOR_FWD;
          }
          else if (equals(paramDirection, F("backward"))) {
            motor_dir = MOTOR_STATES::MOTOR_REV;
          }
          else if (equals(paramDirection, F("standby"))) {
            motor_dir = MOTOR_STATES::MOTOR_STBY;
          }
          else if (equals(paramDirection, F("brake"))) {
            motor_dir = MOTOR_STATES::MOTOR_BRAKE;
          }
          else {
            paramDirection = '?';
            motor_dir      = MOTOR_STATES::MOTOR_STOP;
            parse_error    = true;
          }

          if (paramSpeed.isEmpty()) {
            switch (motor_dir) {
              case MOTOR_STATES::MOTOR_STOP:
              case MOTOR_STATES::MOTOR_STBY:
              case MOTOR_STATES::MOTOR_BRAKE:
                paramSpeed  = '0';
                motor_speed = 0;
                break;
              default:
                parse_error = true;
                paramSpeed  = '?';
                break;
            }
          }
          else {
            motor_speed = paramSpeed.toInt();

            if ((motor_speed < 0) || (motor_speed > 100)) {
              motor_speed = 100;
              # ifdef VERBOSE_P079
              addLog(LOG_LEVEL_INFO, strformat(F("%s: Warning, invalid speed: Now using 100"), ModeStr));
              # endif // ifdef VERBOSE_P079
            }
          }
        }

        if (parse_error == true) {
          String ErrorStr = ModeStr;
          ErrorStr += F(": CMD Syntax Error");
          SendStatus(event, ErrorStr + F(" <br>")); // Reply (echo) to sender. This will print message on browser.
          addLogMove(LOG_LEVEL_INFO, ErrorStr);
        }
        else {
          switch (motor_dir) {
            case MOTOR_STATES::MOTOR_STOP:
            case MOTOR_STATES::MOTOR_STBY:
            case MOTOR_STATES::MOTOR_BRAKE:
              paramSpeed = '0';
              break;
            default:
              break;
          }

          switch (Plugin_079_MotorShield_type) {
            case P079_BoardType::WemosMotorshield: {
              WemosMotor Wemos(I2C_ADDR_PCFG_P079, motor_number, MOTOR_FREQ_P079);
              Wemos.init();

              switch (motor_dir) {
                case MOTOR_STATES::MOTOR_FWD:
                  Wemos.setmotor(P079_CW,  motor_speed);
                  break;
                case MOTOR_STATES::MOTOR_REV:
                  Wemos.setmotor(P079_CCW, motor_speed);
                  break;
                case MOTOR_STATES::MOTOR_STOP:
                  Wemos.setmotor(P079_STOP);
                  break;
                case MOTOR_STATES::MOTOR_STBY:
                  Wemos.setmotor(P079_STANDBY);
                  break;
                case MOTOR_STATES::MOTOR_BRAKE:
                  Wemos.setmotor(P079_SHORT_BRAKE);
                  break;
              }

              break;
            }
            case P079_BoardType::LolinMotorshield: {
              LOLIN_I2C_MOTOR lolin(I2C_ADDR_PCFG_P079);
              lolin.changeFreq(MOTOR_CH_BOTH, MOTOR_FREQ_P079);

              switch (motor_dir) {
                case MOTOR_STATES::MOTOR_FWD:
                  lolin.changeStatus(motor_number, MOTOR_STATUS_CW);
                  lolin.changeDuty(motor_number, motor_speed);
                  break;
                case MOTOR_STATES::MOTOR_REV:
                  lolin.changeStatus(motor_number, MOTOR_STATUS_CCW);
                  lolin.changeDuty(motor_number, motor_speed);
                  break;
                case MOTOR_STATES::MOTOR_STOP:
                  lolin.changeStatus(motor_number, MOTOR_STATUS_STOP);
                  break;
                case MOTOR_STATES::MOTOR_STBY:
                  lolin.changeStatus(motor_number, MOTOR_STATUS_STANDBY);
                  break;
                case MOTOR_STATES::MOTOR_BRAKE:
                  lolin.changeStatus(motor_number, MOTOR_STATUS_SHORT_BRAKE);
                  break;
              }

              break;
            }
          }
        }

        if (loglevelActiveFor(LOG_LEVEL_INFO)) {
          addLog(LOG_LEVEL_INFO,
                 strformat(F("%s: Addr=0x%02x: Mtr=%s, Dir=%s, Spd=%s"),
                           ModeStr.c_str(), I2C_ADDR_PCFG_P079, paramMotor.c_str(),
                           paramDirection.c_str(), paramSpeed.c_str()));
        }

        success = true;
      }
      break;
    }
  }
  return success;
}

#endif // USES_P079
