#include "snake.h"


int main(int argc, char *args[]) {

  /* command argument handling */
  if (!parse_parameters(argc, args))
    return 0;

  printf("\033[?1049h");
  
  /* game settings */

  speed game_speed = AVERAGE;
  selected game_size = {WIDTH, HEIGHT};

  score leader_board[SCORE_NUMBER] = {};

  /* Handling Files to store settings & highscores */


  /* Making sure all the folders on the path exist */

  char folder_path[PATH_MAX];
  
  {
    char xdg_path[PATH_MAX];

    if (!create_xdg_folder(xdg_path))
      return 1;

    if(!create_folder(SETTINGS_FOLDER ,xdg_path, folder_path))
      return 1;
  }
  
  char *settings_path = malloc(strlen(folder_path) + strlen(SETTINGS_FILE) + 2); 
  sprintf(settings_path, "%s/%s", folder_path, SETTINGS_FILE);


  /* Reading the files */

  {
    FILE* setting_file = fopen(settings_path, "r");

    if (setting_file != NULL) {
      
      char setting_input[STRING_LEN]; 

      fgets(setting_input, sizeof(setting_input), setting_file);
      game_speed = string_to_num(setting_input, 0, SPEED_L - 1, game_speed);

      fgets(setting_input, sizeof(setting_input), setting_file);
      game_size.x = string_to_num(setting_input, MIN_SIZE, MAX_SIZE, game_size.x);

      fgets(setting_input, sizeof(setting_input), setting_file);
      game_size.y = string_to_num(setting_input, MIN_SIZE, MAX_SIZE, game_size.y);

      fclose(setting_file);
    }
  }

  char *score_path = malloc(strlen(folder_path) + strlen(SCORE_FILE) + 2);
  sprintf(score_path, "%s/%s", folder_path, SCORE_FILE);


  {
    FILE* score_file = fopen(score_path, "r");

    if (score_file != NULL) {

      char score_input[STRING_LEN];

      uint8_t i;
      for (i = 0; i < SCORE_NUMBER; i++) {
        fgets(score_input, sizeof(score_input), score_file);
        if (score_input[0] != '\0') {
          uint8_t j;
          for(j = 0; score_input[j] != '\0'; j++) {
            if (score_input[j] == '\n' || score_input[j] == ' ' || score_input[j] == '\t') {
              score_input[j] = '\0';
              break;
            }
          }
          score_input[SCORE_LEN - 1] = '\0';
          strncpy(leader_board[i].name, score_input, SCORE_LEN);
        }

        fgets(score_input, sizeof(score_input), score_file);
        leader_board[i].points = string_to_num(score_input, 0, UINT16_MAX, leader_board[i].points);
      }
      fclose(score_file);
    }
  }

  /* Setting up terminal with termios */

  struct termios old, new;

  tcgetattr(STDIN_FILENO, &old);

  new = old;

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

  tcsetattr(STDIN_FILENO, TCSANOW, &new);

  srand(time(NULL));

  printf("\033[?25l");

  struct winsize init_size, current_size;
  ioctl(STDOUT_FILENO, TIOCGWINSZ, &init_size);
  current_size = init_size;
  bool too_small = false;

  bool gameloop = true;

  /* Animation at the start */
  if (!start_animation())
    gameloop = false;


  /* Menu */

  bool menu = true;
  main_menu setting = MAIN_MENU;
  bool change_menu = true;

  main_menu selected_menu = NEW_GAME;
  settings selected_setting = HEIGHT_S;

  uint16_t score = 0;
  
  snake my_snake;
  my_snake.position = NULL;

  board_state **board = NULL;
  board_state *values = NULL;


  struct timespec mili100 = {0, 1000000 * 100};

  while(gameloop) {

    bool play_game = false;

    while (menu) {

      direction pressed_key = DIRECTIONS;

      char string[STRING_LEN] = {};
      ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);


      ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);

      if (current_size.ws_col != init_size.ws_col ||
          current_size.ws_row != init_size.ws_row) {
        init_size = current_size;
        change_menu = true;
      }


      if (length_string > 0) {
        string[length_string] = '\0';

        pressed_key = read_direction(string, false);
      }

      if (pressed_key == Q_BUTTON || pressed_key == ESCAPE) {
        if (setting != MAIN_MENU) {
          setting = MAIN_MENU;
          change_menu = true;
        } else {
          gameloop = false;
          break;
        }
      }


      if (setting == MAIN_MENU) {
        
        if (!too_small) {
          switch (pressed_key) {
            case UP:
              selected_menu = (selected_menu + (MAIN_MENU - 1)) % MAIN_MENU;
              change_menu = true;
              break;
            case DOWN:
              selected_menu = (selected_menu + 1) % MAIN_MENU;
              change_menu = true;
              break;
            default:
              break;
          }
        }

        if (change_menu) {
          too_small = draw_menu(selected_menu, init_size);
          change_menu = false;
        }

        if (pressed_key == ENTER && !too_small) {
          switch (selected_menu) {
          case NEW_GAME:
            menu = false;
            play_game = true;
            break;
          case SETTINGS:
            setting = SETTINGS;
            break;
          case HIGHSCORE:
            setting = HIGHSCORE;
            break;
          default:
            menu = false;
            gameloop = false; 
            break;
          }
          change_menu = true;
        }

      } else if (setting == SETTINGS) {

        /* Settings Menu */

        if (change_menu) {
          
          draw_settings(selected_setting, game_speed, game_size, init_size);

          change_menu = false;
        }


        if (pressed_key < DIRECTIONS) {

          int8_t setting_change = 0;

          switch (pressed_key) {
            case UP:
              selected_setting = (selected_setting + (SETTINGS_MENU - 1)) % SETTINGS_MENU;
              break;
            case DOWN:
              selected_setting = (selected_setting + 1) % SETTINGS_MENU;
              break;
            case LEFT:
              setting_change = 1;
              break;
            case RIGHT:
              setting_change = -1;
              break;
            default:
              break;
          }
          
          if (setting_change) {
            switch (selected_setting) {
              case SPEED_S:
                if (
                  game_speed + setting_change <= SPEED_L - 1 &&
                  ((int8_t) game_speed) + setting_change >= 0 
                ) {
                  game_speed += setting_change;
                }
                break;
              case WIDTH_S:
                if (
                  game_size.x - setting_change >= MIN_SIZE &&
                  game_size.x - setting_change <= MAX_SIZE
                ) {
                  game_size.x -= setting_change;
                }
                break;
              case HEIGHT_S:
                if (
                  game_size.y - setting_change >= MIN_SIZE &&
                  game_size.y - setting_change <= MAX_SIZE
                ) {
                  game_size.y -= setting_change;
                }
                break;
              default:
                break;
            }
          }

          change_menu = true;

        }

        if (pressed_key == ENTER) {
          switch (selected_setting) {
            case BACK_S:
              setting = MAIN_MENU;
              break;
            default:
              break;
          }
          change_menu = true;
        }
      } else if (setting == HIGHSCORE) {
        
        if(change_menu) {
          uint8_t i;

          for (i = 0; i < SCORE_NUMBER; i++) {
            if (score > leader_board[i].points)
              break;
          }

          if (i < SCORE_NUMBER) {

            uint8_t new_score = i;

            for (i = SCORE_NUMBER - 1; i > new_score; i--) {
              leader_board[i] = leader_board[i -1]; 
            }

            leader_board[new_score].points = score;
            leader_board[new_score].name[0] = '\0';

            draw_highscore (new_score, leader_board, init_size);
      
            score = 0;
            change_menu = true;

          } else {
            if (change_menu) {
              draw_highscore (-1, leader_board, init_size);
              change_menu = false;
            }
          }
        }

        if(pressed_key == ENTER) {
          setting = MAIN_MENU;
          change_menu = true;
        }
      } else if(setting == NEW_GAME) {

        if (change_menu) {
          draw_board(game_size, board, init_size, true, 2, score);
          change_menu = false;
        }

        if(pressed_key == ENTER) {
          setting = HIGHSCORE;
          change_menu = true;

          free(my_snake.position);
          free(board);
          free(values);
          my_snake.position = NULL;
          board = NULL;
          values = NULL;
        }
      }

      nanosleep(&mili100, NULL);
    }

    menu = true;

    /* initialisation of the game */
  
    if(play_game) {

      bool run = true;

      my_snake.length = STARTING_LENGTH;

      my_snake.position = malloc((my_snake.length + 1) * sizeof(snake_part));

      direction shift = RIGHT;
      direction tmp_direction = shift;

      {
        uint16_t i;

        my_snake.position[0].coords.y = game_size.y >> 1;
        my_snake.position[0].coords.x = game_size.x >> 1;
        my_snake.position[0].directions[0] = shift;
        my_snake.position[0].directions[1] = shift;
        for (i = 1; i <= my_snake.length; i++) {
          my_snake.position[i].coords.y = -1;
          my_snake.position[i].coords.x = -1;
        }
      }

      board = malloc(game_size.y * sizeof(board_state*));
      values = calloc(game_size.y * game_size.x, sizeof(board_state));

      if(board == NULL || values == NULL)
        run = false;  

      {
        uint8_t i;

        for (i = 0; i < game_size.y; i++)
          board[i] = values + i * game_size.x;
      }

      update_field(my_snake, game_size, board, true);

      uint8_t paused = 0;


      if ((too_small = draw_board(game_size, board, current_size, true, 0, score)))
        paused = 1;


      struct timespec milispeed = {0, 1000000 * (SPEED + game_speed * 25)};

      /* start of the actual game loop */

      while (run) {

        /* input handling  */


        direction pressed_key;

        char string[STRING_LEN] = {};
        ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);

        if (length_string > 0) {
          string[length_string] = '\0';

          pressed_key = read_direction(string, true);

          if (!too_small) {
            if (pressed_key == P_BUTTON || pressed_key == ENTER) {
              paused = (paused == 1) ? 0 : 1;
              if (paused)
                draw_board(game_size, board, current_size, true, paused, score);
            } 

            if (!paused && pressed_key < DIRECTIONS)
              tmp_direction = pressed_key;
          }

          if (pressed_key == Q_BUTTON || pressed_key == ESCAPE)
            run = false;
        }

        /* game logic */

        /*
        struct timeval current;
        gettimeofday(&current, NULL);*/

        if (/*time_diff >= SPEED * 1000*/ true) {

          ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);

          if (current_size.ws_col != init_size.ws_col ||
              current_size.ws_row != init_size.ws_row) {
            init_size = current_size;

            paused = 1;
            too_small = draw_board(game_size, board, current_size, true, paused, score);
          }

          if (!paused) {

            if (tmp_direction < DIRECTIONS &&
                ((shift + (DIRECTIONS >> 1)) % DIRECTIONS) != tmp_direction) {
              shift = tmp_direction;
            }

            uint8_t update = update_snake(game_size ,&my_snake, shift);

            uint8_t head_y = my_snake.position[0].coords.y;
            uint8_t head_x = my_snake.position[0].coords.x;

            if (board[head_y][head_x] == FRUIT) {
              my_snake.length++;
              score += 1 + 
                (((MAX_SIZE >> 1) / (game_size.y) + 
                (MAX_SIZE >> 1) / (game_size.x) + 
                (my_snake.length - STARTING_LENGTH)) / (game_speed + 1));
              snake_part *tmp = realloc(my_snake.position, (my_snake.length + 1) * sizeof(snake_part));
              if (tmp == NULL) {
                gameloop = false;
                break;
              }
              my_snake.position = tmp;

              my_snake.position[my_snake.length].coords.y = -1;
              my_snake.position[my_snake.length].coords.x = -1;
            }

            update_field(my_snake, game_size, board, false);

            draw_board(game_size, board, current_size, false, 0, score);

            switch (update) {
              case 0: 
                gameloop = false;
                break;
              case 2:
                setting = NEW_GAME;
                run = false;
                break;
              default:
                break;
            }

          }
        }
        nanosleep(&milispeed, NULL);
      }
    }
  }



  free(my_snake.position);
  free(board);
  free(values);

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

  printf("\033[32m");
  printf("\033[2J\033[H");


  {
    FILE* setting_file = fopen(settings_path, "w");

    fprintf(setting_file, "%d\n%d\n%d", game_speed, game_size.x, game_size.y);
    
    fclose(setting_file);

    FILE* score_file = fopen(score_path, "w");
    uint8_t i;

    for (i = 0; i < SCORE_NUMBER; i++) {
      fprintf(score_file, "%s\n%i\n", leader_board[i].name, leader_board[i].points);
    }

    fclose(score_file);
  }

  free(settings_path);
  free(score_path);

  printf("\033[?1049l");
  
  return 0;
}
