#include "snake.h"


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

  /* command argument handling */

  if (argc > 1) {
    if (argc > 2) {
      printf("Too many parameters\n");
      goto help_info;
    }

    if (!strcmp(args[1], "-h") || !strcmp(args[1], "--help")) {

      printf("This is a small TUI snake game.\n\n");
      printf("Controls: Arrow keys/WASD to control the snake.\n");
      printf("          P/Enter to pause.\n");
      printf("          Q to quit.\n");

      return 0;
    }

    printf("Argument \"%s\" not recognized\n", args[1]);

  help_info:
    printf("Try \"-h\" or \"--help\" for more information\n");
    return 1;
  }

  
  /* game settings */

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

  score leader_board[SCORE_NUMBER] = {};
  uint16_t score = 0;

  /* Handling Files to store settings & highscores */

  char *env;
  char *folder_path;

  if ((env = getenv("XDG_DATA_HOME")) == NULL) {
    if ((env = getenv("HOME")) == NULL) {
      printf("are you ok?\n");
      return 1;
    }
    
    uint8_t error = 0;

    char local[] = "/.local";
    char share[] = "/share";

    folder_path = malloc(strlen(env) + strlen(local) + 
      strlen(share) + strlen(SETTINGS_FOLDER) + 2);

    sprintf(folder_path, "%s%s", env, local);
    error += make_folder(folder_path);

    strcat(folder_path, share);
    error += make_folder(folder_path);

    if (error <= 1) {
      printf("Failed to create Folder");
      return 1;
    } 
  } else {
    folder_path = malloc(strlen(env) + strlen(SETTINGS_FOLDER) + 2);
    sprintf(folder_path, "%s", env);
  }

  sprintf(folder_path + strlen(folder_path), "/%s", SETTINGS_FOLDER);
  if (!make_folder(folder_path)) {
    printf("Failed to create Folder");
    return 1;
  }

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


  {
    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);

  free(folder_path);

  {
    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;


  /* Animation at the start */
  if (!start_animation())
    goto end_of_program;


  /* Menu */

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

  main_menu selected_menu = NEW_GAME;
  settings selected_setting = HEIGHT_S;

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

menu_start:

  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_KEY || pressed_key == ESCAPE)
      goto end_of_program;


    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;
          break;
        case SETTINGS:
          setting = SETTINGS;
          break;
        case HIGHSCORE:
          setting = HIGHSCORE;
          break;
        default:
          goto end_of_program; 
          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);
    
          score = 0;
          change_menu = true;

        } else {
          draw_highscore (-1, leader_board);    
        }
      }

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

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

  nanosleep(&mili100, NULL);
  }

  menu = true;

  /* initialisation of the game */

  snake my_snake;
  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_state **board = malloc(game_size.y * sizeof(board_state*));
  board_state *values = calloc(game_size.y * game_size.x, sizeof(board_state));

  if(board == NULL || values == NULL)
    goto end_of_program;

  {
    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;

  bool run = true;

  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_KEY || 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_KEY || 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)
            goto end_free;
          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: 
            goto end_free;
            break;
          case 2:
            setting = NEW_GAME;
            draw_board(game_size, board, current_size, false, 2, score);
            goto end_free;
            break;
          default:
            break;
        }

      }
    }
    nanosleep(&milispeed, NULL);
  }

end_free:

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

  if (setting == NEW_GAME)
    goto menu_start;

end_of_program:

  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);
  
  return 0;
}
