#ifdef USES_P023
#include "../PluginStructs/P023_data_struct.h"

#include "../Helpers/Misc.h"
#include "../Helpers/StringParser.h"

#define P023_DATA_MODE_REG      0x40
#define P023_COMMAND_MODE_REG   0x80


const char Plugin_023_myFont_Size[] PROGMEM = {
  0x05, // SPACE
  0x05, // !
  0x07, // "
  0x08, // #
  0x08, // $
  0x08, // %
  0x08, // &
  0x06, // '
  0x06, // (
  0x06, // )
  0x08, // *
  0x08, // +
  0x05, // ,
  0x08, // -
  0x05, // .
  0x08, // /
  0x08, // 0
  0x07, // 1
  0x08, // 2
  0x08, // 3
  0x08, // 4
  0x08, // 5
  0x08, // 6
  0x08, // 7
  0x08, // 8
  0x08, // 9
  0x06, // :
  0x06, // ;
  0x07, // <
  0x08, // =
  0x07, // >
  0x08, // ?
  0x08, // @
  0x08, // A
  0x08, // B
  0x08, // C
  0x08, // D
  0x08, // E
  0x08, // F
  0x08, // G
  0x08, // H
  0x06, // I
  0x08, // J
  0x08, // K
  0x08, // L
  0x08, // M
  0x08, // N
  0x08, // O
  0x08, // P
  0x08, // Q
  0x08, // R
  0x08, // S
  0x08, // T
  0x08, // U
  0x08, // V
  0x08, // W
  0x08, // X
  0x08, // Y
  0x08, // Z
  0x06, // [
  0x08, // BACKSLASH
  0x06, // ]
  0x08, // ^
  0x08, // _
  0x06, // `
  0x08, // a
  0x08, // b
  0x07, // c
  0x08, // d
  0x08, // e
  0x07, // f
  0x08, // g
  0x08, // h
  0x05, // i
  0x06, // j
  0x07, // k
  0x06, // l
  0x08, // m
  0x07, // n
  0x07, // o
  0x07, // p
  0x07, // q
  0x07, // r
  0x07, // s
  0x06, // t
  0x07, // u
  0x08, // v
  0x08, // w
  0x08, // x
  0x07, // y
  0x08, // z
  0x06, // {
  0x05, // |
  0x06, // }
  0x08, // ~
  0x08  // DEL
};

const char Plugin_023_myFont[][5] PROGMEM = {
  { 0x00, 0x00, 0x00, 0x00, 0x00 }, // SPACE
  { 0x00, 0x5F, 0x00, 0x00, 0x00 }, // !
  { 0x00, 0x07, 0x00, 0x07, 0x00 }, // "
  { 0x14, 0x7F, 0x14, 0x7F, 0x14 }, // #
  { 0x24, 0x2A, 0x7F, 0x2A, 0x12 }, // $
  { 0x23, 0x13, 0x08, 0x64, 0x62 }, // %
  { 0x36, 0x49, 0x55, 0x22, 0x50 }, // &
  { 0x00, 0x05, 0x03, 0x00, 0x00 }, // '
  { 0x1C, 0x22, 0x41, 0x00, 0x00 }, // (
  { 0x41, 0x22, 0x1C, 0x00, 0x00 }, // )
  { 0x08, 0x2A, 0x1C, 0x2A, 0x08 }, // *
  { 0x08, 0x08, 0x3E, 0x08, 0x08 }, // +
  { 0xA0, 0x60, 0x00, 0x00, 0x00 }, // ,
  { 0x08, 0x08, 0x08, 0x08, 0x08 }, // -
  { 0x60, 0x60, 0x00, 0x00, 0x00 }, // .
  { 0x20, 0x10, 0x08, 0x04, 0x02 }, // /
  { 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0
  { 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1
  { 0x62, 0x51, 0x49, 0x49, 0x46 }, // 2
  { 0x22, 0x41, 0x49, 0x49, 0x36 }, // 3
  { 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4
  { 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5
  { 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6
  { 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7
  { 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8
  { 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9
  { 0x00, 0x36, 0x36, 0x00, 0x00 }, // :
  { 0x00, 0xAC, 0x6C, 0x00, 0x00 }, // ;
  { 0x08, 0x14, 0x22, 0x41, 0x00 }, // <
  { 0x14, 0x14, 0x14, 0x14, 0x14 }, // =
  { 0x41, 0x22, 0x14, 0x08, 0x00 }, // >
  { 0x02, 0x01, 0x51, 0x09, 0x06 }, // ?
  { 0x32, 0x49, 0x79, 0x41, 0x3E }, // @
  { 0x7E, 0x09, 0x09, 0x09, 0x7E }, // A
  { 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B
  { 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C
  { 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D
  { 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E
  { 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F
  { 0x3E, 0x41, 0x41, 0x51, 0x72 }, // G
  { 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H
  { 0x41, 0x7F, 0x41, 0x00, 0x00 }, // I
  { 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J
  { 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K
  { 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L
  { 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M
  { 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N
  { 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O
  { 0x7F, 0x09, 0x09, 0x09, 0x06 }, // P
  { 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q
  { 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R
  { 0x26, 0x49, 0x49, 0x49, 0x32 }, // S
  { 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T
  { 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U
  { 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V
  { 0x3F, 0x40, 0x38, 0x40, 0x3F }, // W
  { 0x63, 0x14, 0x08, 0x14, 0x63 }, // X
  { 0x03, 0x04, 0x78, 0x04, 0x03 }, // Y
  { 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z
  { 0x7F, 0x41, 0x41, 0x00, 0x00 }, // [
  { 0x02, 0x04, 0x08, 0x10, 0x20 }, // BACKSLASH
  { 0x41, 0x41, 0x7F, 0x00, 0x00 }, // ]
  { 0x04, 0x02, 0x01, 0x02, 0x04 }, // ^
  { 0x80, 0x80, 0x80, 0x80, 0x80 }, // _
  { 0x01, 0x02, 0x04, 0x00, 0x00 }, // `
  { 0x20, 0x54, 0x54, 0x54, 0x78 }, // a
  { 0x7F, 0x48, 0x44, 0x44, 0x38 }, // b
  { 0x38, 0x44, 0x44, 0x28, 0x00 }, // c
  { 0x38, 0x44, 0x44, 0x48, 0x7F }, // d
  { 0x38, 0x54, 0x54, 0x54, 0x18 }, // e
  { 0x08, 0x7E, 0x09, 0x02, 0x00 }, // f
  { 0x18, 0xA4, 0xA4, 0xA4, 0x7C }, // g
  { 0x7F, 0x08, 0x04, 0x04, 0x78 }, // h
  { 0x00, 0x7D, 0x00, 0x00, 0x00 }, // i
  { 0x80, 0x84, 0x7D, 0x00, 0x00 }, // j
  { 0x7F, 0x10, 0x28, 0x44, 0x00 }, // k
  { 0x41, 0x7F, 0x40, 0x00, 0x00 }, // l
  { 0x7C, 0x04, 0x18, 0x04, 0x78 }, // m
  { 0x7C, 0x08, 0x04, 0x7C, 0x00 }, // n
  { 0x38, 0x44, 0x44, 0x38, 0x00 }, // o
  { 0xFC, 0x24, 0x24, 0x18, 0x00 }, // p
  { 0x18, 0x24, 0x24, 0xFC, 0x00 }, // q
  { 0x00, 0x7C, 0x08, 0x04, 0x00 }, // r
  { 0x48, 0x54, 0x54, 0x24, 0x00 }, // s
  { 0x04, 0x7F, 0x44, 0x00, 0x00 }, // t
  { 0x3C, 0x40, 0x40, 0x7C, 0x00 }, // u
  { 0x1C, 0x20, 0x40, 0x20, 0x1C }, // v
  { 0x3C, 0x40, 0x30, 0x40, 0x3C }, // w
  { 0x44, 0x28, 0x10, 0x28, 0x44 }, // x
  { 0x1C, 0xA0, 0xA0, 0x7C, 0x00 }, // y
  { 0x44, 0x64, 0x54, 0x4C, 0x44 }, // z
  { 0x08, 0x36, 0x41, 0x00, 0x00 }, // {
  { 0x00, 0x7F, 0x00, 0x00, 0x00 }, // |
  { 0x41, 0x36, 0x08, 0x00, 0x00 }, // }
  { 0x02, 0x01, 0x01, 0x02, 0x01 }, // ~
  { 0x02, 0x05, 0x05, 0x02, 0x00 }  // DEL
};


P023_data_struct::P023_data_struct(uint8_t                   _address,
                                   uint8_t                   _type,
                                   P023_data_struct::Spacing _font_spacing,
                                   uint8_t                   _displayTimer,
                                   uint8_t                   _use_sh1106)
  :  address(_address), type(_type),  font_spacing(_font_spacing),  displayTimer(_displayTimer), use_sh1106(_use_sh1106)
{}

void P023_data_struct::setDisplayTimer(uint8_t _displayTimer) {
  displayOn();
  displayTimer = _displayTimer;
}

void P023_data_struct::checkDisplayTimer() {
  if (displayTimer > 0) {
    displayTimer--;

    if (displayTimer == 0) {
      displayOff();
    }
  }
}

// Perform some specific changes for OLED display
String P023_data_struct::parseTemplate(String& tmpString, uint8_t lineSize) {
  String result             = parseTemplate_padded(tmpString, lineSize);
  const char degree[3]      = { 0xc2, 0xb0, 0 }; // Unicode degree symbol
  const char degree_oled[2] = { 0x7F, 0 };       // P023_OLED degree symbol

  result.replace(degree, degree_oled);
  return result;
}

void P023_data_struct::resetDisplay() {
  displayOff();
  clearDisplay();
  displayOn();
}

void P023_data_struct::StartUp_OLED(struct EventStruct *event) {
  init_OLED();
  resetDisplay();

  // displayOff();
  setXY(0, 0);

  // clearDisplay(); // Why clear twice?
  // displayOn();

  LoadCustomTaskSettings(event->TaskIndex, strings, P23_Nlines, P23_Nchars);
}

bool P023_data_struct::plugin_read(struct EventStruct *event) {
  for (uint8_t x = 0; x < 8; ++x) {
    if (strings[x].length()) {
      String tmp             = strings[x];
      const String newString = parseTemplate(tmp, 16);

      sendStrXY(newString.c_str(), x, 0);
      #if P023_FEATURE_DISPLAY_PREVIEW
      currentLines[x] = newString;
      #endif // if P023_FEATURE_DISPLAY_PREVIEW
    }
  }
  return true;
}

bool P023_data_struct::plugin_write(struct EventStruct *event,
                                    String            & string) {
  bool success     = false;
  const String cmd = parseString(string, 1); // Changes to lowercase

  if (equals(cmd, F("oledcmd"))) {
    const String param = parseString(string, 2);

    if (equals(param, F("off"))) {
      displayOff();
      success = true;
    }
    else if (equals(param, F("on"))) {
      displayOn();
      success = true;
    }
    else if (equals(param, F("clear"))) {
      clearDisplay();
      success = true;
    }
  }
  else if (equals(cmd, F("oled"))) {
    success = true;
    String text = parseStringToEndKeepCase(string, 4);
    text = parseTemplate(text, 16);
    sendStrXY(text.c_str(), event->Par1 - 1, event->Par2 - 1);
    #if P023_FEATURE_DISPLAY_PREVIEW
    setCurrentText(text, event->Par1 - 1, event->Par2 - 1);
    #endif // if P023_FEATURE_DISPLAY_PREVIEW
  }
  return success;
}

#if P023_FEATURE_DISPLAY_PREVIEW

/**
 * Update the buffer that is used for PLUGIN_WEB_SHOW_VALUES function
 */
void P023_data_struct::setCurrentText(const String& string, int X, int Y) {
  if ((X < 0) || (Y < 0) || (X >= P23_Nlines)) { return; } // Sanity check

  if (Y > 0) {
    if (currentLines[X].length() >= static_cast<size_t>(Y)) {
      currentLines[X] = currentLines[X].substring(0, Y + 1) + string;
    } else {
      for (size_t i = currentLines[X].length(); i < static_cast<size_t>(Y); ++i) {
        currentLines[X] += ' ';
      }
      currentLines[X] += string;
    }
  } else {
    currentLines[X] = string;
  }
}

bool P023_data_struct::web_show_values() {
  bool result     = true;
  uint8_t maxLine = P23_Nlines;

  for (; maxLine > 0; --maxLine) { // Don't show trailing empty lines
    String tmp = currentLines[maxLine - 1];
    tmp.trim();

    if (!tmp.isEmpty()) { break; }
  }

  addHtml(F("<pre>")); // To keep spaces etc. in the shown output

  for (uint8_t i = 0; i < maxLine; ++i) {
    addHtmlDiv(F("div_l"), currentLines[i], EMPTY_STRING, F("style='font-size:75%;'"));

    if (i != maxLine - 1) {
      addHtmlDiv(F("div_br"));
    }
  }
  addHtml(F("</pre>"));
  return result;
}

#endif // if P023_FEATURE_DISPLAY_PREVIEW

void P023_data_struct::displayOn() {
  sendCommand(0xaf); // display on
}

void P023_data_struct::displayOff() {
  sendCommand(0xae); // display off
}

void P023_data_struct::clearDisplay() {
  unsigned char i, k;

  for (k = 0; k < 8; ++k) {
    setXY(k, 0);

    for (i = 0; i < 128; ++i) { // clear all COL
      sendChar(0);              // clear all COL
    }
  }
}

// Actually this sends a byte, not a char to draw in the display.
void P023_data_struct::sendChar(unsigned char data) {
  I2C_write8_reg(address, P023_DATA_MODE_REG, data);
}

void P023_data_struct::sendCommand(unsigned char com) {
  I2C_write8_reg(address, P023_COMMAND_MODE_REG, com);
}

// Set the cursor position in a 16 COL * 8 ROW map (128x64 pixels)
// or 8 COL * 5 ROW map (64x48 pixels)
void P023_data_struct::setXY(unsigned char row, unsigned char col) {
  unsigned char col_offset = 0;

  if (use_sh1106) {
    col_offset = 0x02; // offset of 2 when using SSH1106 controller
  }

  if (type == OLED_64x48) {
    col += 4;
  } else if (type == (OLED_64x48 | OLED_rotated)) {
    col += 4;
    row += 2;
  }

  sendCommand(0xb0 + row);                             // set page address
  sendCommand(0x00 + ((8 * col + col_offset) & 0x0f)); // set low col address
  sendCommand(0x10 + (((8 * col) >> 4) & 0x0f));       // set high col address
}

// Prints a string in coordinates X Y, being multiples of 8.
// This means we have 16 COLS (0-15) and 8 ROWS (0-7).
void P023_data_struct::sendStrXY(const char *string, int X, int Y) {
  setXY(X, Y);
  uint16_t i             = 0;
  uint16_t char_width    = 8;
  uint16_t maxPixels     = 128;   // Assumed default display width
  uint16_t currentPixels = Y * 8; // setXY always uses char_width = 8, Y = 0-based

  if ((type == OLED_64x48) ||     // Cater for that 1 smaller size display
      (type == (OLED_64x48 | OLED_rotated))) {
    maxPixels = 64;
  }

  constexpr int myFont_nrChars = NR_ELEMENTS(Plugin_023_myFont_Size);

  while (*string && currentPixels < maxPixels) { // Prevent display overflow on the character level
    auto index = *string - 0x20;
    if (index < 0 || index >= myFont_nrChars) {
      // Char not in font, just print a space
      index = 0;
    }
    if (font_spacing == Spacing::optimized) {
      char_width = pgm_read_byte(&(Plugin_023_myFont_Size[index]));
    }

    const char * baddr = Plugin_023_myFont[index];
    for (i = 0; i < char_width && currentPixels < maxPixels; ++i,++currentPixels) { // Prevent display overflow on the pixel-level
      sendChar((i == 0 || i > 5) ? 0x00 : pgm_read_byte(baddr + i - 1)); 
    }
    string++;
  }
}

void P023_data_struct::init_OLED() {
  unsigned char multiplex;
  unsigned char compins;

  if (type == OLED_128x32) {
    multiplex = 0x1F;
    compins   = 0x02;
  } else {
    multiplex = 0x3F;
    compins   = 0x12;
  }

  sendCommand(0xAE);       // display off
  sendCommand(0xD5);       // SETDISPLAYCLOCKDIV
  sendCommand(0x80);       // the suggested ratio 0x80
  sendCommand(0xA8);       // SSD1306_SETMULTIPLEX
  sendCommand(multiplex);  // 0x1F if 128x32, 0x3F if others (e.g. 128x64)
  sendCommand(0xD3);       // SETDISPLAYOFFSET
  sendCommand(0x00);       // no offset
  sendCommand(0x40 | 0x0); // SETSTARTLINE

  if (use_sh1106) {
    sendCommand(0xAD);     // CHARGEPUMP mode SH1106
    sendCommand(0x8B);     // CHARGEPUMP On SH1106
    sendCommand(0x32);     // CHARGEPUMP voltage 8V SH1106
    sendCommand(0x81);     // SETCONTRAS
    sendCommand(0x80);     // SH1106
  } else {
    sendCommand(0x8D);     // CHARGEPUMP
    sendCommand(0x14);
    sendCommand(0x81);     // SETCONTRAS
    sendCommand(0xCF);
  }
  sendCommand(0x20);       // MEMORYMODE
  sendCommand(0x00);       // 0x0 act like ks0108
  sendCommand(0xA0);       // 128x32 ???
  sendCommand(0xC0);       // 128x32 ???
  sendCommand(0xDA);       // COMPINS
  sendCommand(compins);    // 0x02 if 128x32, 0x12 if others (e.g. 128x64)
  sendCommand(0xD9);       // SETPRECHARGE
  sendCommand(0xF1);
  sendCommand(0xDB);       // SETVCOMDETECT
  sendCommand(0x40);
  sendCommand(0xA4);       // DISPLAYALLON_RESUME
  sendCommand(0xA6);       // NORMALDISPLAY

  clearDisplay();
  sendCommand(0x2E);       // stop scroll
  sendCommand(0x20);       // Set Memory Addressing Mode
  sendCommand(0x00);       // Set Memory Addressing Mode ab Horizontal addressing mode
}

#endif // ifdef USES_P023
