#include "_Plugin_Helper.h"
#ifdef USES_P146

# include "src/PluginStructs/P146_data_struct.h"

// #######################################################################################################
// #################### Plugin 146 Cache Controller Reader ###############################################
// #######################################################################################################
//
// Allow to read back recorded data from the Cache Controller
// Can be used to trigger to re-send data to controllers when those are again available to receive data
//
// Typical use cases:
// - Dumping data to a MQTT broker
// - Upload binary data to a web server (not yet implemented)
//
// Receive "instructions" from MQTT controller or web command to initiate a dump.
//
// Ideas: (thus not yet implemented)
// - Filter data based on taskindex/type
// - Filter data to only upload data related to sufficient change in value
// - Allow to upload the original timestamp along with the sample

/** Changelog:
 * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for Cache reader)
 */

# define PLUGIN_146
# define PLUGIN_ID_146         146
# define PLUGIN_NAME_146       "Generic - Cache Reader"
# define PLUGIN_VALUENAME1_146 "FileNr"
# define PLUGIN_VALUENAME2_146 "FilePos"


# include "src/ControllerQueue/C016_queue_element.h"
# include "src/Globals/C016_ControllerCache.h"
# include "src/Globals/CPlugins.h"

boolean Plugin_146(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_146;
      dev.Type              = DEVICE_TYPE_DUMMY;
      dev.VType             = Sensor_VType::SENSOR_TYPE_DUAL;
      dev.ValueCount        = 2;
      dev.SendDataOption    = true;
      dev.OutputDataType    = Output_Data_type_t::Default;
      dev.HideDerivedValues = true;
      break;
    }

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

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

    # if FEATURE_MQTT_DISCOVER
    case PLUGIN_GET_DISCOVERY_VTYPES:
    {
      event->Par1 = static_cast<int>(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported
      success     = true;
      break;
    }
    # endif // if FEATURE_MQTT_DISCOVER

    case PLUGIN_SET_DEFAULTS:
    {
      P146_SET_ERASE_BINFILES(0);
      P146_SET_SEND_BINARY(0);
      P146_SET_SEND_BULK(1);
      P146_SET_SEND_TIMESTAMP(1);
      P146_SET_SEND_READ_POS(1);
      P146_SET_JOIN_TIMESTAMP(1);
      P146_SET_ONLY_SET_TASKS(1);
      P146_SEPARATOR_CHARACTER = ',';

      P146_MINIMAL_SEND_INTERVAL = 100;
      P146_MQTT_MESSAGE_LENGTH   = 800;

      String strings[P146_Nlines];
      strings[P146_TaskInfoTopicIndex] = F("%sysname%_%unit%/%tskname%/upload_meta");
      strings[P146_PublishTopicIndex]  = F("%sysname%_%unit%/%tskname%/upload");

      SaveCustomTaskSettings(event->TaskIndex, strings, P146_Nlines, 0);


      success = true;
      break;
    }

    case PLUGIN_INIT:
    {
      // Init the controller cache handler, just in case the cache controller may not be enabled
      ControllerCache.init();

      // Restore the last position from RTC when rebooting.
      ControllerCache.setPeekFilePos(
        P146_TASKVALUE_FILENR,
        P146_TASKVALUE_FILEPOS);

      int peekFileNr{};
      int peekReadPos = ControllerCache.getPeekFilePos(peekFileNr);

      if (peekReadPos >= 0) {
        P146_SET_TASKVALUE_FILENR(peekFileNr);
        P146_SET_TASKVALUE_FILEPOS(peekReadPos);
      }


      success = initPluginTaskData(
        event->TaskIndex,
        new (std::nothrow) P146_data_struct(event));
      break;
    }

    case PLUGIN_EXIT:
    {
      P146_data_struct::flush();
      break;
    }

    case PLUGIN_READ:
    {
      if (ControllerCache.peekDataAvailable()) {
        Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + P146_MINIMAL_SEND_INTERVAL);

        if (P146_GET_SEND_BULK) {
          if (P146_GET_SEND_BINARY) {
            P146_data_struct::prepare_BulkMQTT_message(event->TaskIndex);
          } else {
            P146_data_struct *P146_data = static_cast<P146_data_struct *>(getPluginTaskData(event->TaskIndex));

            if (nullptr != P146_data) {
              const char separator = static_cast<char>(P146_SEPARATOR_CHARACTER);
              P146_data->prepareCSVInBulk(event->TaskIndex, P146_GET_JOIN_TIMESTAMP, P146_GET_ONLY_SET_TASKS, separator);
            }
          }
        } else if (P146_GET_SEND_VIA_ORIG_TASK || P146_GET_SEND_VIA_EVENT) {
          // Do not set the "success" or else the task values of this Cache reader task will be sent to the same controller too.

          bool processed = false;

          if (P146_GET_SEND_VIA_ORIG_TASK && P146_data_struct::sendViaOriginalTask(event->TaskIndex, P146_GET_SEND_TIMESTAMP)) {
            processed = true;
          }

          if (P146_GET_SEND_VIA_EVENT && P146_data_struct::sendViaEvent_AllCache(event->TaskIndex, P146_GET_SEND_TIMESTAMP)) {
            processed = true;
          }

          if (processed) {
            int readFileNr    = 0;
            const int readPos = ControllerCache.getPeekFilePos(readFileNr);

            if (P146_GET_ERASE_BINFILES) {
              // Check whether we must delete the oldest file
              if ((P146_TASKVALUE_FILENR != 0) && (P146_TASKVALUE_FILENR  < readFileNr)) {
                ControllerCache.deleteCacheBlock(P146_TASKVALUE_FILENR);
              }
            }

            P146_SET_TASKVALUE_FILENR(readFileNr);
            P146_SET_TASKVALUE_FILEPOS(readPos);
          }
        }
      } else {
        // Default to 1 sec
        Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 1000);
      }

      break;
    }

    case PLUGIN_PROCESS_CONTROLLER_DATA:
    {
      if (P146_GET_SEND_BULK) {
        P146_data_struct *P146_data = static_cast<P146_data_struct *>(getPluginTaskData(event->TaskIndex));

        if (nullptr != P146_data) {
          bool data_sent = false;

          if (P146_GET_SEND_BINARY) {
            data_sent = (0 != P146_data->sendBinaryInBulk(event->TaskIndex, P146_MQTT_MESSAGE_LENGTH));
          } else {
            data_sent = (0 != P146_data->sendCSVInBulk(event->TaskIndex, P146_MQTT_MESSAGE_LENGTH));
          }

          if (data_sent) {
            int readFileNr    = 0;
            const int readPos = ControllerCache.getPeekFilePos(readFileNr);

            if (P146_GET_ERASE_BINFILES) {
              // Check whether we must delete the oldest file
              const int filenr = P146_TASKVALUE_FILENR;

              if ((filenr != 0) && (filenr  < readFileNr)) {
                ControllerCache.deleteCacheBlock(filenr);
              }
            }

            P146_SET_TASKVALUE_FILENR(readFileNr);
            P146_SET_TASKVALUE_FILEPOS(readPos);
          }
          success = true;
        }
      }

      break;
    }

    case PLUGIN_WEBFORM_LOAD:
    {
      addFormCheckBox(F("Delete Cache Files After Send"), F("deletebin"), P146_GET_ERASE_BINFILES);

      addFormSubHeader(F("Read Rate"));
      addFormNumericBox(F("Minimal Send Interval"), F("minsendinterval"), P146_MINIMAL_SEND_INTERVAL, 0, 1000);
      addUnit(F("ms"));

      addFormSubHeader(F("MQTT Output Options"));
      addFormCheckBox(F("Send Bulk"),          F("sendbulk"), P146_GET_SEND_BULK);
      addFormCheckBox(F("HEX encoded Binary"), F("binary"),   P146_GET_SEND_BINARY);

      //      addFormCheckBox(F("Send ReadPos"),          F("sendreadpos"),    P146_GET_SEND_READ_POS);
      addFormNumericBox(F("Max Message Size"),
                        F("maxmsgsize"),
                        P146_MQTT_MESSAGE_LENGTH,
                        sizeof(C016_binary_element) + 16,
                        32768);

      String strings[P146_Nlines];
      LoadCustomTaskSettings(event->TaskIndex, strings, P146_Nlines, 0);
      addFormTextBox(F("TaskInfo Topic"), getPluginCustomArgName(P146_TaskInfoTopicIndex), strings[P146_TaskInfoTopicIndex], P146_Nchars);
      addFormTextBox(F("Publish Topic"),  getPluginCustomArgName(P146_PublishTopicIndex),  strings[P146_PublishTopicIndex],  P146_Nchars);


      addFormSubHeader(F("Non MQTT Output Options"));
      addFormCheckBox(F("Send via Original Task"), F("origTask"),  P146_GET_SEND_VIA_ORIG_TASK);
      addFormCheckBox(F("Send as Event"),          F("sendEvent"), P146_GET_SEND_VIA_EVENT);

      //      addFormCheckBox(F("Send Timestamp"), F("sendtimestamp"), P146_GET_SEND_TIMESTAMP);

      addTableSeparator(F("Export to CSV"), 2, 3);

      {
        const __FlashStringHelper *separatorLabels[] = {
          F("Tab"),
          F("Comma"),
          F("Semicolon")
        };
        const int separatorOptions[] = {
          '\t',
          ',',
          ';'
        };
        constexpr size_t optionCount = NR_ELEMENTS(separatorOptions);
        const FormSelectorOptions selector(optionCount, separatorLabels, separatorOptions);
        selector.addFormSelector(F("Separator"), F("separator"), P146_SEPARATOR_CHARACTER);
      }
      addFormCheckBox(F("Join Samples with same Timestamp"), F("jointimestamp"), P146_GET_JOIN_TIMESTAMP);
      addFormCheckBox(F("Export only enabled tasks"),        F("onlysettasks"),  P146_GET_ONLY_SET_TASKS);

      addFormNote(F("Download button link only updated after saving"));

      addRowLabel(EMPTY_STRING);
      html_add_button_prefix();
      addHtml(F("dumpcache?separator="));

      switch (static_cast<char>(P146_SEPARATOR_CHARACTER))
      {
        case '\t': addHtml(F("Tab"));
          break;
        case ',':  addHtml(F("Comma"));
          break;
        case ';':
        default:   addHtml(F("Semicolon"));
          break;
      }

      if (P146_GET_JOIN_TIMESTAMP) {
        addHtml(F("&jointimestamp=1"));
      }

      if (P146_GET_ONLY_SET_TASKS) {
        addHtml(F("&onlysettasks=1"));
      }
      addHtml(F("'>Download as CSV</a>"));

      success = true;
      break;
    }

    case PLUGIN_WEBFORM_SAVE:
    {
      P146_SET_ERASE_BINFILES(isFormItemChecked(F("deletebin")));
      P146_SET_SEND_BULK(isFormItemChecked(F("sendbulk")));
      P146_SET_SEND_BINARY(isFormItemChecked(F("binary")));

      //      P146_SET_SEND_READ_POS(isFormItemChecked(F("sendreadpos")));
      //      P146_SET_SEND_TIMESTAMP(isFormItemChecked(F("sendtimestamp")));

      P146_MINIMAL_SEND_INTERVAL = getFormItemInt(F("minsendinterval"));
      P146_MQTT_MESSAGE_LENGTH   = getFormItemInt(F("maxmsgsize"));

      P146_SET_JOIN_TIMESTAMP(isFormItemChecked(F("jointimestamp")));
      P146_SET_ONLY_SET_TASKS(isFormItemChecked(F("onlysettasks")));
      P146_SEPARATOR_CHARACTER = getFormItemInt(F("separator"));

      P146_SET_SEND_VIA_ORIG_TASK(isFormItemChecked(F("origTask")));
      P146_SET_SEND_VIA_EVENT(isFormItemChecked(F("sendEvent")));

      String strings[P146_Nlines];

      for (uint8_t varNr = 0; varNr < P146_Nlines; varNr++) {
        strings[varNr] = webArg(getPluginCustomArgName(varNr));
      }

      const String error = SaveCustomTaskSettings(event->TaskIndex, strings, P146_Nlines, 0);

      if (!error.isEmpty()) {
        addHtmlError(error);
      }


      success = true;
      break;
    }

    case PLUGIN_WRITE:
    {
      const String command    = parseString(string, 1);
      const String subcommand = parseString(string, 2);

      if (equals(command, F("cachereader"))) {
        if (equals(subcommand, F("setreadpos"))) {
          if (P146_data_struct::setPeekFilePos(event->Par2, event->Par3)) {
            int peekFileNr{};
            int peekReadPos = ControllerCache.getPeekFilePos(peekFileNr);

            if (peekReadPos >= 0) {
              P146_SET_TASKVALUE_FILENR(peekFileNr);
              P146_SET_TASKVALUE_FILEPOS(peekReadPos);
            }
          }
          success = true;
        } else if (equals(subcommand, F("sendtaskinfo"))) {
          P146_data_struct *P146_data = static_cast<P146_data_struct *>(getPluginTaskData(event->TaskIndex));

          if (nullptr != P146_data) {
            P146_data->sendTaskInfoInBulk(event);
            success = true;
          }
        } else if (equals(subcommand, F("flush"))) {
          P146_data_struct::flush();
          success = true;
        }
      }
      break;
    }
  }
  return success;
}

#endif // ifdef USES_P146
