// Tudor Mocioi 2026. XIAO + display + QPAD interaction functions based on code by Quentin.
// Snake-specific functions based on https://github.com/ranzuh/snake.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// globals
#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

// Initialize the display using the standard Wire (I2C) interface.
// On the XIAO RP2040, Wire defaults to SDA = GPIO 6, SCL = GPIO 7.
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

#define N_TOUCH 6
#define THRESHOLD 6

#define IND_UP 5
#define IND_DOWN 2
#define IND_LEFT 3
#define IND_RIGHT 4
#define IND_RESTART 0
#define IND_REM 1

#define DEBUG true

int touch_pins[N_TOUCH] = { 3, 4, 2, 27, 1, 26 };
int touch_values[N_TOUCH] = { 0, 0, 0, 0, 0, 0 };

bool pin_touched_now[N_TOUCH] = { false, false, false, false, false, false };
bool pin_touched_past[N_TOUCH] = { false, false, false, false, false, false };

int dirX = 1; /* direction xy */
int dirY = 0;
int speed = 10000; /* controls speed of the snake */
int appleX = 0;    /* current apple xy position */
int appleY = 0;
int appleEaten = 1;   /* is apple eaten? */
int snakeLength = 20; /* player snakeLength */
bool game_over = false;

int snakeArray[100][2];

int colliding(int *snakeArray, int snakeLength) {
  /* checks if snake is colliding with walls */
  int x = *snakeArray;
  int y = *(snakeArray + 1);

  if (x < 0 || x > SCREEN_WIDTH) {
    return 1;
  } else if (y < 0 || y > SCREEN_HEIGHT) {
    return 1;
  }

  for (size_t i = 2; i < snakeLength * 2 + 2; i += 2) {
    if (x == *(snakeArray + i) && y == *(snakeArray + i + 1)) {
      return 1;
    }
  }
  return 0;
}

void moveSnake(int *snakeArray, int dirX, int dirY, int speed, int snakeLength) {
  /* moves snake one position to the direction provided also controls the speed
     of the game using usleep function */
  int oldX = 0;
  int oldY = 0;
  int curX = *snakeArray;
  int curY = *(snakeArray + 1);
  int newX = curX + dirX;
  int newY = curY + dirY;

  char print_buffer[30];
  sprintf(print_buffer, "Current Snake X Y %d %d", curX, curY);
  Serial.print(print_buffer);
  Serial.println("");

  *(snakeArray) = newX;
  *(snakeArray + 1) = newY;
  display.drawPixel(newX, newY, SSD1306_WHITE);

  for (size_t i = 2; i < snakeLength * 2; i += 2) {
    oldX = curX;
    oldY = curY;
    curX = *(snakeArray + i);
    curY = *(snakeArray + i + 1);
    newX = oldX;
    newY = oldY;
    *(snakeArray + i) = newX;
    *(snakeArray + i + 1) = newY;

    display.drawPixel(newX, newY, SSD1306_WHITE);
  }

  if (dirY != 0) {
    delay(500000 / speed);
  }

  delay(1000000 / speed);
}

void letThereBeApple(int *appleX, int *appleY, int *appleEaten) {
  /* new random apple location if apple is eaten, if not then prints apple at
     previous location */
  if (*appleEaten) {
    srand(time(0));  //use current time as seed for random generator
    *appleX = (rand() % SCREEN_WIDTH) + 1;
    *appleY = (rand() % SCREEN_HEIGHT) + 1;
    *appleEaten = 0;
  }

  display.drawPixel(*appleX, *appleY, SSD1306_WHITE);
}

void eatApple(int *snakeArray, int appleX, int appleY,
              int *appleEaten, int *snakeLength) {
  /* if snake find apple get one point, also sets appleEaten to true and
  prints snakeLength and debug info */
  int x = *snakeArray;
  int y = *(snakeArray + 1);
  if (x == appleX && y == appleY) {
    *appleEaten = 1;
    *snakeLength += 1;
    int snakeLen = *snakeLength;
    int lastX = *(snakeArray + snakeLen * 2 - 2);
    int lastY = *(snakeArray + snakeLen * 2 - 2 + 1);
    *(snakeArray + snakeLen * 2) = lastX;
    *(snakeArray + snakeLen * 2 + 1) = lastY;
  }
}

void update_touch() {
  int t;
  int t_max = 200;
  int p;

  for (int i = 0; i < N_TOUCH; i++) {
    p = touch_pins[i];

    // set to low
    pinMode(p, OUTPUT);
    digitalWriteFast(p, LOW);

    // settle
    delayMicroseconds(25);

    // enable pull-up
    pinMode(p, INPUT_PULLUP);

    // measure time to rise
    t = 0;
    while (!digitalReadFast(p) && t < t_max) {
      t++;
    }
    touch_values[i] = t;

    // update state
    pin_touched_past[i] = pin_touched_now[i];
    pin_touched_now[i] = touch_values[i] > THRESHOLD;
  }
}

void print_touch() {
  char print_buffer[30];
  for (int i = 0; i < N_TOUCH; i++) {
    sprintf(print_buffer, "%4d ", touch_values[i]);
    Serial.print(print_buffer);
  }
  Serial.println("");
}

void setup() {
  // initialize Serial port
  Serial.begin(0);
  while (!Serial)
    ;

  // Initialize the OLED display at I2C address 0x3C
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("OLED allocation failed");
    for (;;)
      ;  // Loop forever if OLED is not found
  }

  display.clearDisplay();
  display.display();

  Serial.println("Initialized display!");

  for (size_t i = 0; i < 100; i++) {
    for (size_t j = 0; j < 2; j++) {
      snakeArray[i][j] = 0;
    }
  }
  snakeArray[0][0] = 3;
  snakeArray[0][1] = 3;
}

void loop() {
  if (!game_over) {

    display.clearDisplay();
    char print_buffer[30];

    letThereBeApple(&appleX, &appleY, &appleEaten);
    sprintf(print_buffer, "Apple at %d %d", appleX, appleY);
    Serial.print(print_buffer);
    Serial.println("");

    moveSnake(&snakeArray[0][0], dirX, dirY, speed, snakeLength);
    sprintf(print_buffer, "Snake X position %d", snakeArray[0][0]);
    Serial.print(print_buffer);
    Serial.println("");

    eatApple(&snakeArray[0][0], appleX, appleY, &appleEaten, &snakeLength);


    display.display();

    update_touch();
    if (pin_touched_now[IND_UP] && !pin_touched_past[IND_UP]) {
      dirY = -1;
      dirX = 0;
    }

    if (pin_touched_now[IND_DOWN] && !pin_touched_past[IND_DOWN]) {
      dirY = 1;
      dirX = 0;
    }

    if (pin_touched_now[IND_LEFT] && !pin_touched_past[IND_LEFT]) {
      dirY = 0;
      dirX = -1;
    }

    if (pin_touched_now[IND_RIGHT] && !pin_touched_past[IND_RIGHT]) {
      dirY = 0;
      dirX = 1;
    }
  }

  if (colliding(&snakeArray[0][0], snakeLength)) {
    game_over = true;
    display.clearDisplay();
    display.setCursor(0, 20);  // Move cursor down a bit
    display.setTextColor(SSD1306_WHITE);
    display.setTextSize(2);  // Double text size for the typed letters
    display.println("Game over!");
    display.display();

    update_touch();
    if (pin_touched_now[IND_RESTART] && !pin_touched_past[IND_RESTART]) {
      game_over = false;
      dirX = 1; /* direction xy */
      dirY = 0;
      speed = 10000; /* controls speed of the snake */
      appleX = 0;    /* current apple xy position */
      appleY = 0;
      appleEaten = 1;   /* is apple eaten? */
      snakeLength = 20; /* player snakeLength */
      for (size_t i = 0; i < 100; i++) {
        for (size_t j = 0; j < 2; j++) {
          snakeArray[i][j] = 0;
        }
      }
      snakeArray[0][0] = 3;
      snakeArray[0][1] = 3;
    }
    delay(50);
  }
}
