#include <unistd.h>

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

#include <time.h>
#include <sys/time.h>
#include <string.h>
#include <termios.h>

#define RIGHT_CODE 'C'
#define LEFT_CODE 'D'
#define UP_CODE 'A'
#define DOWN_CODE 'B'

#define WIDTH 50
#define HEIGHT 50

#define SPEED 100 /* in ms */

#define MAX_LENGTH 1000
#define STARTING_LENGTH 2

typedef enum {
  UP, DOWN, LEFT, RIGHT
} direction;

typedef enum {
  EMPTY, SNAKE, HEAD, FRUIT
} field_content;

typedef struct selected {
  int16_t x;
  int16_t y;
} selected;

typedef struct snake {
  selected position[MAX_LENGTH];
  uint16_t length;
} snake;


void update_field(snake my_snake, field_content board[HEIGHT][WIDTH], selected fruit);
selected new_fruit(field_content board[HEIGHT][WIDTH]);

void draw_board(field_content board[HEIGHT][WIDTH], bool redraw);
void draw_pause();

direction read_direction (char* string, direction shift);


int main() {

  struct termios old, new;

  tcgetattr(STDIN_FILENO, &old);

  new = old;

  new.c_lflag &= ~(ECHO | ICANON);
  new.c_cc[VMIN] = 0;
  new.c_cc[VTIME] = 1;

  tcsetattr(STDIN_FILENO, TCSANOW, &new);

  srand(time(NULL));


  snake my_snake = {};  
  my_snake.length = STARTING_LENGTH;

  {
    uint16_t i;
    
    my_snake.position[0].x = HEIGHT >> 1;
    my_snake.position[0].y = WIDTH >> 1;
    for (i = 1; i < my_snake.length; i++) {
      my_snake.position[i].y = -1;
      my_snake.position[i].x = -1;
    }
  }

  selected fruit = {-1, -1};

  field_content board[HEIGHT][WIDTH] = {0};

  update_field(my_snake, board, fruit);

  fruit = new_fruit(board);
  update_field(my_snake, board, fruit);


  printf("\033[?25l");
  draw_board(board, false);


  direction shift = RIGHT;


  struct timeval start, current;

  gettimeofday(&start, NULL);


  bool paused = false;
  bool run = true;

  while (run) {

    char string[1000] = {};
    ssize_t n = read(STDIN_FILENO, string, sizeof(string) - 1);
    
    gettimeofday(&current, NULL);

    if (n > 0) {
      string[n] = '\0';
  
      if (!paused)
        shift = read_direction(string, shift);

      if (strstr(string, "q"))
        run = false;

       if (strstr(string, "p")) {
          paused = !paused;
          if (paused)
            draw_pause();
      }
    }

    uint64_t time_diff = (current.tv_sec - start.tv_sec) * 1000000 + (current.tv_usec - start.tv_usec);

    if ((time_diff >= SPEED * 1000) && !paused) {
      
      start = current;

      uint16_t i;
      for (i = my_snake.length; i > 0; i--) {
        my_snake.position[i].y = my_snake.position[i - 1].y;
        my_snake.position[i].x = my_snake.position[i - 1].x;
      }

      switch (shift) {
        case UP:
          my_snake.position[0].y = ((my_snake.position[0].y + HEIGHT - 1) % HEIGHT);
          break;
        case DOWN:
          my_snake.position[0].y = ((my_snake.position[0].y + 1) % HEIGHT);
          break;
        case LEFT:
          my_snake.position[0].x = ((my_snake.position[0].x + WIDTH - 1) % WIDTH);
          break;
        case RIGHT:
          my_snake.position[0].x = ((my_snake.position[0].x + 1) % WIDTH);
          break;
      }
    
      uint8_t head_y = my_snake.position[0].y;
      uint8_t head_x = my_snake.position[0].x;

      for (i = 1; i < my_snake.length; i++) {
        int16_t x = my_snake.position[i].x;
        int16_t y = my_snake.position[i].y;

        if (
          x == head_x && 
          y == head_y
        ) {
          printf("\n\nHAHA LOST!!!\n\n");
          goto end_of_program;
        }
      }

      if (head_y == fruit.y && head_x == fruit.x) {
        my_snake.length++;
        fruit = new_fruit(board);
      }


      update_field(my_snake, board, fruit);
      draw_board(board, true);
    }

  }

end_of_program:

  tcsetattr(STDIN_FILENO, TCSANOW, &old);
  printf("\033[?25h");

  return 0;
}





void update_field(snake my_snake, field_content board[HEIGHT][WIDTH], selected fruit) {
  
  uint8_t i, j;

  for (i = 0; i < HEIGHT; i++) {
    for (j = 0; j < WIDTH; j++) {   
      board[i][j] = EMPTY;
    }
  }

  for (i = 0; i < my_snake.length; i++) {
    int16_t x = my_snake.position[i].x;
    int16_t y = my_snake.position[i].y;
    if (x >= 0 && y >= 0)
      board[y][x] = (i == 0) ? HEAD : SNAKE;
  }
  
  if (fruit.x >= 0 && fruit.y >= 0)
    board[fruit.y][fruit.x] = FRUIT;
}


selected new_fruit(field_content board[HEIGHT][WIDTH]) {
  bool found_space = false;

  uint8_t x;
  uint8_t y;

  while (!found_space) {
    x = rand() % WIDTH;
    y = rand() % HEIGHT;

    if (board[y][x] == EMPTY)
      found_space = true;
  }

  selected fruit = {y, x};

  return fruit;
}


void draw_board (field_content board[HEIGHT][WIDTH], bool redraw) {
  
  uint8_t i, j; 

  if (redraw) {
    for (i = 0; i < HEIGHT + 2; i++)
      printf("\033[A");
    printf("\r");
  }

  printf("+");
  for (i = 0; i < WIDTH; i++)
    printf("--");
  printf("+\n");

  for ( i = 0; i < HEIGHT; i++) {

    printf("|");

    for (j = 0; j < WIDTH; j++) {

      switch (board[i][j]) {
        case EMPTY:
          printf("  ");
          break;
        case SNAKE:
          printf("\033[31m x\033[0m");
          break;
        case HEAD:
          printf("\033[32m X\033[0m");
          break;
        case FRUIT:
          printf("\033[34m O\033[0m");
          break;
      }
    }
    printf("|\n");
  }

    printf("+");
  for (i = 0; i < WIDTH; i++)
    printf("--");
  printf("+\n");
}


void draw_pause() {

  const uint8_t height = 5;
  const uint8_t width = 14;

  char *pause_menu [] = {
    "+------------+",
    "|            |",
    "|   PAUSED   |",
    "|            |",
    "+------------+"
  };

  uint8_t i, j;

  const uint8_t going_up = (HEIGHT + height) >> 1;
  const uint8_t going_right = (WIDTH * 2 - width) >> 1;

  for (i = 0; i < going_up; i++)
    printf("\033[%c", UP_CODE);
  printf("\r");

  for (i = 0; i < height; i++) {
    for (j = 0; j < going_right; j++)
      printf("\033[%c", RIGHT_CODE);

    printf("\033[37m%s\033[0m\033[%c\r", pause_menu[i], DOWN_CODE);
  }

  for (i = 0; i < going_up - height; i++)
    printf("\033[%c", DOWN_CODE);
  printf("\r");

  fflush(stdout);
}


direction read_direction (char* string, direction shift) {

  if (strlen(string) >= 3) {
    if (string[0] == '\033') {
      if (string[1] == '[') {
       
        switch (string[2]) {
          case RIGHT_CODE:
            if (shift != LEFT)
              return RIGHT;
            break;
          case LEFT_CODE:
            if (shift != RIGHT)
              return LEFT;
            break;
          case UP_CODE:
            if (shift != DOWN)
              return UP;
            break;
          case DOWN_CODE:
            if (shift != UP)
              return DOWN;
            break;
        }          
      }
    }
  }

  return shift;
}
