#include <Adafruit_SSD1306.h>
#include <LiquidCrystal.h>

//Kijelző define-ok
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

//Joystick define-ok
#define VERTICAL A0
#define HORIZONTAL A1
#define CENTER 12

//Buzzer define-ok
#define BUZZER 13

//Játék define-ok

//Győzelemhez szükséges pont
#define SCORE_TO_WIN 10

//Méret
#define SNAKE_BASE_LENGTH 3

//Irányok
#define LEFT 0
#define RIGHT 1
#define UP 2
#define DOWN 3

//Játék státuszok
#define GAME_RUN 1
#define GAME_OVER 2
#define GAME_WIN 3

//OLED setup
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

//LCD setup
LiquidCrystal lcd(2, 3, 11, 10, 9, 8);

byte* snakeCoordinates = (byte*)malloc((SNAKE_BASE_LENGTH * 2) * sizeof(byte));
byte foodCoordinates[2];

byte optionsCount = 3;
char* options[] = {
  "Start",
  "Settings",
  "Info"
};
byte optionsStartInHover[] = {
  50,
  40,
  53
};

byte settingsCount = 2;
char* settings[] = {
  "Size:",
  "Velocity:"
};
byte selectedSetting = 0;

byte sizeCount = 2;
byte size[] = {
  2, 4
};
byte selectedSize = 0;

byte velocityCount = 4;
int velocity[] = {
  100, 140, 180, 220
};
byte selectedVelocity = 0;

byte option = 0;
byte state = 0;

byte length;
byte direction;
int score;
byte gameState;

bool first = false;

bool initialization = true;

void setup() {

  Serial.begin(9600);

  lcd.begin(16, 2);
 
  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    while(true);
  }

  pinMode(VERTICAL, INPUT);
  pinMode(HORIZONTAL, INPUT);
  pinMode(CENTER, INPUT_PULLUP);
  pinMode(BUZZER, OUTPUT);

  setupGame();

  lcd.clear();
  lcd.print("WELCOME TO SNAKE");
}

void loop() {
  switch (state) {
    //Főmenü
    case 0:
        selectMenuOption();
        drawMenu();
      break;
    //Játék
    case 1:
      if (gameState == GAME_RUN) {
        initGame(1000);

        int x = analogRead(HORIZONTAL);
        int y = analogRead(VERTICAL);

        setDirection(x, y);
        runGame(direction);
      } else if (gameState == GAME_OVER) {
        overGame();
      } else {
        winGame();
      }
      break;
    //Beállítások
    case 2:
      selectSetting();
      drawSettings();
      break;
    //Információ
    case 3:
      returnFromInformation();
      drawInformation();
      break;
  }
}

//Játék vezérlő függvények

//Játék inicializálása (joystick miatt)
void initGame(int time) {
  if (initialization) {
    oled.clearDisplay();
    oled.setTextSize(1);
    oled.setTextColor(WHITE);
    oled.setCursor(12, 30);
    oled.println("Initialization...");
    oled.display();

    delay(time);
    initialization = false;

    lcd.clear();
    lcd.print("SCORE: ");
    lcd.print(score);
  }
}

//Alapbeállítások beállítása, nullázások
void setupGame() {

  length = SNAKE_BASE_LENGTH;
  score = 0;
  direction = UP;
  gameState = GAME_RUN;
  initialization = true;

  for (int i = 0; i < (length * 2); i+=2) {
    snakeCoordinates[i] = SCREEN_WIDTH/2;
    snakeCoordinates[i+1] = SCREEN_HEIGHT/2 - size[selectedSize]*(length-i/2);
  }
  generateFood();
}

//Játékmenet
void runGame(byte direction) {

  oled.clearDisplay();

  if (score >= SCORE_TO_WIN) {
    gameState = GAME_WIN;
  }

  if (!killSnake()) {

    drawSnake();
    drawFood();

    if (hitFood()) {
      eatFood();
    }

    for (int i = length - 1; i >= 1; i--) {
      snakeCoordinates[i * 2] = snakeCoordinates[(i-1) * 2];
      snakeCoordinates[i * 2 + 1] = snakeCoordinates[(i-1) * 2 + 1];
    }

    switch (direction) {
      case LEFT:
        snakeCoordinates[0] -= size[selectedSize];
        break;
      case RIGHT:
        snakeCoordinates[0] += size[selectedSize];
        break;
      case UP:
        snakeCoordinates[1] -= size[selectedSize];
        break;
      case DOWN:
        snakeCoordinates[1] += size[selectedSize];
        break;
    }
  } else {
    gameState = GAME_OVER;
  }

  oled.display();
  delay(velocity[selectedVelocity]);
}

//Játék vége
void overGame() {

  drawGameOver();

  if (digitalRead(CENTER) == LOW) {
    state = 0;
    first = true;

    setupGame();

    lcd.clear();
    lcd.print("WELCOME TO SNAKE");
  }
}

//Játék vége kirajzolása
void drawGameOver() {
  oled.clearDisplay();
  oled.setTextSize(2.5);
  oled.setTextColor(WHITE);
  oled.setCursor(10, 15);
  oled.println("GAME OVER");
  oled.setTextSize(1);
  oled.setCursor(1, 45);
  oled.println("Press the joystick to");
  oled.setCursor(8, 55);
  oled.println("return to the menu!");
  oled.display();
}

//Játék vége győzelemmel
void winGame() {

  drawWinGame();

  if (digitalRead(CENTER) == LOW) {
    state = 0;
    setupGame();

    lcd.clear();
    lcd.print("WELCOME TO SNAKE");
  }
}

//Győzelem kirajzolása
void drawWinGame() {
  oled.clearDisplay();
  oled.setTextSize(2);
  oled.setTextColor(WHITE);
  oled.setCursor(28, 15);
  oled.println("WINNER");
  oled.setTextSize(1);
  oled.setCursor(1, 45);
  oled.println("Press the joystick to");
  oled.setCursor(8, 55);
  oled.println("return to the menu!");
  oled.display();
}

//Irány beállítása
void setDirection(int x, int y) {
  if (x > 612 && direction != RIGHT) {
    direction = LEFT;
  } else if (x < 412 && direction != LEFT) {
    direction = RIGHT;
  } else if (y > 612 && direction != DOWN) {
    direction = UP;
  } else if (y < 412 && direction != UP) {
    direction = DOWN;
  }
}

//Kígyó kirajzolása
void drawSnake() {
  for (int i = 0; i < length; i++) {
    drawSnakePixel(snakeCoordinates[i * 2], snakeCoordinates[i * 2 + 1]);
  }
}

//Kigyó egy pixelének kirajzolása
void drawSnakePixel(byte x, byte y) {
  for (int i = 0; i < size[selectedSize]; i++) {
    for (int j = 0; j < size[selectedSize]; j++) {
      oled.drawPixel(x + j, y + i, 1);
    }
  }
}

//Kígyó megölése
bool killSnake() {
  
  if ((getSnakeHeadX() <= 0) || (getSnakeHeadX() >= (SCREEN_WIDTH - size[selectedSize])) || (getSnakeHeadY() <= 0) || (getSnakeHeadY() >= (SCREEN_HEIGHT - size[selectedSize]))) {
    return true;
  }

  for (int i = 1; i < length; i++) {
    if (getSnakeHeadX() == snakeCoordinates[i * 2] && getSnakeHeadY() == snakeCoordinates[i * 2 + 1]) {
      return true;
    }
  }
  return false;
}

//Snake fejének lekérdezése (könnyebb átláthatóság végett)
byte getSnakeHeadX() {
  return snakeCoordinates[0];
}

byte getSnakeHeadY() {
  return snakeCoordinates[1];
}

//Food függvények

//Food generálása
void generateFood() {
  byte row, column, x, y;

  bool out = true;

  do {

    row = random(0, (SCREEN_WIDTH / size[selectedSize]) - size[selectedSize]);
    column = random(0, (SCREEN_HEIGHT / size[selectedSize]) - size[selectedSize]);

    x = row * size[selectedSize];
    y = column * size[selectedSize];

    for (int i = 0; i < length; i++) {
      if (x == snakeCoordinates[i * 2] && y == snakeCoordinates[i * 2 + 1]) {
        out = false;
      }
    }
  } while (!out);

  foodCoordinates[0] = x;
  foodCoordinates[1] = y;
}

//Food kirajzolása
void drawFood() {
  drawSnakePixel(foodCoordinates[0], foodCoordinates[1]);
}

//Food megevése
void eatFood() {
  score++;
  length++;
  snakeCoordinates = (byte*)realloc(snakeCoordinates, (length * 2) * sizeof(byte));
  lcd.clear();
  lcd.print("SCORE: ");
  lcd.print(score);
  generateFood();
}

//Fooddal való érintkezés
bool hitFood() {
  if (getSnakeHeadX() == foodCoordinates[0] && getSnakeHeadY() == foodCoordinates[1]) {
    tone(BUZZER, 170, 100);
    return true;
  }
  return false;
}

//--Menüpont függvények--//

//Menüpont kiválasztása
void selectMenuOption() {

  if (first) {
    delay(200);
    first = false;
  }

  int vertical = analogRead(VERTICAL);

  if (vertical < 412) {
    if (option < (optionsCount - 1)) {
      option++;
    } else {
      option = 0;
    }
    delay(250);
  } else if (vertical > 612) {
    if (option > 0) {
      option--;
    } else {
      option = (optionsCount - 1);
    }
    delay(250);
  }

  if (digitalRead(CENTER) == LOW) {
    first = true;
    state = option + 1;
  }
}

//Menü kirajzolása
void drawMenu() {
  oled.clearDisplay();
  oled.setTextColor(WHITE);
  oled.setTextSize(1);
  oled.setCursor(15, 5);
  oled.println("SNAKE Remastered");
  oled.setTextSize(1);
  
  for (int i = 0; i < optionsCount; i++) {
    if (option == i) {
      drawHoveredMenuOption(options[i], optionsStartInHover[i], 25 + (i * 12));
    } else {
      drawMenuOption(options[i], optionsStartInHover[i], 25 + (i * 12));
    }
  }
  oled.display();
}

//Menüpont kirajzolása (alap, kijelölt)
void drawMenuOption(char* option, byte cursorX, byte cursorY) {
  oled.setTextColor(WHITE);
  oled.setCursor(cursorX, cursorY);
  oled.println(option);
}

void drawHoveredMenuOption(char* option, byte cursorX, byte cursorY) {
  for (int i = 0; i < SCREEN_WIDTH/2; i++) {
    for (int j = 0; j < 10; j++) {
      oled.drawPixel(SCREEN_WIDTH/4 + i, cursorY - 1 + j, WHITE);
    }
  }
  oled.setTextColor(BLACK);
  oled.setCursor(cursorX, cursorY);
  oled.println(option);
}

//Beállítások

//Beállítások kirajzolása
void drawSettings() {
  oled.clearDisplay();
  oled.setTextSize(1);
  oled.setTextColor(WHITE);
  oled.setCursor(40, 5);
  oled.println("Settings");
  for (int i = 0; i < settingsCount; i++) {
    if (selectedSetting == i) {
      drawHoveredSetting(i, 5, 20 + (i * 10));
    } else {
      drawSetting(i, 5, 20 + (i * 10));
    }
  }
  oled.setTextColor(WHITE);
  oled.setCursor(5, 43);
  oled.println("Press the middle to");
  oled.setCursor(45, 53);
  oled.println("return");
  oled.display();
}

//Beállítás kiválasztása
void selectSetting() {

  if (first) {
    delay(200);
    first = false;
  }

  int vertical = analogRead(VERTICAL);

  if (vertical < 412) {
    if (selectedSetting < (settingsCount - 1)) {
      selectedSetting++;
    } else {
      selectedSetting = 0;
    }
    delay(250);
  } else if (vertical > 612) {
    if (selectedSetting > 0) {
      selectedSetting--;
    } else {
      selectedSetting = (settingsCount - 1);
    }
    delay(250);
  }

  int horizontal = analogRead(HORIZONTAL);

  if (horizontal < 412) {
    if (selectedSetting == 0) {
      if (selectedSize < (sizeCount - 1)) {
        selectedSize++;
      } else {
        selectedSize = 0;
      }
    } else {
      if (selectedVelocity < (velocityCount - 1)) {
        selectedVelocity++;
      } else {
        selectedVelocity = 0;
      }
    }
    delay(250);
  } else if (horizontal > 612) {
    if (selectedSetting == 0) {
      if (selectedSize > 0) {
        selectedSize--;
      } else {
        selectedSize = (sizeCount - 1);
      }
    } else {
      if (selectedVelocity > 0) {
        selectedVelocity--;
      } else {
        selectedVelocity = (velocityCount - 1);
      }      
    }
    delay(250);
  }

  if (digitalRead(CENTER) == LOW) {
    setupGame();
    state = 0;
    first = true;
  }
}

//Beállítás kirajzolása (alap, kijelölt)
void drawSetting(byte setting, byte cursorX, byte cursorY) {
  oled.setTextColor(WHITE);
  oled.setCursor(cursorX, cursorY);
  oled.println(settings[setting]);

  oled.setCursor(cursorX + 80, cursorY);

  if (setting == 0) {
    oled.println(size[selectedSize]);
  } else {
    oled.println(velocity[selectedVelocity]);
  }
}

void drawHoveredSetting(byte setting, byte cursorX, byte cursorY) {

  for (int i = 0; i < SCREEN_WIDTH; i++) {
    for (int j = 0; j < 10; j++) {
      oled.drawPixel(i, cursorY - 1 + j, WHITE);
    }
  }

  oled.setTextColor(BLACK);
  oled.setCursor(cursorX, cursorY);
  oled.println(settings[setting]);

  oled.setCursor(cursorX + 80, cursorY);

  if (setting == 0) {
    oled.println(size[selectedSize]);
  } else {
    oled.println(velocity[selectedVelocity]);
  }
}

//Információ

//Információ kirajzolása
void drawInformation() {
  oled.clearDisplay();
  oled.setTextSize(1);
  oled.setTextColor(WHITE);
  oled.setCursor(30, 5);
  oled.println("Created by:");
  oled.setCursor(30, 15);
  oled.println("Balazs Nagy");
  oled.setCursor(40, 25);
  oled.println("XM6PZH");
  oled.setCursor(5, 43);
  oled.println("Press the middle to");
  oled.setCursor(45, 53);
  oled.println("return");
  oled.display();
}

//Visszatérés az információból a menübe
void returnFromInformation() {

  if (first) {
    delay(200);
    first = false;
  }

  if (digitalRead(CENTER) == LOW) {
    state = 0;
    first = true;
  }
}