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

#include "game.h"
#include "game_aux.h"
#include "game_ext.h"
#include "game_struct.h"
#include "game_tools.h"

// dummy test
bool test_dummy(void) { return true; }

/*******************************************/
/*                                         */
/*       Tests bibliothèque game           */
/*                                         */
/*******************************************/

bool test_set_piece_shape(void) {
  game g = game_new_empty_ext(1, NB_SHAPES, true);
  bool test = true;
  for (int i = 0; i < NB_SHAPES; i++) {
    game_set_piece_shape(g, 0, i, i);
    if (game_get_piece_shape(g, 0, i) != i) test = false;
  }
  game_delete(g);
  return test;
}

bool test_set_piece_orientation(void) {
  game g = game_new_empty_ext(1, NB_DIRS, true);
  bool test = true;
  for (int i = 0; i < NB_DIRS; i++) {
    game_set_piece_orientation(g, 0, i, i);
    if (game_get_piece_orientation(g, 0, i) != i) test = false;
  }
  game_delete(g);
  return test;
}

bool test_is_connected(void) {
  game g = game_new_empty_ext(2, 3, true);
  bool test_empty = game_is_connected(g);
  game_set_piece_shape(g, 1, 0, ENDPOINT);
  bool test_1pc = game_is_connected(g);
  game_set_piece_shape(g, 0, 0, ENDPOINT);
  game_set_piece_orientation(g, 0, 0, SOUTH);
  bool test_2pc = game_is_connected(g);
  game_set_piece_shape(g, 1, 0, SEGMENT);
  game_set_piece_shape(g, 0, 0, SEGMENT);
  bool test_wrapping = game_is_connected(g);
  game_set_piece_shape(g, 1, 2, ENDPOINT);
  game_set_piece_shape(g, 0, 2, ENDPOINT);
  game_set_piece_orientation(g, 0, 2, SOUTH);
  bool test_disconnected = game_is_connected(g);
  game_delete(g);
  return test_empty && !test_1pc && test_2pc && test_wrapping &&
         !test_disconnected;
}

// Il faut tester tous les cas possibles : les variations avec
// ignore_orientation valant true et false dans les cas où le jeux est le est le
// même, où le jeu est le même, où la seule différence est l'orientation, et où
// il est différent
bool test_equal(void) {
  game g = game_default();
  game g2 = game_default();
  bool test1 = game_equal(g, g, false) &&
               game_equal(g, g, true);  // Le jeu est exactement le même
  game_play_move(g2, 0, 0, 1);
  bool test2 = !game_equal(g, g2, false) &&
               game_equal(g, g2, true);  // Le jeu est le même à l'exception de
                                         // l'orientation d'une pièce
  game g3 = game_new_empty();
  bool test3 = game_equal(g3, g2, false) ||
               game_equal(g3, g2, true);  // Le jeu est différent
  game_delete(g);
  game_delete(g2);

  g = game_new_empty_ext(5, 5, true);
  g2 = game_new_empty_ext(5, 6, true);
  bool test_ext = !game_equal(g3, g, true) && !game_equal(g3, g2, true);
  game_delete(g);
  game_delete(g2);
  game_delete(g3);
  return test1 && test2 && !test3 && test_ext;
}

bool test_shuffle_orientation(void) {
  game g = game_default();
  game g2 = game_copy(g);
  game_shuffle_orientation(g2);
  bool test = !game_equal(g, g2, false) && game_equal(g, g2, true);
  game g3 = game_copy(g2);
  game_shuffle_orientation(g2);
  bool test2 = !game_equal(g3, g2, false) && game_equal(g3, g2, true);
  game_delete(g);
  game_delete(g2);
  game_delete(g3);

  // With new_ext
  shape s[6] = {EMPTY, CORNER, ENDPOINT, CROSS, TEE, SEGMENT};
  direction d[6] = {NORTH, EAST, WEST, SOUTH, SOUTH, NORTH};
  g = game_new_ext(1, 6, s, d, true);
  g2 = game_copy(g);
  game_shuffle_orientation(g2);
  bool test_ext = game_equal(g, g2, true) && !game_equal(g, g2, false);
  game_delete(g);
  game_delete(g2);
  return test && test2 && test_ext;
}

bool test_get_adjacent_square(void) {
  game g = game_new_empty();
  game g2 = game_new_empty_ext(5, 4, true);
  unsigned int inext, jnext;
  // Test si les cases adjacentes sont hors de la grille (wrapping=false)
  bool testf = game_get_ajacent_square(g, 0, 0, NORTH, &inext, &jnext) ||
               game_get_ajacent_square(g, 0, 0, WEST, &inext, &jnext) ||
               game_get_ajacent_square(g, 4, 4, EAST, &inext, &jnext) ||
               game_get_ajacent_square(g, 4, 4, SOUTH, &inext, &jnext);

  // test out of bounds when wrapping is true
  uint last_row = game_nb_rows(g2) - 1, last_col = game_nb_cols(g2) - 1;
  if (!game_get_ajacent_square(g2, 0, 0, NORTH, &inext, &jnext) ||
      !(inext == last_row && jnext == 0)) {
    return false;
  }
  if (!game_get_ajacent_square(g2, 0, 0, WEST, &inext, &jnext) ||
      !(inext == 0 && jnext == last_col)) {
    return false;
  }
  if (!game_get_ajacent_square(g2, last_row, last_col, SOUTH, &inext, &jnext) ||
      !(inext == 0 && jnext == last_col)) {
    return false;
  }
  if (!game_get_ajacent_square(g2, last_row, last_col, EAST, &inext, &jnext) ||
      !(inext == last_row && jnext == 0)) {
    return false;
  }

  // Test inside the grid
  for (unsigned int i = 0; i < NB_DIRS; i++) {
    game_get_ajacent_square(g, 2, 2, i, &inext, &jnext);
    if (i == NORTH && (inext != 1 || jnext != 2)) {
      return false;
    }
    if (i == SOUTH && (inext != 3 || jnext != 2)) {
      return false;
    }
    if (i == EAST && (inext != 2 || jnext != 3)) {
      return false;
    }
    if (i == WEST && (inext != 2 || jnext != 1)) {
      return false;
    }
  }
  game_delete(g);
  game_delete(g2);
  return !testf;
}

bool test_check_edge(void) {
  game g = game_new_empty_ext(4, 3, true);
  game_set_piece_shape(g, 0, game_nb_cols(g) - 1, CORNER);
  game_set_piece_shape(g, 0, 0, TEE);
  bool test1 = game_check_edge(g, 0, 0, WEST) == MATCH;
  bool test2 = game_check_edge(g, 0, 0, SOUTH) == NOEDGE;
  bool test3 = game_check_edge(g, 0, 0, EAST) == MISMATCH;
  game_delete(g);
  return test1 && test2 && test3;
}

bool test_default_solution() {
  game g = game_default_solution();
  game g2 = game_default();
  bool test = game_won(g) && game_equal(g, g2, true);
  game_delete(g);
  game_delete(g2);
  return test;
}

bool test_is_well_paired(void) {
  game g = game_default();
  game g2 = game_default_solution();
  bool test1 = game_is_well_paired(g);
  bool test2 = game_is_well_paired(g2);

  // We check if it works when something's not paired in each direction
  game_set_piece_shape(g2, 4, 4, EMPTY);
  if (game_is_well_paired(g2)) return false;
  game_set_piece_shape(g2, 4, 4, ENDPOINT);

  game_set_piece_shape(g2, 0, 4, EMPTY);
  if (game_is_well_paired(g2)) return false;
  game_set_piece_shape(g2, 0, 4, ENDPOINT);

  game_set_piece_shape(g2, 0, 2, EMPTY);
  if (game_is_well_paired(g2)) return false;
  game_set_piece_shape(g2, 0, 2, ENDPOINT);

  game_set_piece_shape(g2, 4, 0, EMPTY);
  if (game_is_well_paired(g2)) return false;
  game_set_piece_shape(g2, 4, 0, ENDPOINT);
  game_delete(g);
  game_delete(g2);

  // Test with the extension
  shape s[6] = {CORNER, CORNER, SEGMENT, SEGMENT, CORNER, CORNER};
  direction d[6] = {WEST, NORTH, WEST, EAST, SOUTH, EAST};
  g = game_new_ext(3, 2, s, d, true);
  bool test_ext = game_is_well_paired(g);
  game_delete(g);
  return !test1 && test2 && test_ext;
}

bool test_has_half_edge() {
  game g = game_new_empty_ext(1, NB_SHAPES, true);
  for (int s = 0; s < NB_SHAPES; s++) {
    game_set_piece_shape(g, 0, s, s);
    for (int dir = 0; dir < NB_DIRS; dir++) {
      switch (s) {
        case EMPTY:
          if (game_has_half_edge(g, 0, s, dir)) return false;
          break;
        case ENDPOINT:
          if (dir == NORTH) {
            if (!game_has_half_edge(g, 0, s, dir)) return false;
          } else {
            if (game_has_half_edge(g, 0, s, dir)) return false;
          }
          break;
        case SEGMENT:
          if (dir == NORTH || dir == SOUTH) {
            if (!game_has_half_edge(g, 0, s, dir)) return false;
          } else {
            if (game_has_half_edge(g, 0, s, dir)) return false;
          }
          break;
        case CORNER:
          if (dir == NORTH || dir == EAST) {
            if (!game_has_half_edge(g, 0, s, dir)) return false;
          } else {
            if (game_has_half_edge(g, 0, s, dir)) return false;
          }
          break;
        case TEE:
          if (dir != SOUTH) {
            if (!game_has_half_edge(g, 0, s, dir)) return false;
          } else {
            if (game_has_half_edge(g, 0, s, dir)) return false;
          }
          break;
        case CROSS:
          if (!game_has_half_edge(g, 0, s, dir)) return false;
          break;
        default:
          return false;
          break;
      }
    }
  }
  game_delete(g);
  return true;
}

bool test_undo() {
  game g = game_new_empty_ext(3, 2, true);
  game g_start = game_copy(g);
  game_undo(g);
  bool test_nothing = game_equal(g, g_start, false);
  game_play_move(g, 0, 0, 1);
  game g_c1 = game_copy(g);
  game_play_move(g, 0, 0, 1);
  game_undo(g);
  bool test_c1 = game_equal(g, g_c1, false);
  game_undo(g);
  bool test_start = game_equal(g, g_start, false);
  game_delete(g_c1);
  game_delete(g_start);
  // Verify if history deleted with shuffle
  game_shuffle_orientation(g);
  g_c1 = game_copy(g);
  game_undo(g);
  bool test_shuffle = game_equal(g, g_c1, false);
  game_delete(g_c1);
  // Verify if history deleted with reset
  game_reset_orientation(g);
  g_c1 = game_copy(g);
  game_undo(g);
  bool test_reset = game_equal(g, g_c1, false);
  game_delete(g);
  game_delete(g_c1);
  return test_nothing && test_c1 && test_start && test_shuffle && test_reset;
}

bool test_redo() {
  game g = game_new_empty_ext(3, 2, true);
  game_play_move(g, 0, 0, 1);
  game g_apres1 = game_copy(g);
  // We need to test twice to check if redo works in case undo doesn't, because
  // if that's the case the test can succeed without redo even working
  game_play_move(g, 0, 0, 1);
  game g_apres2 = game_copy(g);
  game_undo(g);
  game_undo(g);
  game_redo(g);
  bool test1 = game_equal(g, g_apres1, false);
  game_redo(g);
  bool test2 = game_equal(g, g_apres2, false);
  game_delete(g_apres1);
  game_delete(g_apres2);
  // Test whether it doesn't redo if we play another move as it should or not
  game_play_move(g, 0, 0, 1);
  game_undo(g);
  game_play_move(g, 0, 0, 3);
  g_apres1 = game_copy(g);
  game_redo(g);
  bool test3 = game_equal(g, g_apres1, false);
  game_delete(g_apres1);
  // Same with shuffle
  game_play_move(g, 0, 0, 1);
  game_undo(g);
  game_shuffle_orientation(g);
  g_apres1 = game_copy(g);
  game_redo(g);
  bool test4 = game_equal(g, g_apres1, false);
  game_delete(g);
  game_delete(g_apres1);
  return test1 && test2 && test3 && test4;
}

bool test_solve() {
  // Pour ces tests, les jeux générés auparavant auront des noms où w représente
  // le wrapping, <x>e (x un nombre) représente le nombre de pièces empty s'il y
  // en a, <x>p représente le nombre de liens en plus (extras), et seront tous
  // shuffled
  game solvables[6] = {game_load("ressources/test_files/game_4x4"),
                       game_load("ressources/test_files/game_4x4_w"),
                       game_load("ressources/test_files/game_4x4_w1e"),
                       game_load("ressources/test_files/game_4x4_w1p"),
                       game_load("ressources/test_files/game_4x4_w1e1p"),
                       game_load("ressources/test_files/game_4x5_w3p")};
  for (int i = 0; i < 6; i++) {
    if (!game_solve(solvables[i])) {
      game_delete(solvables[i]);
      return false;
    }
    shape next_shape = (game_get_piece_shape(solvables[i], 0, 0) + 1) %
                       NB_SHAPES;  // Always change the shape of a piece
    game_set_piece_shape(solvables[i], 0, 0, next_shape);
    if (game_solve(solvables[i])) {
      game_delete(solvables[i]);
      return false;
    }
    game_delete(solvables[i]);
  }
  return true;
}
bool test_nb_solutions() {
  // Pour ces tests, les jeux générés auparavant auront des noms où w représente
  // le wrapping, <x>e (x un nombre) représente le nombre de pièces empty s'il y
  // en a, <x>p représente le nombre de liens en plus (extras), et seront tous
  // shuffled
  game solvables[6] = {game_load("../ressources/test_files/game_4x4"),
                       game_load("../ressources/test_files/game_4x4_w"),
                       game_load("../ressources/test_files/game_4x4_w1e"),
                       game_load("../ressources/test_files/game_4x4_w1p"),
                       game_load("../ressources/test_files/game_4x4_w1e1p"),
                       game_load("../ressources/test_files/game_4x5_w3p")};
  for (int i = 0; i < 6; i++) {
    printf("%d\n", i);
    if ((game_nb_solutions(solvables[i]) != 1 && i != 2 && i != 5) ||
        ((i == 2 || i == 5) && game_nb_solutions(solvables[i]) != 2)) {
      printf("failed %d\n", i);
      game_delete(solvables[i]);
      return false;
    }
    shape next_shape = (game_get_piece_shape(solvables[i], 0, 0) + 1) %
                       NB_SHAPES;  // Always change the shape of a piece
    game_set_piece_shape(solvables[i], 0, 0, next_shape);
    if (game_nb_solutions(solvables[i]) != 0) {
      game_delete(solvables[i]);
      return false;
    }
    game_delete(solvables[i]);
  }
  return true;
}

/***           *** USAGE ***           ***/

void usage(int argc, char *argv[]) {
  fprintf(stderr, "Usage: %s <testname> [<...>]\n", argv[0]);
  exit(EXIT_FAILURE);
}

// main
int main(int argc, char *argv[]) {
  if (argc == 1) usage(argc, argv);

  // tests
  fprintf(stderr, "=> Start test \"%s\"\n", argv[1]);
  bool ok = false;
  if (strcmp("dummy", argv[1]) == 0)
    ok = test_dummy();
  else if (strcmp("set_piece_shape", argv[1]) == 0)
    ok = test_set_piece_shape();
  else if (strcmp("set_piece_orientation", argv[1]) == 0)
    ok = test_set_piece_orientation();
  else if (strcmp("is_connected", argv[1]) == 0)
    ok = test_is_connected();
  else if (strcmp("equal", argv[1]) == 0)
    ok = test_equal();
  else if (strcmp("shuffle_orientation", argv[1]) == 0)
    ok = test_shuffle_orientation();
  else if (strcmp("get_adjacent_square", argv[1]) == 0)
    ok = test_get_adjacent_square();
  else if (strcmp("check_edge", argv[1]) == 0)
    ok = test_check_edge();
  else if (strcmp("is_well_paired", argv[1]) == 0)
    ok = test_is_well_paired();
  else if (strcmp("default_solution", argv[1]) == 0)
    ok = test_default_solution();
  else if (strcmp("undo", argv[1]) == 0)
    ok = test_undo();
  else if (strcmp("redo", argv[1]) == 0)
    ok = test_redo();
  else if (strcmp("has_half_edge", argv[1]) == 0)
    ok = test_has_half_edge();
  else if (strcmp("solve", argv[1]) == 0)
    ok = test_solve();
  else if (strcmp("nb_solutions", argv[1]) == 0)
    ok = test_nb_solutions();
  else {
    fprintf(stderr, "Error: test \"%s\" not found!\n", argv[1]);
    exit(EXIT_FAILURE);
  }

  // print test result
  if (ok) {
    fprintf(stderr, "Test \"%s\" finished: SUCCESS\n", argv[1]);
    return EXIT_SUCCESS;
  } else {
    fprintf(stderr, "Test \"%s\" finished: FAILURE\n", argv[1]);
    return EXIT_FAILURE;
  }
}