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

#include "main.h"

#include "badapple.h"
#include "felix.h"
#include "homer.h"
#include "ketchup.h"
#include "nyancat.h"
#include "thisisfine.h"

#define R1 42
#define G1 40
#define BL1 41
#define R2 38
#define G2 37
#define BL2 39
#define CH_A 45
#define CH_B 36
#define CH_C 48
#define CH_D 35
#define CH_E 21
#define CLK 2
#define LAT 47
#define OE 14

// esp32 w/ wiring harness
// #define R1 25
// #define G1 27 //26
// #define BL1 26 //27
// #define R2 14
// #define G2 13 //12
// #define BL2 12 //13
// #define CH_A 23
// #define CH_B 22 //19
// #define CH_C 5
// #define CH_D 17
// #define CH_E 32 // assign to any available pin if using panels with 1/32 scan
// #define CLK 16
// #define LAT 4
// #define OE 15

#define PIN_E 21
#define PANEL_WIDTH 64
#define PANEL_HEIGHT 32         // Panel height of 64 will required PIN_E to be defined.

#define PANELS_NUMBER 1

#define PANE_WIDTH PANEL_WIDTH * PANELS_NUMBER
#define PANE_HEIGHT PANEL_HEIGHT
#define NUM_LEDS PANE_WIDTH*PANE_HEIGHT

#define BACK_BUTTON 6
#define NEXT_BUTTON 7

MatrixPanel_I2S_DMA *matrix = nullptr;
AnimatedGIF gif;
int frameDelay = 500;
uint16_t GIFminimumTime = 10;

void setup(){
  pinMode(BACK_BUTTON, INPUT_PULLUP);
  pinMode(NEXT_BUTTON, INPUT_PULLUP);

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

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

  gif.begin(LITTLE_ENDIAN_PIXELS);
}

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(x + xOffset, 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(x, y, usPalette[*s++]);
    }
  }
}


int16_t GIFindex = -1;     // Current file index in GIFpath
int8_t GIFincrement = 1;   // +1 = next GIF, -1 = prev, 0 = same
uint32_t GIFstartTime = 0; // When current GIF started playing
bool GIFisOpen = false;    // True if GIF is currently open

const uint8_t * gifs[6] = {badapple, felix, homer, ketchup, nyancat, thisisfine};
const size_t lengths[6] = {sizeof(badapple), sizeof(felix), sizeof(homer), sizeof(ketchup), sizeof(nyancat), sizeof(thisisfine)};
uint8_t num_gifs = 6;
int16_t xPos = 0, yPos = 0;

void loop() {

  if(!digitalRead(BACK_BUTTON)) {
    GIFincrement = -1;                // Back
    while(!digitalRead(BACK_BUTTON)); // Wait for release
  }

  if(!digitalRead(NEXT_BUTTON)) {
    GIFincrement = 1;                 // Forward
    while(!digitalRead(NEXT_BUTTON)); // Wait for release
  }

  if (GIFincrement) { // Change file?
    if (GIFisOpen) {  // If currently playing,
      gif.close();    // stop it
      GIFisOpen = false;
    }
    GIFindex += GIFincrement; // Fwd or back 1 file
    if (GIFindex >= num_gifs) {
      GIFindex = 0;
    } else if (GIFindex < 0) {
      GIFindex = num_gifs -1;
    }
    Serial.printf("Opening new gif %d...\n", GIFindex);
    if (gif.open((uint8_t *)gifs[GIFindex], lengths[GIFindex], GIFDraw)) {
    //if (gif.open((uint8_t* )nyancat, sizeof(nyancat), GIFDraw)) {
      Serial.printf("GIF dimensions: %d x %d\n",
          gif.getCanvasWidth(), gif.getCanvasHeight());
      xPos = (matrix->width() - gif.getCanvasWidth()) / 2; // Center on matrix
      yPos = (matrix->height() - gif.getCanvasHeight()) / 2;
      GIFisOpen = true;
      GIFstartTime = millis();
      GIFincrement = 0; // Reset increment flag 
    }
  } else if (GIFisOpen) {
    if (gif.playFrame(true, NULL) >= 0) { // Auto resets to start if needed
      //matrix.show();
      if ((millis() - GIFstartTime) >= (GIFminimumTime * 1000)) {
        GIFincrement = 1; // Minimum time has elapsed, proceed to next GIF
      }
    } else {
      Serial.println("Decode Error");
      GIFincrement = 1; // Decode error, proceed to next GIF
    }
  }
}

  //if (gif.open((uint8_t *)badapple, sizeof(badapple), GIFDraw))
  //{
  //  Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight());
  //  while (gif.playFrame(true, NULL))
  //  {
  //  }
  //  gif.close();
  //}

