#include <Adafruit_ILI9341.h>

#define TFT_CLK 18
#define TFT_MISO 19
#define TFT_MOSI 23
#define TFT_CS 15
#define TFT_RST 4
#define TFT_DC 2

#define JOY_X 34
#define JOY_Y 35
#define JOY_SW 32

#define CARD_WIDTH 40
#define CARD_HEIGHT 40
#define CARD_MARGIN 16
#define NUM_CARDS 16

#define COVERED 0
#define UNCOVERED 1
#define REMOVED 2

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

struct Card {
  int color = -1;
  int state;
};

Card cards[NUM_CARDS];
int selectedCardIndex = -1;
int numUncoveredCards = 0;

void initializeGame();
void drawBoard();
void handleInput();
void uncoverCard(int index);
void checkMatch();
void gameOver();

void setup() {
  tft.begin();
  tft.setRotation(3);
  Serial.begin(9600);

  initializeGame();
}

void loop() {
  handleInput();
}

void initializeGame() {
  int colors[] = {ILI9341_RED, ILI9341_GREEN, ILI9341_BLUE, ILI9341_YELLOW, ILI9341_CYAN, ILI9341_MAGENTA, ILI9341_PINK, ILI9341_LIGHTGREY};

  for (int i = 0; i < NUM_CARDS; i++) {
    cards[i].state = COVERED;
  }
  
  for (int i = 0; i < NUM_CARDS / 2; i++) {
    int color = colors[i];
    int index1 = random(NUM_CARDS);
    int index2 = random(NUM_CARDS);
    while (cards[index1].color != -1 || cards[index2].color != -1 || index1 == index2) {
      index1 = random(NUM_CARDS);
      index2 = random(NUM_CARDS);
    }
    cards[index1].color = color;
    cards[index2].color = color;
  }

  selectedCardIndex = -1;
  numUncoveredCards = 0;

  drawBoard();
}

void drawBoard() {
  tft.fillScreen(ILI9341_BLACK);

  for (int i = 0; i < NUM_CARDS; i++) {
    int row = i / 4;
    int col = i % 4;
    int x = col * (CARD_WIDTH + CARD_MARGIN) + CARD_MARGIN;
    int y = row * (CARD_HEIGHT + CARD_MARGIN) + CARD_MARGIN;

    int color;
    if (cards[i].state == COVERED) {
      color = ILI9341_DARKGREY;
    } else {
      color = cards[i].color;
    }

    tft.fillRect(x, y, CARD_WIDTH, CARD_HEIGHT, color);
  }
}

int getButtonState() {
  static bool rise = false;
  if (digitalRead(JOY_SW)) {
    if (rise) {
      rise = false;
      return 1;
    } else {
      return 0;
    }
  } else {
    rise = true;
    return 0;
  }
}

int getJOYselect() {
  static int preRow = 0;
  static int preCol = 0;
  int row = preRow;
  int col = preCol;
  static bool upRise = false;
  static bool downRise = false;
  static bool rightRise = false;
  static bool leftRise = false;
  
  if (analogRead(JOY_X) > 2100) {
    if (upRise) {
      upRise = false;
      row++;
    }
  } else {
    upRise = true;
  }

  if (analogRead(JOY_X) < 2000) {
    if (downRise) {
      downRise = false;
      row--;
    }
  } else {
    downRise = true;
  }

  if (analogRead(JOY_Y) > 2100) {
    if (rightRise) {
      rightRise = false;
      col++;
    }
  } else {
    rightRise = true;
  }

  if (analogRead(JOY_Y) < 2000) {
    if (leftRise) {
      leftRise = false;
      col--;
    }
  } else {
    leftRise = true;
  }
  
  if (row < 0) {
    row = 0;
  }
  
  if (col < 0) {
    col = 0;
  }
  
  if (row >= 4) {
    row = 3;
  }
  
  if (col >= 4) {
    col = 3;
  }
  
  int x = col * (CARD_WIDTH + CARD_MARGIN) + CARD_MARGIN;
  int y = row * (CARD_HEIGHT + CARD_MARGIN) + CARD_MARGIN;
  tft.fillRect(x + 40, y + 40, 8, 8, ILI9341_RED);
  
  if (preRow != row || preCol != col) {
    int x = preCol * (CARD_WIDTH + CARD_MARGIN) + CARD_MARGIN;
    int y = preRow * (CARD_HEIGHT + CARD_MARGIN) + CARD_MARGIN;
    tft.fillRect(x + 40, y + 40, 8, 8, ILI9341_BLACK);
    preRow = row;
    preCol = col;
  }
  
  return row * 4 + col;
}

void handleInput() {
  int index = getJOYselect();
  int buttonState = getButtonState();
  
  if (buttonState) {
    if (selectedCardIndex == -1) {
      if (cards[index].state == COVERED) {
        uncoverCard(index);
        selectedCardIndex = index;
      }
    } else {
      if (index != selectedCardIndex && cards[index].state == COVERED) {
        uncoverCard(index);
        delay(1000);
        checkMatch(index, selectedCardIndex);
      }

      selectedCardIndex = -1;
    }
  }
}

void uncoverCard(int index) {
  cards[index].state = UNCOVERED;
  numUncoveredCards++;

  int row = index / 4;
  int col = index % 4;
  int x = col * (CARD_WIDTH + CARD_MARGIN) + CARD_MARGIN;
  int y = row * (CARD_HEIGHT + CARD_MARGIN) + CARD_MARGIN;

  int color = cards[index].color;
  tft.fillRect(x, y, CARD_WIDTH, CARD_HEIGHT, color);
}

void checkMatch(int A, int B) {
  int rowA = A / 4;
  int colA = A % 4;

  int xA = colA * (CARD_WIDTH + CARD_MARGIN) + CARD_MARGIN;
  int yA = rowA * (CARD_HEIGHT + CARD_MARGIN) + CARD_MARGIN;

  int rowB = B / 4;
  int colB = B % 4;

  int xB = colB * (CARD_WIDTH + CARD_MARGIN) + CARD_MARGIN;
  int yB = rowB * (CARD_HEIGHT + CARD_MARGIN) + CARD_MARGIN;

  if (cards[A].color == cards[B].color) {
    cards[A].state = REMOVED;
    cards[B].state = REMOVED;

    tft.fillRect(xA, yA, CARD_WIDTH, CARD_HEIGHT, ILI9341_BLACK);
    tft.fillRect(xB, yB, CARD_WIDTH, CARD_HEIGHT, ILI9341_BLACK);

    numUncoveredCards -= 2;
    if (numUncoveredCards == 0) {
      gameOver();
    }
  } else {
    cards[A].state = COVERED;
    cards[B].state = COVERED;

    tft.fillRect(xA, yA, CARD_WIDTH, CARD_HEIGHT, ILI9341_DARKGREY);
    tft.fillRect(xB, yB, CARD_WIDTH, CARD_HEIGHT, ILI9341_DARKGREY);
  }
}

void gameOver() {
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(20, 100);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(3);
  tft.println("Game Over!");
  delay(3000);
  initializeGame();
}