#include <TFT_eSPI.h>  // Graphics and font library for ILI9341 driver chip
#include <SPI.h>

TFT_eSPI tft = TFT_eSPI();  // Invoke library

struct Rectangle {
  int line1_0x, line1_0y;
  int line1_1x, line1_1y;
  int line3_0x, line3_0y;
  int line3_1x, line3_1y;
};

int line1[2][33] = {{0}};
int line2[2][33] = {{0}};
int line3[2][33] = {{0}};
int line4[2][33] = {{0}};
int line5[2][6] = {
  {70, 61, 49, 34, 17, -5},
  {250, 259, 271, 286, 303, 325}
};

int line_1x0[6], line_1y0[6], line_1x1[6], line_1y1[6];
int line_3x0[6], line_3y0[6], line_3x1[6], line_3y1[6];

int prev_1x0[6], prev_1y0[6], prev_1x1[6], prev_1y1[6];
int prev_3x0[6], prev_3y0[6], prev_3x1[6], prev_3y1[6];

uint8_t lines[6][6] = {
  { 0, 0, 1, 1, 2, 3 },
  { 0, 0, 0, 1, 2, 3 },
  { 3, 4, 5, 6, 7, 8 },
  {8, 10, 12, 14, 16, 18 },
  {18, 20, 23, 26, 30, 31 },
  {0, 9, 12, 15, 18, 21 }
};

int leftLineX = 10;
int rightLineX = 310;

// Screen center coordinates
#define CENTER_X 160  // Adjust for your TFT resolution if needed
#define CENTER_Y 100  // Adjust for your TFT resolution if needed

char countdown[7] = "047100";
unsigned long lastUpdateTime = 0;
unsigned long lastRedLinesTime = 0;
int semiHeight = 5;  // Initial height of the semi-rectangle
bool countdownFinished = false;

Rectangle rect[6];
Rectangle rectInit[6];

uint8_t rectIndex = 0;
// uint8_t lineIndex = 0;

void setup() {
  Serial.begin(9600);
  tft.begin();
  tft.setRotation(1);         // Set the screen rotation (1 = landscape)
  tft.fillScreen(TFT_BLACK);  // Clear the screen

  rect[0] = { 156, 95, 156, 105, 164, 105, 164, 95 };
  rect[1] = { 156, 95, 156, 105, 164, 105, 164, 95 };
  rect[2] = { 150, 86, 150, 114, 170, 114, 170, 86 };
  rect[3] = { 138, 71, 138, 129, 182, 129, 182, 71 };
  rect[4] = { 113, 41, 113, 159, 207, 159, 207, 41 };
  rect[5] = { 70, 3, 70, 198, 250, 198, 250, 3 };

  rectInit[0] = { 156, 95, 156, 105, 164, 105, 164, 95 };
  rectInit[1] = { 156, 95, 156, 105, 164, 105, 164, 95 };
  rectInit[2] = { 150, 86, 150, 114, 170, 114, 170, 86 };
  rectInit[3] = { 138, 71, 138, 129, 182, 129, 182, 71 };
  rectInit[4] = { 113, 41, 113, 159, 207, 159, 207, 41 };
  rectInit[5] = { 70, 3, 70, 198, 250, 198, 250, 3 };

  drawRoundedRectangle();
  drawGrid();
  drawSemi();
  drawCountdown();
  calculatePoints();
}

void loop() {
  updateAnimation();

  if(countdownFinished) {
    countdownFinished = false;

    tft.drawLine(leftLineX, 4, leftLineX, 197, TFT_BLACK);   // Clear previous lines
    tft.drawLine(leftLineX + 1, 4, leftLineX + 1, 197, TFT_BLACK);   // Clear previous lines
    tft.drawLine(leftLineX + 2, 4, leftLineX + 2, 197, TFT_BLACK);   // Clear previous lines
    tft.drawLine(rightLineX, 4, rightLineX, 197, TFT_BLACK);
    tft.drawLine(rightLineX - 1, 4, rightLineX - 1, 197, TFT_BLACK);
    tft.drawLine(rightLineX - 2, 4, rightLineX - 2, 197, TFT_BLACK);

    leftLineX = 10;
    rightLineX = 310;
    strcpy(countdown, "047100");  // Set countdown to default
    drawCountdown();
  }
}

void drawRoundedRectangle() {
  tft.drawRoundRect(2, 0, 316, 200, 15, TFT_YELLOW);
  tft.drawRoundRect(3, 1, 314, 198, 15, TFT_YELLOW);
}

void drawSemi(void) {
  tft.drawRoundRect(70, 207, tft.width() - 132, 31, 15, TFT_YELLOW);
  tft.drawRoundRect(71, 208, tft.width() - 133, 32, 15, TFT_YELLOW);
}

void drawRedLines() {
  tft.drawLine(leftLineX, 4, leftLineX, 197, TFT_RED);   // Draw updated lines
  tft.drawLine(leftLineX + 1, 4, leftLineX + 1, 197, TFT_RED);   // Draw updated lines
  tft.drawLine(leftLineX + 2, 4, leftLineX + 2, 197, TFT_RED);   // Draw updated lines
  tft.drawLine(rightLineX, 4, rightLineX, 197, TFT_RED);
  tft.drawLine(rightLineX - 1, 4, rightLineX - 1, 197, TFT_RED);
  tft.drawLine(rightLineX - 2, 4, rightLineX - 2, 197, TFT_RED);
}

// Draw and update countdown
void drawCountdown() {
  tft.setTextColor(TFT_RED, TFT_BLACK);
  tft.setTextDatum(TC_DATUM);  // Center alignment
  tft.setTextSize(3);
  tft.drawString(countdown, CENTER_X + 2, 213);
}

// Update countdown and animation
void updateCountdownAndAnimation() {
  if (countdownFinished) return;

  unsigned long currentTime = millis();
  if (currentTime - lastUpdateTime < 10) return; // Update every 10ms

  lastUpdateTime = currentTime;

  updateRedLines();

  // Update countdown logic
  for (int i = 4; i >= 0; i--) {
    if (countdown[i] > '0') {
      countdown[i]--;
      break;
    } else {
      countdown[i] = '9';
    }
  }

  if (strcmp(countdown, "000000") == 0) {
    countdownFinished = true;
  }

  drawCountdown();  // Update the countdown display
}

// Update red lines moving to the center
void updateRedLines() {
  
  unsigned long redLinesTime = millis();
  if (countdownFinished || redLinesTime - lastRedLinesTime < 510) return;

  lastRedLinesTime = redLinesTime;

  tft.drawLine(leftLineX, 4, leftLineX, 197, TFT_BLACK);   // Clear previous lines
  tft.drawLine(leftLineX + 1, 4, leftLineX + 1, 197, TFT_BLACK);   // Clear previous lines
  tft.drawLine(leftLineX + 2, 4, leftLineX + 2, 197, TFT_BLACK);   // Clear previous lines
  tft.drawLine(rightLineX, 4, rightLineX, 197, TFT_BLACK);
  tft.drawLine(rightLineX - 1, 4, rightLineX - 1, 197, TFT_BLACK);
  tft.drawLine(rightLineX - 2, 4, rightLineX - 2, 197, TFT_BLACK);

  leftLineX++;
  rightLineX--;

  // Stop moving lines when they reach the center
  if (leftLineX >= CENTER_X - 3) {

    leftLineX = 157;
    rightLineX = 163;
  }
  
  tft.drawLine(leftLineX, 4, leftLineX, 197, TFT_RED);   // Draw updated lines
  tft.drawLine(leftLineX + 1, 4, leftLineX + 1, 197, TFT_RED);   // Draw updated lines
  tft.drawLine(leftLineX + 2, 4, leftLineX + 2, 197, TFT_RED);   // Draw updated lines
  tft.drawLine(rightLineX, 4, rightLineX, 197, TFT_RED);
  tft.drawLine(rightLineX - 1, 4, rightLineX - 1, 197, TFT_RED);
  tft.drawLine(rightLineX - 2, 4, rightLineX - 2, 197, TFT_RED);
  
  drawGrid();
}

void drawGrid(void) {
  tft.drawLine(25, 2, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(26, 2, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(27, 2, CENTER_X - 1, CENTER_Y, TFT_YELLOW);
  tft.drawLine(295, 2, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(294, 2, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(293, 2, CENTER_X + 1, CENTER_Y, TFT_YELLOW);

  tft.drawLine(80, 2, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(81, 2, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(82, 2, CENTER_X - 1, CENTER_Y, TFT_YELLOW);
  tft.drawLine(240, 2, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(239, 2, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(238, 2, CENTER_X + 1, CENTER_Y, TFT_YELLOW);

  tft.drawLine(2, 60, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(2, 61, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(2, 62, CENTER_X - 1, CENTER_Y, TFT_YELLOW);
  tft.drawLine(317, 60, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(317, 61, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(317, 62, CENTER_X + 1, CENTER_Y, TFT_YELLOW);

  tft.drawLine(2, 130, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(2, 131, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(2, 132, CENTER_X - 1, CENTER_Y, TFT_YELLOW);
  tft.drawLine(317, 130, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(317, 131, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(317, 132, CENTER_X + 1, CENTER_Y, TFT_YELLOW);

  tft.drawLine(25, 199, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(26, 199, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(27, 199, CENTER_X - 1, CENTER_Y, TFT_YELLOW);
  tft.drawLine(295, 199, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(294, 199, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(293, 199, CENTER_X + 1, CENTER_Y, TFT_YELLOW);

  tft.drawLine(80, 199, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(81, 199, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(82, 199, CENTER_X - 1, CENTER_Y, TFT_YELLOW);
  tft.drawLine(240, 199, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(239, 199, CENTER_X, CENTER_Y, TFT_YELLOW);
  tft.drawLine(238, 199, CENTER_X + 1, CENTER_Y, TFT_YELLOW);
}

void calculatePoints(void) {

  for(uint8_t counts = 0; counts < 32; counts++) {

    line1[0][counts] = rect[0].line1_0x;
    line1[1][counts] = rect[0].line1_0y;
    line2[0][counts] = rect[0].line1_0x;
    line2[1][counts] = rect[0].line1_1y;
    line4[0][counts] = rect[0].line3_0x;
    line4[1][counts] = rect[0].line1_0y;
    line3[0][counts] = rect[0].line3_0x;
    line3[1][counts] = rect[0].line1_1y;

    limit(rect[0].line1_0x, rect[0].line1_0y, 80, 2, &line_1x0[0], &line_1y0[0]);
    rect[0].line1_0x = line_1x0[0];
    rect[0].line1_0y = line_1y0[0];

    limit(rect[0].line1_1x, rect[0].line1_1y, 80, 199, &line_1x1[0], &line_1y1[0]);
    rect[0].line1_1x = line_1x1[0];
    rect[0].line1_1y = line_1y1[0];

    limit(rect[0].line3_0x, rect[0].line3_0y, 240, 199, &line_3x0[0], &line_3y0[0]);
    rect[0].line3_0x = line_3x0[0];
    rect[0].line3_0y = line_3y0[0];

    limit(rect[0].line3_1x, rect[0].line3_1y, 240, 2, &line_3x1[0], &line_3y1[0]);
    rect[0].line3_1x = line_3x1[0];
    rect[0].line3_1y = line_3y1[0];
  }

  for(uint8_t i=0; i<6; i++) {
    rect[i].line1_0x = rectInit[i].line1_0x;
    rect[i].line1_0y = rectInit[i].line1_0y;

    rect[i].line1_1x = rectInit[i].line1_1x;
    rect[i].line1_1y = rectInit[i].line1_1y;

    rect[i].line3_0x = rectInit[i].line3_0x;
    rect[i].line3_0y = rectInit[i].line3_0y;

    rect[i].line3_1x = rectInit[i].line3_1x;
    rect[i].line3_1y = rectInit[i].line3_1y;
  }
  line1[0][31] = 70;
  line1[1][31] = 3;
  line2[0][31] = 70;
  line2[1][31] = 197;
  line3[0][31] = 250;
  line3[1][31] = 3;
  line4[0][31] = 250;
  line4[1][31] = 197;
}

void updateAnimation(void) {

  for (uint8_t i = 0; i < 6; i++) {
    updateCountdownAndAnimation();

    if(i != 5) {
      rect[i].line1_0x = line1[0][(lines[i][rectIndex])];
      rect[i].line1_0y = line1[1][(lines[i][rectIndex])];
      rect[i].line1_1x = line2[0][(lines[i][rectIndex])];
      rect[i].line1_1y = line2[1][(lines[i][rectIndex])];
      
      rect[i].line3_0x = line3[0][(lines[i][rectIndex])];
      rect[i].line3_0y = line3[1][(lines[i][rectIndex])];
      rect[i].line3_1x = line4[0][(lines[i][rectIndex])];
      rect[i].line3_1y = line4[1][(lines[i][rectIndex])];
    } else {
      rect[i].line1_0x = line5[0][rectIndex];
      rect[i].line1_0y = 3;
      rect[i].line1_1x = line5[0][rectIndex];
      rect[i].line1_1y = 197;
      
      rect[i].line3_0x = line5[1][rectIndex];
      rect[i].line3_0y = 3;
      rect[i].line3_1x = line5[1][rectIndex];
      rect[i].line3_1y = 197;
    }

    if (rectIndex < 3 && i == 1)
      continue;

      tft.drawLine(prev_1x0[i], prev_1y0[i], prev_1x1[i], prev_1y1[i], TFT_BLACK);
      if(i!=5)
        tft.drawLine(prev_1x1[i], prev_1y1[i], prev_3x0[i], prev_3y0[i], TFT_BLACK);
      tft.drawLine(prev_3x0[i], prev_3y0[i], prev_3x1[i], prev_3y1[i], TFT_BLACK);

    tft.drawLine(rect[i].line1_0x, rect[i].line1_0y, rect[i].line1_1x, rect[i].line1_1y, TFT_YELLOW);
    if(i != 5)
      tft.drawLine(rect[i].line1_1x, rect[i].line1_1y, rect[i].line3_0x, rect[i].line3_0y, (rectIndex > 3 && i == 4) ? TFT_BLACK : TFT_YELLOW);
    tft.drawLine(rect[i].line3_0x, rect[i].line3_0y, rect[i].line3_1x, rect[i].line3_1y, TFT_YELLOW);

    prev_1x0[i] = rect[i].line1_0x;
    prev_1y0[i] = rect[i].line1_0y;
    prev_1x1[i] = rect[i].line1_1x;
    prev_1y1[i] = rect[i].line1_1y;
    prev_3x0[i] = rect[i].line3_0x;
    prev_3y0[i] = rect[i].line3_0y;
    prev_3x1[i] = rect[i].line3_1x;
    prev_3y1[i] = rect[i].line3_1y;

    drawGrid();
    drawRedLines();
  }
  rectIndex = (rectIndex + 1) % 5;
}


void limit(int x0, int y0, int x1, int y1, int *xr, int *yr) {  //Bresenham's Algorithm
  int dx = abs(x1 - x0);
  int dy = abs(y1 - y0);
  int sx = (x0 < x1) ? 1 : -1;
  int sy = (y0 < y1) ? 1 : -1;
  int err = dx - dy;

  for (uint8_t i = 0; i < 3; i++) { //increment the semi rectangle by 5 points
    int e2 = 2 * err;

    if (e2 > -dy) {
      err -= dy;
      x0 += sx;
    }

    if (e2 < dx) {
      err += dx;
      y0 += sy;
    }
  }
  *xr = x0;
  *yr = y0;
}