#include "_Plugin_Helper.h"
#ifdef USES_P062

// #######################################################################################################
// #################################### Plugin 062: MPR121 KeyPad ########################################
// #######################################################################################################

// ESPEasy Plugin to scan a 12 key touch pad chip MPR121
// written by Jochen Krapf (jk@nerd2nerd.org)

/** Changelog:
 * 2025-06-14 tonhuisman: Add support for Custom Value Type per task value
 * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for Keypad)
 *                        Update changelog
 * 2021-12-29 tonhuisman: Add setting for panel sensitivity, as requested in https://github.com/letscontrolit/ESPEasy/issues/3828
 *                        Reformat source using Uncrustify
 * 2020-10-14 tonhuisman: Added settings for global and per-sensor sensitivity
 *                        and getting 'calibration' touch pressure data (current, min, max)
 */

// ScanCode;
// Value 1...12 for the key number
// No key - the code 0
// If more than one key is pressed, the scan code is the code with the lowest value

// If ScanCode is unchecked the value is the KeyMap 1.Key=1, 2.Key=2, 3.Key=4, 4.Key=8 ... 1.Key=2048
// If more than one key is pressed, the value is sum of all KeyMap-values


# define PLUGIN_062
# define PLUGIN_ID_062         62
# define PLUGIN_NAME_062       "Keypad - MPR121 Touch"
# define PLUGIN_VALUENAME1_062 "ScanCode"


# include "src/PluginStructs/P062_data_struct.h"

# define P062_FLAGS_USE_CALIBRATION    0               // Set in P062_CONFIG_FLAGS

# define P062_CONFIG_FLAGS             PCONFIG_LONG(0) // 0-31 flags

# define P062_DEFAULT_TOUCH_TRESHOLD   12              // Defaults got from MPR_121 source
# define P062_DEFAULT_RELEASE_TRESHOLD 6

boolean Plugin_062(uint8_t function, struct EventStruct *event, String& string)
{
  boolean success = false;

  switch (function)
  {
    case PLUGIN_DEVICE_ADD:
    {
      auto& dev = Device[++deviceCount];
      dev.Number             = PLUGIN_ID_062;
      dev.Type               = DEVICE_TYPE_I2C;
      dev.VType              = Sensor_VType::SENSOR_TYPE_SWITCH;
      dev.ValueCount         = 1;
      dev.SendDataOption     = true;
      dev.TimerOption        = true;
      dev.TimerOptional      = true;
      dev.ExitTaskBeforeSave = false;
      dev.CustomVTypeVar     = true;
      break;
    }

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

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

    # if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE
    case PLUGIN_GET_DISCOVERY_VTYPES:
    {
      #  if FEATURE_CUSTOM_TASKVAR_VTYPE

      for (uint8_t i = 0; i < event->Par5; ++i) {
        event->ParN[i] = ExtraTaskSettings.getTaskVarCustomVType(i);  // Custom/User selection
      }
      #  else // if FEATURE_CUSTOM_TASKVAR_VTYPE
      event->Par1 = static_cast<int>(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported
      #  endif // if FEATURE_CUSTOM_TASKVAR_VTYPE
      success = true;
      break;
    }
    # endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE

    case PLUGIN_I2C_HAS_ADDRESS:
    case PLUGIN_WEBFORM_SHOW_I2C_PARAMS:
    {
      const uint8_t i2cAddressValues[] = { 0x5A, 0x5B, 0x5C, 0x5D };

      if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) {
        addFormSelectorI2C(F("i2c_addr"), 4, i2cAddressValues, PCONFIG(0));
      } else {
        success = intArrayContains(4, i2cAddressValues, event->Par1);
      }
      break;
    }

    # if FEATURE_I2C_GET_ADDRESS
    case PLUGIN_I2C_GET_ADDRESS:
    {
      event->Par1 = PCONFIG(0);
      success     = true;
      break;
    }
    # endif // if FEATURE_I2C_GET_ADDRESS

    case PLUGIN_WEBFORM_LOAD:
    {
      addFormCheckBox(F("ScanCode"), F("scancode"), PCONFIG(1));

      addFormSubHeader(F("Sensitivity"));
      {
        uint8_t touch_treshold = PCONFIG(2);

        if (touch_treshold == 0) {
          touch_treshold = P062_DEFAULT_TOUCH_TRESHOLD; // default value
        }
        addFormNumericBox(F("Touch treshold (1..255)"), F("ttreshold"), touch_treshold, 0, 255);
        addUnit(concat(F("Default: "), P062_DEFAULT_TOUCH_TRESHOLD));
      }

      {
        uint8_t release_treshold = PCONFIG(3);

        if (release_treshold == 0) {
          release_treshold = P062_DEFAULT_RELEASE_TRESHOLD; // default value
        }
        addFormNumericBox(F("Release treshold (1..255)"), F("rtreshold"), release_treshold, 0, 255);
        addUnit(concat(F("Default: "), P062_DEFAULT_RELEASE_TRESHOLD));
      }
      {
        const __FlashStringHelper *sensitivityOptions[] = {
          F("Normal"),
          F("Extra sensitive (behind 4-6mm glass cover)")
        };
        const int sensitivityValues[] = {
          MPR212_NORMAL_SENSITIVITY,
          MPR212_EXTRA_SENSITIVITY
        };
        constexpr size_t optionCount = NR_ELEMENTS(sensitivityValues);
        const FormSelectorOptions selector(optionCount, sensitivityOptions, sensitivityValues);
        selector.addFormSelector(F("Panel sensitivity"), F("psens"), PCONFIG(4));
      }
      {
        bool canCalibrate     = true;
        bool tbUseCalibration = bitRead(P062_CONFIG_FLAGS, P062_FLAGS_USE_CALIBRATION);

        P062_data_struct *P062_data = static_cast<P062_data_struct *>(getPluginTaskData(event->TaskIndex));

        if (nullptr == P062_data) {
          P062_data    = new (std::nothrow) P062_data_struct();
          canCalibrate = false;

          if (P062_data == nullptr) {
            return success;
          }
        }
        P062_data->loadTouchObjects(event->TaskIndex);

        addRowLabel(F("Object"));
        html_table(EMPTY_STRING, false); // Sub-table
        html_table_header(F("&nbsp;#&nbsp;"));
        html_table_header(F("Touch (0..255)"));
        html_table_header(F("Release (0..255)"));

        if (tbUseCalibration && canCalibrate) {
          html_table_header(F("Current"));
          html_table_header(F("Min"));
          html_table_header(F("Max"));
        }

        for (int objectNr = 0; objectNr < P062_MaxTouchObjects; ++objectNr) {
          html_TR_TD();
          addHtml(F("&nbsp;"));
          addHtmlInt(objectNr + 1);
          html_TD();
          addNumericBox(getPluginCustomArgName(objectNr + 100), P062_data->StoredSettings.TouchObjects[objectNr].touch, 0, 255);
          html_TD();
          addNumericBox(getPluginCustomArgName(objectNr + 200), P062_data->StoredSettings.TouchObjects[objectNr].release, 0, 255);

          if (tbUseCalibration && canCalibrate) {
            uint16_t current = 0;
            uint16_t min     = 0;
            uint16_t max     = 0;
            P062_data->getCalibrationData(objectNr, &current, &min, &max);
            html_TD();
            addHtmlInt(current);
            html_TD();
            addHtmlInt(min);
            html_TD();
            addHtmlInt(max);
          }
        }
        html_end_table();

        if (canCalibrate) {
          const int choice1 = tbUseCalibration ? 1 : 0;
          addFormSelector_YesNo(F("Enable Calibration"), F("ucal"), choice1, true);

          if (tbUseCalibration) {
            addFormCheckBox(F("Clear calibrationdata"), F("clr_cal"), false);
          }
        }
        delete P062_data;
      }
      success = true;
      break;
    }

    case PLUGIN_WEBFORM_SAVE:
    {
      PCONFIG(0) = getFormItemInt(F("i2c_addr"));

      PCONFIG(1) = isFormItemChecked(F("scancode"));

      PCONFIG(2) = getFormItemInt(F("ttreshold"));
      PCONFIG(3) = getFormItemInt(F("rtreshold"));
      PCONFIG(4) = getFormItemInt(F("psens"));

      uint32_t lSettings = 0;
      bitWrite(lSettings, P062_FLAGS_USE_CALIBRATION, getFormItemInt(F("ucal")) == 1);
      P062_CONFIG_FLAGS = lSettings;

      {
        bool canCalibrate           = true;
        P062_data_struct *P062_data = static_cast<P062_data_struct *>(getPluginTaskData(event->TaskIndex));

        if (nullptr == P062_data) {
          P062_data    = new (std::nothrow) P062_data_struct();
          canCalibrate = false;

          if (P062_data == nullptr) {
            return success; // Save other settings even though this didn't initialize properly
          }
        }
        P062_data->loadTouchObjects(event->TaskIndex);

        for (int objectNr = 0; objectNr < P062_MaxTouchObjects; ++objectNr) {
          P062_data->StoredSettings.TouchObjects[objectNr].touch   = getFormItemIntCustomArgName(objectNr + 100);
          P062_data->StoredSettings.TouchObjects[objectNr].release = getFormItemIntCustomArgName(objectNr + 200);
        }
        # ifdef PLUGIN_062_DEBUG

        if (loglevelActiveFor(LOG_LEVEL_INFO)) {
          addLogMove(LOG_LEVEL_INFO, strformat(F("P062_data save size: %u"), sizeof(P062_data->StoredSettings)));
        }
        # endif // PLUGIN_062_DEBUG
        SaveCustomTaskSettings(event->TaskIndex, reinterpret_cast<const uint8_t *>(&(P062_data->StoredSettings)),
                               sizeof(P062_data->StoredSettings));

        if (!canCalibrate) {
          delete P062_data;
          P062_data = nullptr;
        } else {
          const bool clearCalibration = isFormItemChecked(F("clr_cal"));

          if (clearCalibration) {
            P062_data->clearCalibrationData();
            # ifdef PLUGIN_062_DEBUG
            addLog(LOG_LEVEL_INFO, F("p062 clear calibration"));
            # endif // PLUGIN_062_DEBUG
          }
        }
      }
      success = true;
      break;
    }

    case PLUGIN_INIT:
    {
      bool tbUseCalibration = bitRead(P062_CONFIG_FLAGS, P062_FLAGS_USE_CALIBRATION);

      initPluginTaskData(event->TaskIndex, new (std::nothrow) P062_data_struct());
      P062_data_struct *P062_data = static_cast<P062_data_struct *>(getPluginTaskData(event->TaskIndex));

      if (nullptr != P062_data) {
        if (!P062_data->init(event->TaskIndex, PCONFIG(0), PCONFIG(1), tbUseCalibration, PCONFIG(4))) {
          clearPluginTaskData(event->TaskIndex);
          P062_data = nullptr;
        } else {
          success = true;

          uint8_t touch_treshold = PCONFIG(2);

          if (touch_treshold == 0) {
            touch_treshold = P062_DEFAULT_TOUCH_TRESHOLD; // default value
          }
          uint8_t release_treshold = PCONFIG(3);

          if (release_treshold == 0) {
            release_treshold = P062_DEFAULT_RELEASE_TRESHOLD; // default value
          }

          if ((touch_treshold != P062_DEFAULT_TOUCH_TRESHOLD) && (release_treshold != P062_DEFAULT_RELEASE_TRESHOLD)) {
            P062_data->setThresholds(touch_treshold, release_treshold); // Set custom tresholds, ignore default values
          }

          for (uint8_t objectNr = 0; objectNr < P062_MaxTouchObjects; ++objectNr) {
            if ((P062_data->StoredSettings.TouchObjects[objectNr].touch != 0) &&
                (P062_data->StoredSettings.TouchObjects[objectNr].release != 0)) {
              P062_data->setThreshold(objectNr,
                                      P062_data->StoredSettings.TouchObjects[objectNr].touch,
                                      P062_data->StoredSettings.TouchObjects[objectNr].release);
            }
          }
        }
      }

      break;
    }

    case PLUGIN_TEN_PER_SECOND:
    {
      P062_data_struct *P062_data =
        static_cast<P062_data_struct *>(getPluginTaskData(event->TaskIndex));

      if (nullptr == P062_data) {
        return success;
      } else {
        uint16_t key;

        if (P062_data->readKey(key))
        {
          UserVar.setFloat(event->TaskIndex, 0, key);
          event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH;

          if (loglevelActiveFor(LOG_LEVEL_INFO)) {
            String log = F("Tkey : ");
            log.reserve(22);

            if (PCONFIG(1)) {
              log += F("ScanCode=");
            }
            else {
              log += F("KeyMap=");
            }
            log += formatToHex(key);
            addLogMove(LOG_LEVEL_INFO, log);

            bool tbUseCalibration = bitRead(P062_CONFIG_FLAGS, P062_FLAGS_USE_CALIBRATION);

            if (tbUseCalibration) {
              uint16_t colMask = 0x01;

              for (uint8_t col = 0; col < P062_MaxTouchObjects; ++col)
              {
                if (key & colMask) // this key pressed?
                {
                  uint16_t current = 0;
                  uint16_t min     = 0;
                  uint16_t max     = 0;
                  P062_data->getCalibrationData(col, &current, &min, &max);
                  addLog(LOG_LEVEL_INFO,
                         strformat(F("P062 touch #%d current: %d min: %d max: %d"),
                                   col, current, min, max));

                  if (!PCONFIG(1)) {
                    break;
                  }
                }
                colMask <<= 1;
              }
            }
          }

          sendData(event);
        }
      }
      success = true;
      break;
    }

    case PLUGIN_READ:
    {
      success = true;
      break;
    }
  }
  return success;
}

#endif // USES_P062
