#include "_Plugin_Helper.h"
#ifdef USES_P104

// #######################################################################################################
// #######################   Plugin 104 - dot matrix display plugin MAX7219       ########################
// #######################################################################################################
//
// Chips/displays supported:
//  MAX7219/21 -- 3 pins (SPI + CS) - Dot matrix display, 8x8 leds per unit/module, units can be connected daisy chained, up to 255
// (theoretically, untested yet)
//
// Plugin Commands:
// Commands have this syntax: dotmatrix,<subcommand>,<zone>[,<argument>]
// <zone> is in the range of configured zones, limited to 1..8 (ESP8266) or 1..16 (ESP32)
// Subcommands:
// clear[,all|<zone>]           : Clears the entire display (all zones if 'all' or no zone specified) or the zone specified
// update[,all|<zone>]          : Updates the entire display (all zones if 'all' or no zone specified) or the zone specified
// size,<zone>,<modules>        : Set the number of modules (Size) for that zone (1..64). A complete reconfiguration will be done if this
//                                setting is changed
// txt,<zone>,<text>            : Put the <text> (use quotes if it contains spaces or commas) in the specified zone, for Bar graph a set of
//                                graphStrings can be set
// settxt,<zone>,<text>         : As the txt subcommand, but also stores the text in the settings for that zone (not automatically saved)
// content,<zone>,<contenttype> : Set the desired content type for that zone (0..)
//                                0 = Text : Any text, including variable expansion
//                                1 = Text reverse : Any text, including variable expansion, reversed when displayed
//                                2 = Clock (4 mod.) : Time in 4 digits (HH:mm) 24h with flashing colon or Clock settings are applied
//                                3 = Clock sec (6 mod) : Time in 6 digits (HH:mm ss) 24h with flashing colon between HH and mm
//                                4 = Date (4 mod.) : Date in 4 digits (dd MM)
//                                5 = Date yy (6/7 mod.) : Date in 6 or 8 digits (dd MM yy / dd mm yyyy)
//                                6 = Date/time (9/13 mod.) : Date + time in 10 digits (dd MM yy HH:mm) 24h, flashing colon between HH and
//                                    mm
//                                7 = Bar graph : See below at bar/setbar commands on how to set the graphString(s).
//                                The (n mod.) suffix indicates the number of modules required to make all digits visible at once
// alignment,<zone>,<alignment> : Set the Alignment of the zone (0 = Left, 1 = Center, 2 = Right). A complete reconfiguration will be done
//                                if this setting is changed
// anim.in,<zone>,<animation>   : Set the Animation In type of the zone (1..). A complete reconfiguration will be done if this setting is
//                                changed
// anim.out,<zone>,<animation>  : Set the Animation Out type of the zone (0..). A complete reconfiguration will be done if this setting is
//                                changed
//                                The supported animation ID's are visible in the Animation In/Out selection comboboxes
//                                Only supported animations are accepted (some can be disabled at compiletime)
// speed,<zone>,<speed>         : Set the Speed of the zone, determining the delay in millis between each animation step
// pause,<zone>,<pause>         : Set the Pause of the zone, determining the delay in millis after Animation In is completed before
//                                Animation Out starts
// font,<zone>,<font ID>        : Set the font for the zone. The font ID is visible in the UI in the Font selection combobox.
// inverted,<zone>,<inv.option> : Set the content display to normal (0), light on dar3k, or inverted (1), dark on light background.
// layout,<zone>,<layout ID>    : Set the Layout type if a double-height font is included (upper(1) or lower(2) part) else only Default (0)
//                                is available
// specialeffect,<zone>,<effect>: Set the Special Effects field, 0 = None, 1 = Flip Up/Done, 2 = Flip Left/Right, 3 = Flip u/d & l/r
//                                Any special effect marked with an asterisk * should not be combined with an Animation marked with an
//                                asterisk, as that could result in unexpected effects during display (it may look strange)
// offset,<zone>,<modules>      : Set the Offset of modules for that zone (0..size). A complete reconfiguration will be done if this setting
//                                is changed
// brightness,<zone>,<level>    : Set the Brightness for the zone, range from 0 (off) to 15 (very bright)
// repeat,<zone>,<delay_sec>    : Set the Repeat (sec) for the zone, after this delay the text & animation will be repeated.
//                                -1 = off, range 0..86400 seconds (24h)
// bar,<zone>,<graph-string>    : Set the graph-string for a Bargraph zone, format: value,max,min,direction,barType|...
// setbar,<zone>,<graph-string> : as the bar subcommand, but also stores the graph-string in the settings for that zone (not automatically
//                                saved)
//                                value: numeric value of type double, variables can be used
//                                max: value for a full bar display, default 100 (percent)
//                                min: value for minimal bar display, default 0
//                                direction: 0 (default): right to left, 1: left to right
//                                barType: 0: solid, width: 8/number of graph-strings
//                                         1: solid, width: 1
//                                         2: dotted line, alternating, only if the bar is wider than 1 pixel
//                                Up to 8 graph-strings can be provided and must be separated by a pipe |
//                                The bar width is determined by the number of graph-strings
// dot,<zone>,<r>,<c>[,0][,...] : Draw dot(s) at the row/column positions provided, adding a 0 after a coordinate will turn that dot off.
//                                Default is to turn a dot On (no argument needed).
//                                Row/col must fit within the zone (r = 1..8, c = 1..8 * modules), double-height parts are separate zones!
//                                Display of the selected zone is suspended until all provided dots are drawn. No quotes are needed around
//                                the coordinate set.
//
// History:
// 2025-04-03 tonhuisman: Set character spacing correctly when changing fonts
// 2023-10-15 tonhuisman: Code improvements, now using NR_ELEMENTS macro instead of multiple #ifdefs and increments
//                        Re-enable use of settings-version V3 after some more testing
// 2023-10-08 tonhuisman: Disable use of settings-version V3 for backward compatibility
// 2023-08-15 tonhuisman: Implement Extended CustomTaskSettings, and use that to significantly improve saving the settings on LittleFS by
//                        a) only storing the settings-version (V3) in regular CustomTaskSettings file, and the rest in the Extended
//                           CustomTaskSettings file, by using the offset as a starting location for the data elements
//                        b) Combine storing the size and the data-block in a single save action
//                        Apply toStringNoZero() converter to reduce the settings-data to be saved
// 2023-08-13 tonhuisman: Add Dot subcommand for pixel-drawing in a zone. Can be applied on any type of zone (so can be overwritten by the
//                        original content when that's updated...)
//                        Set default Hardware type to FC16, as that's the most used for modules found on Aliexpress
// 2023-03-07 tonhuisman: Parse text to display without trimming off leading and trailing spaces
// 2022-08-12 tonhuisman: Remove [DEVELOPMENT] tag
// 2021-10-03 tonhuisman: Add Inverted option per zone
// 2021-09    tonhuisman: Minor improvements, attempts to fix stack failures
// 2021-08-08 tonhuisman: Reworked loading & saving the settings from A huge fixed size pre-allocated block to dynamic allocation
//                        and saving/loading per zone. That should sove the numerous stack related crashes.
// 2021-08-07 tonhuisman: Review feedback: several small improvements and corrections
// 2021-07-18 tonhuisman: Small optimizations and improvements
// 2021-07-14 tonhuisman: Fix some bugs in font selection, add Text reverse content type to improve usability of Vertical font
// 2021-07-12 tonhuisman: Reduce number of reconfiguration during command handling, will be applied the next time content is displayed
//                        update/correct some documentation
// 2021-07-08 tonhuisman: Several bugfixes: settings defaults, fix brightness to enable 0 value, simplify storing the zone settings
// 2021-06-29-2021-07-03: Add Actions column to insert before/after zone or delete a zone, order the zones either in numeric order
//            tonhuisman: or in display order ('upside-down'), fixed many bugs, refactored bar-graph method, other improvements
//                        All optional at compile-time by P104_USE_ZONE_ACTIONS and P104_USE_ZONE_ORDERING
//                        Disabled P104_DEBUG_DEV by default
// 2021-06-28 tonhuisman: Bugfixes during testing, re-enable subcommands for ESP8266 display build
// 2021-06-27 tonhuisman: Implement 'barType' option for Bar-graph, bugfixes, bugfixes, bugfixes
// 2021-06-26 tonhuisman: Implement 'direction' option for Bar-graph, bugfixes
// 2021-06-26 tonhuisman: Add update command for updating one or all zones, restart repeat timer if content is updated by command, bugfixes
// 2021-06-24 tonhuisman: Add min/max range with negative minimal value support and zero-point indication if possible
// 2021-06-23 tonhuisman: Add Bar-graph option and bar command (initial feature, options to implement) guarded with P104_USE_BAR_GRAPH
// 2021-06-22 tonhuisman: Add Bar-graph initial code-infrastructure
// 2021-06-21 tonhuisman: Add options for 'formatting' time & date, will be disabled on memory-tight configs guarded by
//                        P104_USE_DATETIME_OPTIONS
//                        Introduced guard P104_ADD_SETTINGS_NOTES to disable some addFormNotes() to further reduce code size
// 2021-06-19 tonhuisman: Implement repeat delay, add settxt command, add command reference (above), bug fixing, some source reorganization
//                        Webform_Load now works on current settings if the plugin is active, instead of last stored settings
//                        Disabled most commands a some fonts to make the build fit in the ESP8266 display configuration
// 2021-06-18 tonhuisman: Implement PLUGIN_WRITE commands
//                        Implement several fonts extracted from MD_Parola examples (Vertical, Extended ASCII, Full Double Height, Arabic,
//                        Greek, Katakana) (NB: Vertical isn't working as expected yet) Will be disabled when flash memory is tight
// 2021-06-16 tonhuisman: Implement Clock and Date (once per second)
// 2021-06-13 tonhuisman: First working version, many improvemnts to make, like command handling, repeat timer implementaation
// 2021-05 tonhuisman: Store and retrieve settings for max 8 (ESP82xx) or 16 (ESP32) zones
// 2021-04 tonhuisman: Pickup and rework to get it working with ESPEasy
// 2021-03 rixtyan : Initial plugin setup, partially based on P073 MAX7219 LED display

# define PLUGIN_104
# define PLUGIN_ID_104           104
# define PLUGIN_NAME_104         "Display - MAX7219 dot matrix"

# define PLUGIN_104_DEBUG        true // activate extra log info in the debug


# include "src/PluginStructs/P104_data_struct.h"


boolean Plugin_104(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_104;
      dev.Type               = DEVICE_TYPE_SPI;
      dev.VType              = Sensor_VType::SENSOR_TYPE_NONE;
      dev.ExitTaskBeforeSave = false;
      break;
    }

    case PLUGIN_SET_DEFAULTS: {
      CONFIG_PORT              = -1;
      P104_CONFIG_HARDWARETYPE = static_cast<int>(MD_MAX72XX::moduleType_t::FC16_HW);
      break;
    }

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

    case PLUGIN_GET_DEVICEGPIONAMES: {
      event->String1 = formatGpioName_output(F("CS"));
      break;
    }

    case PLUGIN_WEBFORM_LOAD: {
      int8_t spi_pins[3];
      Settings.getSPI_pins(spi_pins);
      int    pinnr = -1;
      bool   input, output, warning;
      String note;
      note.reserve(72);
      note = F("SPI->MAX7219: MOSI");

      if (spi_pins[2] != -1) {
        getGpioInfo(spi_pins[2], pinnr, input, output, warning);
        note += wrap_braces(createGPIO_label(spi_pins[2], pinnr, true, true, false));
      }
      note += F("->DIN, CLK");

      if (spi_pins[0] != -1) {
        getGpioInfo(spi_pins[0], pinnr, input, output, warning);
        note += wrap_braces(createGPIO_label(spi_pins[0], pinnr, true, true, false));
      }
      note += F("->CLK");
      addFormNote(note);

      P104_data_struct *P104_data = static_cast<P104_data_struct *>(getPluginTaskData(event->TaskIndex));

      bool createdWhileActive = false;

      if (nullptr == P104_data) { // Create new object if not active atm.
        P104_data = new (std::nothrow) P104_data_struct(static_cast<MD_MAX72XX::moduleType_t>(P104_CONFIG_HARDWARETYPE),
                                                        event->TaskIndex,
                                                        CONFIG_PIN1,
                                                        -1,
                                                        P104_CONFIG_ZONE_COUNT);
        createdWhileActive = true;
      }

      if (nullptr == P104_data) {
        addFormNote(F("Memory allocation error, re-open task to load."));
        return success;
      }

      if (createdWhileActive) {
        P104_data->loadSettings();
      }
      success = P104_data->webform_load(event);

      if (createdWhileActive) {
        delete P104_data; // Clean up
      }
      break;
    }

    case PLUGIN_WEBFORM_SAVE: {
      P104_data_struct *P104_data = static_cast<P104_data_struct *>(getPluginTaskData(event->TaskIndex));

      P104_CONFIG_ZONE_COUNT = getFormItemInt(F("zonecount"));
      bool createdWhileActive = false;

      if (nullptr == P104_data) { // Create new object if not active atm.
        P104_data = new (std::nothrow) P104_data_struct(static_cast<MD_MAX72XX::moduleType_t>(P104_CONFIG_HARDWARETYPE),
                                                        event->TaskIndex,
                                                        CONFIG_PIN1,
                                                        -1,
                                                        P104_CONFIG_ZONE_COUNT);
        createdWhileActive = true;
      }

      if (nullptr != P104_data) {
        P104_data->setZones(P104_CONFIG_ZONE_COUNT);
        success = P104_data->webform_save(event);

        if (!success) {
          addHtmlError(P104_data->getError());
        }

        if (createdWhileActive) {
          delete P104_data; // Cleanup
        }
      }

      break;
    }

    case PLUGIN_INIT: {
      uint8_t numDevices = P104_CONFIG_TOTAL_UNITS;

      if (numDevices == 0) { // fallback value
        numDevices++;
      }
      # ifdef P104_DEBUG

      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
        addLogMove(LOG_LEVEL_INFO, concat(F("dotmatrix: PLUGIN_INIT numDevices: "), numDevices));
      }
      # endif // ifdef P104_DEBUG

      initPluginTaskData(event->TaskIndex,
                         new (std::nothrow) P104_data_struct(static_cast<MD_MAX72XX::moduleType_t>(P104_CONFIG_HARDWARETYPE),
                                                             event->TaskIndex,
                                                             CONFIG_PIN1,
                                                             numDevices,
                                                             P104_CONFIG_ZONE_COUNT));

      P104_data_struct *P104_data =
        static_cast<P104_data_struct *>(getPluginTaskData(event->TaskIndex));

      if (nullptr == P104_data) {
        return success;
      }

      P104_data->logAllText = bitRead(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_LOG_ALL_TEXT);

      // initialize the LED display
      if (P104_data->begin()) {
        // Setup the zones from configuration
        P104_data->configureZones();

        success = true;
      }
      break;
    }

    case PLUGIN_EXIT: {
      P104_data_struct *P104_data = static_cast<P104_data_struct *>(getPluginTaskData(event->TaskIndex));

      if ((nullptr == P104_data) || (nullptr == P104_data->P)) {
        return success;
      }

      if (bitRead(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_CLEAR_DISABLE)) { // Clear on exit?
        P104_data->P->displayClear();
      }
      success = true;

      break;
    }

    case PLUGIN_WRITE: {
      P104_data_struct *P104_data = static_cast<P104_data_struct *>(getPluginTaskData(event->TaskIndex));

      if (nullptr != P104_data) {
        success = P104_data->handlePluginWrite(event->TaskIndex, string); // process commands
      }

      break;
    }

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

      if ((nullptr != P104_data) && (nullptr != P104_data->P)) {
        P104_data->P->displayAnimate(); // Keep the animations moving
        success = true;
      }

      break;
    }

    case PLUGIN_ONCE_A_SECOND: {
      P104_data_struct *P104_data = static_cast<P104_data_struct *>(getPluginTaskData(event->TaskIndex));

      if ((nullptr != P104_data) && (nullptr != P104_data->P)) {
        if (P104_data->P->displayAnimate()) {     // At least 1 zone is ready
          for (uint8_t z = 0; z < P104_CONFIG_ZONE_COUNT; z++) {
            if (P104_data->P->getZoneStatus(z)) { // If the zone is ready, see if it should be repeated
              P104_data->checkRepeatTimer(z);
            }
          }

          P104_data->handlePluginOncePerSecond(event); // Update date & time contents, if needed
        }
        success = true;
      }
    }
  }

  return success;
}

#endif // USES_P104
