#include <Arduino.h>
#include "xtensa/core-macros.h"
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>

#include <AnimatedGIF.h>

#include "main.h"
#include "constants.h"
#include "config.h"
#include "util.h"
#include "pp.h"

#include <vector>
#include <string>

MatrixPanel_I2S_DMA *matrix = nullptr;
AnimatedGIF gif;

void setup(){
  Serial.begin(BAUD_RATE);
  //while (!Serial) {
  //  ; // wait for serial port to connect. Needed for native USB
  //}
  pinMode(ONBOARD_LED, OUTPUT);

  // redefine pins if required
  HUB75_I2S_CFG::i2s_pins _pins={R1, G1, BL1, R2, G2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK};
  HUB75_I2S_CFG mxconfig(PANEL_WIDTH, PANEL_HEIGHT, PANELS_NUMBER, _pins);

  mxconfig.gpio.e = PIN_E;
  mxconfig.driver = HUB75_I2S_CFG::FM6126A;   // for panels using FM6126A chips

  mxconfig.latch_blanking = 4;
  mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M;
  mxconfig.clkphase = false;

  // mxconfig.double_buff = true;

  matrix = new MatrixPanel_I2S_DMA(mxconfig);
  matrix->begin();
  matrix->setBrightness8(255);
  matrix->fillScreenRGB888(0, 0, 0);

  gif.begin(LITTLE_ENDIAN_PIXELS);
}

void draw_title(const std::string& title, uint16_t color) {
    int y_pos = 0;
    int x_pos = 0;
    //matrix->setCursor(x_pos, y_pos);
    for (size_t idx = 0; idx < title.size(); idx++) {
      char current = title[idx];
      if (current != ' ') {
          if (current == 'I') {
              x_pos -=1;
          }
          matrix->drawChar(x_pos, y_pos, current, color, matrix->color444(0,0,0), 1);
          if (current == 'I') {
              x_pos += 5;
          } else {
              x_pos += 6; // char width + 1px gap
          }
      } else {
          x_pos += 2; // small space
      }

      if (x_pos > (63 - 5)) { // new line
          if (current != ' ') {
              matrix->drawChar(x_pos, y_pos, '-', color, matrix->color444(0,0,0), 1);
          }
          x_pos = 0;
          y_pos = y_pos + 8; //char height + 1px
      }
    }
}

void draw_time(const std::string &time) {
    int x_pos = 0;
    int y_pos = 32 - 8;

    for (size_t idx = 0; idx < time.size(); idx++) {
        char current = time[idx];
        int offset = 0;
        switch (current) {
            case '1':
                offset = 1;
                break;
        }
        if (current == ':') {
            matrix->drawPixel(x_pos, y_pos + 2, matrix->color444(1,1,1));
            matrix->drawPixel(x_pos, y_pos + 4, matrix->color444(1,1,1));
            x_pos = x_pos + 2;
        } else {
            x_pos = x_pos - offset;
            matrix->drawChar(x_pos, y_pos, current, matrix->color444(1,1,1), matrix->color444(0,0,0), 1);
            x_pos = x_pos + (6 - offset);
        }
    }
}

void draw_location(const std::string &location) {
    int pixel_size = (location.size() * 6) - 1;
    matrix->setCursor(63 - pixel_size, 32 - 8);
    matrix->print(location.c_str());
}

void GIFDraw(GIFDRAW *pDraw) {
  uint8_t *s;
  uint16_t *d, *usPalette, usTemp[320];
  int x, y, iWidth;

  usPalette = pDraw->pPalette;
  y = pDraw->iY + pDraw->y; // current line

  s = pDraw->pPixels;
  if (pDraw->ucDisposalMethod == 2) // restore to background color
  {
    for (x = 0; x < iWidth; x++)
    {
      if (s[x] == pDraw->ucTransparent)
        s[x] = pDraw->ucBackground;
    }
    pDraw->ucHasTransparency = 0;
  }
  // Apply the new pixels to the main image
  if (pDraw->ucHasTransparency) // if transparency used
  {
    Serial.println("Transparency!");
    uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
    int x, iCount;
    pEnd = s + pDraw->iWidth;
    //x = 0;
    x = pDraw->iX;
    iCount = 0; // count non-transparent pixels
    while (x < pDraw->iWidth)
    {
      c = ucTransparent - 1;
      d = usTemp;
      while (c != ucTransparent && s < pEnd)
      {
        c = *s++;
        if (c == ucTransparent) // done, stop
        {
          s--; // back up to treat it like transparent
        }
        else // opaque
        {
          *d++ = usPalette[c];
          iCount++;
        }
      }           // while looking for opaque pixels
      if (iCount) // any opaque pixels?
      {
        for (int xOffset = 0; xOffset < iCount; xOffset++)
        {
          matrix->drawPixel(28 + x + xOffset, 25 + y, usTemp[xOffset]);
        }
        x += iCount;
        iCount = 0;
      }
      // no, look for a run of transparent pixels
      c = ucTransparent;
      while (c == ucTransparent && s < pEnd)
      {
        c = *s++;
        if (c == ucTransparent)
          iCount++;
        else
          s--;
      }
      if (iCount)
      {
        x += iCount; // skip these
        iCount = 0;
      }
    }
  }
  else
  {
    s = pDraw->pPixels;
    // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
    for (x = 0; x < pDraw->iWidth; x++)
    {
      matrix->drawPixel(28 + x, 25 + y, usPalette[*s++]);
    }
  }
}

void loop() {
  std::string title = "THE ART OF TEXT RENDERING";
  std::string time = "11:00";
  std::string loc = "GRND";
  draw_title(title, matrix->color565(249, 176, 0));
  draw_time(time);
  draw_location(loc);

  if (gif.open((uint8_t *)reallytinypp, sizeof(reallytinypp), GIFDraw)) {
    while (gif.playFrame(true, NULL)) {
        ;
    }
    gif.close();
  }
}
