#include <Arduino.h>
#include <ArduinoJson.h>
#include <WiFi.h>
#include <HTTPClient.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;
WiFiClientSecure client;
HTTPClient http;
DynamicJsonDocument doc(32768);

long lastrefresh = 0;
bool firstRequest = true;

int current_index = 0;
long last_index_change = 0;


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);

  matrix->setCursor(0, 0);
  char ssid[] = "YOUR_SSID";
  char pw[] = "YOUR_PSK";
  matrix->print(ssid);
  //Serial.print("Connecting");
  WiFi.begin(ssid, pw);
  // WiFi.begin(ssid, NULL);
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      matrix->fillScreenRGB888(0, 0, 0);
      matrix->setCursor(0, 0);
      matrix->print(WiFi.status());
      //Serial.print(".");
  }
  matrix->fillScreenRGB888(0, 0, 0);

  client.setInsecure();
  http.useHTTP10(true);
  http.begin(client, "https://www.theresno.cloud/schedhelp");
}

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 if (x_pos != 0) {
          x_pos += 2; // small space
      }

      if (y_pos >= 16 && x_pos >= (63 - 6)) {
          // ellipsis
            matrix->drawPixel(x_pos, y_pos + 6, color);
            matrix->drawPixel(x_pos + 2, y_pos + 6, color);
            matrix->drawPixel(x_pos + 4, y_pos + 6, color);
            break;
      }
      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 - 7;

    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->color565(255,255,255));
            matrix->drawPixel(x_pos, y_pos + 4, matrix->color565(255,255,255));
            x_pos = x_pos + 2;
        } else {
            x_pos = x_pos - offset;
            matrix->drawChar(x_pos, y_pos, current, matrix->color565(255,255,255), 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 - 7);
    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, 27 + 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, 27 + y, usPalette[*s++]);
    }
  }
}

void loop() {
    if(firstRequest || (millis() - lastrefresh > 120000)) {
        http.GET();

        DeserializationError error = deserializeJson(doc, http.getStream());
        if (error) {
            //Serial.print(F("deserializeJson() failed: "));
            //Serial.println(error.f_str());
            return;
        }
        lastrefresh = millis();
        firstRequest = false;
    }
    const char *j_title = doc[current_index]["title"];
    const char *j_time = doc[current_index]["start"];
    const char *j_room = doc[current_index]["room"];

    std::string title(j_title); //= "THE ART OF TEXT RE:NDERING";
    std::string time(j_time); // = "11:00";
    std::string loc(j_room); //= "GRND";
    //draw_title(title, matrix->color565(249, 176, 0));
    draw_title(title, matrix->color565(doc[current_index]["r"], doc[current_index]["g"], doc[current_index]["b"]));
    draw_time(time);
    draw_location(loc);


    if (gif.open((uint8_t *)reallytinypp, sizeof(reallytinypp), GIFDraw)) {
      while (gif.playFrame(true, NULL)) {
          ;
      }
      gif.close();
    }
    if (millis() - last_index_change > DISPLAY_DURATION ) {
      current_index++;
      if (current_index > 4) {
          current_index = 0;
      }
      last_index_change = millis();
      matrix->fillScreenRGB888(0, 0, 0);
    }
}
