#include <InputDebounce.h>
#include "game_defs.h"
#include "Ssd1306Screen.h"

//#define USE_INPUT_DEBOUNCE_LIB

// Uncomment to snake change direction randomly. Direction inputs are also deactived
// #define AUTO_MOVE

#define UP_PIN 7
#define DOWN_PIN 10
#define LEFT_PIN 9
#define RIGHT_PIN 6
#define ACTION_PIN 8

typedef struct {
  Position head;
  Position tail;
} Snake;

Direction moveField[FIELD_HEIGHT][FIELD_WIDTH];
Position fruitPosition;
Snake snake;
Direction snakeDirection;
GameState gameState;
uint16_t snakeVelocity;
uint8_t fruitCounter;
uint8_t level;
uint16_t score;
uint64_t lastMoveTime;

Screen* screen;

bool upPressed;
bool downPressed;
bool leftPressed;
bool rightPressed;
bool actionPressed;

#ifdef USE_INPUT_DEBOUNCE_LIB
static InputDebounce upPin;
static InputDebounce downPin;
static InputDebounce leftPin;
static InputDebounce rightPin;
static InputDebounce actoinPin;
#endif

void initNewGame();
void paintGame();
bool checkCollision();
GameState moveSnake();
Position nextHeadPosition();
Position createFruitPosition();

#ifdef USE_INPUT_DEBOUNCE_LIB
void buttonPressed(uint8_t pinNumber);
void buttonReleased(uint8_t pinNumber);
#endif

void setup() {
  Serial.begin(9600);

#ifdef USE_INPUT_DEBOUNCE_LIB
  upPin.registerCallbacks(buttonPressed, buttonReleased);
  downPin.registerCallbacks(buttonPressed, buttonReleased);
  leftPin.registerCallbacks(buttonPressed, buttonReleased);
  rightPin.registerCallbacks(buttonPressed, buttonReleased);
  actoinPin.registerCallbacks(buttonPressed, buttonReleased);

  upPin.setup(UP_PIN);
  downPin.setup(DOWN_PIN);
  leftPin.setup(LEFT_PIN);
  rightPin.setup(RIGHT_PIN);
  actoinPin.setup(ACTION_PIN);
#else
  pinMode(UP_PIN, INPUT_PULLUP);
  pinMode(DOWN_PIN, INPUT_PULLUP);
  pinMode(LEFT_PIN, INPUT_PULLUP);
  pinMode(RIGHT_PIN, INPUT_PULLUP);
  pinMode(ACTION_PIN, INPUT_PULLUP);
#endif

  screen = new Ssd1306Screen();
  screen->begin();

  initNewGame();
}

void loop() {
  upPressed = false;
  downPressed = false;
  leftPressed = false;
  rightPressed = false;
  actionPressed = false;

#ifdef USE_INPUT_DEBOUNCE_LIB
  unsigned long now = millis();
  upPin.process(now);
  downPin.process(now);
  leftPin.process(now);
  rightPin.process(now);
  actoinPin.process(now);
#else
  if(!digitalRead(UP_PIN)) {
    upPressed = true;
  }
  if(!digitalRead(DOWN_PIN)) {
    downPressed = true;
  }
  if(!digitalRead(LEFT_PIN)) {
    leftPressed = true;
  }
  if(!digitalRead(RIGHT_PIN)) {
    rightPressed = true;
  }
  if(!digitalRead(ACTION_PIN)) {
    actionPressed = true;
  }
#endif

  moveSnake();
  
  screen->show();
}

void initNewGame() {
  lastMoveTime = millis();
  gameState = RUNNING;
  fruitCounter = 0;
  snakeDirection = RIGHT;
  snakeVelocity = 1000;
  level = 1;
  score = 0;

  screen->clear();

  for(int row = 0; row < FIELD_HEIGHT; row++) {
    for(int column = 0; column < FIELD_WIDTH; column++) {
      moveField[row][column] = NONE;
    }
  }

  snake = (Snake) {
    (Position) {5, 3},
    (Position) {5, 1}
  };

  moveField[5][1] = RIGHT;
  moveField[5][2] = RIGHT;
  moveField[5][3] = RIGHT;

  for(int row = 0; row < FIELD_HEIGHT; row++) {
    for(int column = 0; column < FIELD_WIDTH; column++) {
      if(moveField[row][column] != NONE) {
        screen->drawCell(row, column);
      }
    }
  }

  fruitPosition = createFruitPosition();
  screen->drawFruit(fruitPosition.row, fruitPosition.column);
  screen->drawGameFrame();
  screen->drawPlacarNames();
  screen->drawPlacarValues(score, level);
}

GameState moveSnake() {
  if(upPressed) { snakeDirection = UP; }
  if(downPressed) { snakeDirection = DOWN; }
  if(leftPressed) { snakeDirection = LEFT; }
  if(rightPressed) { snakeDirection = RIGHT; }

  uint64_t currentMoveTime = millis();
  if(currentMoveTime - lastMoveTime < snakeVelocity) return;
  lastMoveTime = currentMoveTime;

#ifdef AUTO_MOVE
    if(random(10) < 4) {
      if(snakeDirection == RIGHT || snakeDirection == LEFT) {
        snakeDirection = random(2) ? UP : DOWN;
      }
      if(snakeDirection == UP || snakeDirection == DOWN) {
        snakeDirection = random(2) ? LEFT : RIGHT;
      }
    }
#endif

  Position targetHeadPosition = nextHeadPosition();

  if(checkCollision(targetHeadPosition.row, targetHeadPosition.column)) {
#ifndef AUTO_MOVE
    return;
#else
    Direction directions[] = {DOWN, UP, LEFT, RIGHT};
    int retries = 100;
    while(retries >= 0) {
      snakeDirection = directions[random(4)];
      targetHeadPosition = nextHeadPosition();
      if(!checkCollision(targetHeadPosition.row, targetHeadPosition.column)) break;
      retries--;
    }
#endif
  }

  moveField[snake.head.row][snake.head.column] = snakeDirection;
  snake.head = targetHeadPosition;
  screen->drawCell(snake.head.row, snake.head.column);

  if(snake.head.row == fruitPosition.row && snake.head.column == fruitPosition.column) {
    fruitCounter++;
    score += fruitCounter;
    if(fruitCounter < 197) {
      fruitPosition = createFruitPosition();
      screen->drawFruit(fruitPosition.row, fruitPosition.column);
    }

    if(level < 30 && fruitCounter % 6 == 1) {
      snakeVelocity -= 30;
      level++;
    }

    screen->drawPlacarValues(score, level);

    if(fruitCounter == 197) {
      return GAME_OVER;
    }
    return RUNNING;
  }

  screen->clearCell(snake.tail.row, snake.tail.column);
  Direction tailDirection = moveField[snake.tail.row][snake.tail.column];
  moveField[snake.tail.row][snake.tail.column] = NONE;

  if(tailDirection == RIGHT) {
    snake.tail.column++;
  } else if(tailDirection == LEFT) {
    snake.tail.column--;
  } else if(tailDirection == DOWN) {
    snake.tail.row++;
  } else if(tailDirection == UP) {
    snake.tail.row--;
  }

  return RUNNING;
}

Position nextHeadPosition() {
  Position nextPosition = snake.head;
  if(snakeDirection == RIGHT) {
    nextPosition.column++;
  } else if(snakeDirection == LEFT) {
    nextPosition.column--;
  } else if(snakeDirection == DOWN) {
    nextPosition.row++;
  } else if(snakeDirection == UP) {
    nextPosition.row--;
  }
  return nextPosition;
}

bool checkCollision(int8_t row, int8_t column) {
  return row < 0
    || row >= FIELD_HEIGHT
    || column < 0
    || column >= FIELD_WIDTH
    || moveField[row][column] != NONE;
}

Position createFruitPosition() {
  randomSeed(millis() + snake.head.row + snake.head.column + analogRead(0) + analogRead(1));
  uint8_t columnOffset = random(FIELD_WIDTH);
  for(uint8_t i = 0; i < FIELD_WIDTH; i++) {
    uint8_t rowOffset = random(FIELD_HEIGHT);
    for(uint8_t j = 0; j < FIELD_HEIGHT; j++) {
      uint8_t column = (columnOffset + i) % FIELD_WIDTH;
      uint8_t row = (rowOffset + j) % FIELD_HEIGHT;
      if(moveField[row][column] == NONE) {
        return (Position) {row, column};
      }
    }
  }
}

#ifdef USE_INPUT_DEBOUNCE_LIB
void buttonPressed(uint8_t pinNumber) {
  if(pinNumber == UP_PIN) {
    upPressed = true;
  } else if(pinNumber == DOWN_PIN) {
    downPressed = true;
  } else if(pinNumber == LEFT_PIN) {
    leftPressed = true;
  } else if(pinNumber == RIGHT_PIN) {
    rightPressed = true;
  } else if(pinNumber == ACTION_PIN) {
    actionPressed = true;
  }
}

void buttonReleased(uint8_t pinNumber) {
}
#endif