#include <FastLED.h>
#include "Snake.h"

#define NUM_LEDS      135
#define LEDS_PER_ROW  15
#define DATA_PIN      6
#define SPEAKER_PIN   8
#define BTN_LEFT      2
#define BTN_RIGHT     4
#define BTN_UP        5
#define BTN_DOWN      3

#define NOTE_G3  196
#define NOTE_C4  262
#define NOTE_C5  523
#define NOTE_E4  330
#define NOTE_G4  392
#define NOTE_G5  784

// Initialize the snake field with x=15, y=9, delay=10 ticks
Snake snakeGame(15, 9, 10);

CRGB leds[NUM_LEDS];

unsigned long timerTone = 0;
unsigned long timerReset = 0;
unsigned long timerRefresh = 0;
unsigned long timerMoveTone = 0;
unsigned long timerSnakeRose = 0;

bool isMoveTone = false;
bool isGameReset = false;
bool isMoveToneStop = false;
bool isSnakeRose = false;
bool isSnakeRoseEnd = false;

int lastSnakeLenght = 0;

uint8_t direction = 0;
uint8_t lastDirection = 0;

void setup() {
  pinMode(BTN_LEFT, INPUT_PULLUP);
  pinMode(BTN_RIGHT, INPUT_PULLUP);
  pinMode(BTN_UP, INPUT_PULLUP);
  pinMode(BTN_DOWN, INPUT_PULLUP);

  snakeGame.setBodyColor(255, 0, 255); // Optionally set the color of the snakeparts
  snakeGame.setFoodColor(0, 60, 125); // Optionally set the color of the food
  snakeGame.setHeadColor(225, 20, 60); // Optionally set the color of the snakeparts
  lastSnakeLenght = snakeGame.getSnakeLenght();

  delay(1000);

  FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
}

void inputEvent() {

  if (digitalRead(BTN_LEFT) == LOW) {
    snakeGame.goLeft(); // Snake will go left on the next move
    isMoveTone = true;
    direction = LEFT;
  }

  if (digitalRead(BTN_RIGHT) == LOW) {
    snakeGame.goRight(); // Snake will go right on the next move
    isMoveTone = true;
    direction = RIGHT;
  }

  if (digitalRead(BTN_UP) == LOW) {
    snakeGame.goUp(); // Snake will go up on the next move
    isMoveTone = true;
    direction = UP;
  }

  if (digitalRead(BTN_DOWN) == LOW) {
    snakeGame.goDown(); // Snake will go down on the next move
    isMoveTone = true; 
    direction = DOWN;
  }

  // Checks whether the snake has changed position
  if (isMoveTone && (direction != lastDirection)) {
    isMoveTone = false;
    isMoveToneStop = true;
    lastDirection = direction;
    tone(SPEAKER_PIN, NOTE_G4);
    timerMoveTone = millis();
  }

  if ((isMoveToneStop) && ((millis() - timerMoveTone) >= 30)) {
    isMoveToneStop = false;
    noTone(SPEAKER_PIN);
  }
}

byte setPixel(byte x, byte y, byte r, byte g, byte b)
{
  byte ledID = NUM_LEDS - (y * LEDS_PER_ROW) - x - 1 ;
  leds[ledID].setRGB(r, g, b);
  return ledID;
}

void changeRGBtoGBR()
{
  for (int whiteLed = 0; whiteLed < NUM_LEDS; whiteLed++)
  {
    leds[whiteLed].setRGB(leds[whiteLed].g, leds[whiteLed].b, leds[whiteLed].r);
  }
}

void clearScreen()
{
  for (int whiteLed = 0; whiteLed < NUM_LEDS; whiteLed++)
  {
    leds[whiteLed].setRGB( 3, 3, 3);
  }
}

void loop()
{
  inputEvent();

  if (!snakeGame.wasGameReset() && (millis() - timerRefresh > 30)) {
    timerRefresh = millis();

    // This needs to be updated every frame
    Snake::pixel* snakeLimbs = snakeGame.getSnakeLimbs();
    Snake::pixel* snakeFood = snakeGame.getFoodPositions();

    clearScreen();

    setPixel(snakeFood[0].posX, 8 - snakeFood[0].posY, snakeFood[0].pixelColor.r, snakeFood[0].pixelColor.g, snakeFood[0].pixelColor.b); // display the food

    for (int i = 0; i < snakeGame.getSnakeLenght(); i++)
    {
      // Display the snake, my setpixel method has x=0, y=0 at the top left, but the library has it at bottom left, so I invert the Y-Axis:
      setPixel(snakeLimbs[i].posX, 8 - snakeLimbs[i].posY, snakeLimbs[i].pixelColor.r, snakeLimbs[i].pixelColor.g, snakeLimbs[i].pixelColor.b);
    }

    if (lastSnakeLenght != snakeGame.getSnakeLenght()) {
      lastSnakeLenght = snakeGame.getSnakeLenght();
      isSnakeRose = true;
      tone(SPEAKER_PIN, NOTE_G4);
      timerSnakeRose = millis();
    }

    FastLED.show();
    snakeGame.tick(); // Main loop for the snake library
  }

  if ((isSnakeRose) && (millis() - timerSnakeRose) >= 30) {
      isSnakeRose = false;
      tone(SPEAKER_PIN, NOTE_G5);
      isSnakeRoseEnd = true;
      timerSnakeRose = millis();
  }

  if ((isSnakeRoseEnd) && (millis() - timerSnakeRose) >= 30) {
      isSnakeRoseEnd = false;
      noTone(SPEAKER_PIN);
  }

  // If the snake bit itself or the wall, flash a little
  if (snakeGame.wasGameReset()) {
      changeRGBtoGBR();
      FastLED.show();
      isGameReset = true;
      tone(SPEAKER_PIN, NOTE_G3);
      timerReset = millis();  
  }

  if ((isGameReset) && (millis() - timerReset) >= 200) {
    isGameReset = false;
    noTone(SPEAKER_PIN);
  }
}