#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_tools.h"

/* ********** TEST Dummy ********** */

bool test_dummy(void) { return true; }

/* ********** TEST game_new ********** */
bool test_new() {
  game g = game_new(NULL, NULL);
  game g2 = game_default();
  game g3 = game_new_empty();
  game g4 = game_new_empty_ext(5, 10, true);
  bool test = game_equal(g, g3, false) || !game_equal(g, g2, false) ||
              !game_equal(g, g4, false);
  game_delete(g);
  game_delete(g2);
  game_delete(g3);
  game_delete(g4);
  return test;
}

/* ********** TEST game_get_piece_shape ********** */

bool test_get_piece_shape(void) {
  game g1 = game_new_empty_ext(5, 10, true);
  for (unsigned int i = 0; i < NB_SHAPES; i++) {
    game_set_piece_shape(g1, 0, 0, i);
    if (game_get_piece_shape(g1, 0, 0) != i) {
      game_delete(g1);
      return false;
    }
  }
  game_delete(g1);
  return true;
}

/* ********** TEST game_copy ********** */
bool test_copy(void) {
  // test with default
  game g1 = game_default();
  game g2 = game_copy(g1);
  game g3 = game_default();
  game_shuffle_orientation(g3);
  bool test = game_equal(g1, g2, false) && !game_equal(g3, g2, false);
  game_delete(g1);
  game_delete(g2);
  game_delete(g3);

  // test with a rectangle
  game g4 = game_new_empty_ext(5, 10, true);
  game g5 = game_copy(g4);
  game g6 = game_new_empty_ext(5, 10, true);
  game_shuffle_orientation(g6);
  bool test2 = game_equal(g4, g5, false) && !game_equal(g6, g5, false);
  game_delete(g4);
  game_delete(g5);
  game_delete(g6);

  return test && test2;
}

/* ********** TEST game_new_empty ********** */

bool test_new_empty(void) {
  game g1 = game_new_empty();
  game g2 = game_new(NULL, NULL);
  game g3 = game_default();
  game g4 = game_new_empty_ext(5, 5, true);
  bool test = game_equal(g1, g2, false) && !game_equal(g1, g3, false) &&
              !game_equal(g1, g4, false);
  game_delete(g1);
  game_delete(g2);
  game_delete(g3);
  game_delete(g4);
  return test;
}

/* ********** TEST game_won ********** */

bool test_won(void) {
  // test only with default else i would need to make a game and it's solution
  game g1 = game_default_solution();
  game g2 = game_new_empty();
  game g3 = game_default();
  game_set_piece_shape(g2, 1, 0, ENDPOINT);
  game_set_piece_shape(g2, 0, 0, ENDPOINT);
  game_set_piece_orientation(g2, 0, 0, SOUTH);
  game_set_piece_shape(g2, 1, 1, ENDPOINT);
  game_set_piece_shape(g2, 0, 1, ENDPOINT);
  game_set_piece_orientation(g2, 0, 1, SOUTH);
  bool test = game_won(g1) && !game_won(g2) && !game_won(g3);
  game_delete(g1);
  game_delete(g2);
  game_delete(g3);
  return test;
}

/* ********** TEST game_default ********** */

bool test_default(void) {
  // test only with default because we test default
  game g1 = game_default();
  game g2 = game_default_solution();
  game g3 = game_new_empty();
  bool test = game_equal(g2, g1, true) && !game_equal(g1, g3, true);
  game_delete(g1);
  game_delete(g2);
  game_delete(g3);
  return test;
}

/* ********** TEST game_play_move ********** */

bool test_play_move(void) {
  game g1 = game_new_empty_ext(5, 10, true);
  for (int i = 0; i < game_nb_rows(g1); i++) {
    for (int j = 0; j < game_nb_cols(g1); j++) {
      direction sens = game_get_piece_orientation(g1, i, j);
      game_play_move(g1, i, j, 1);
      if (sens == 3) {
        sens = -1;
      }
      if (sens + 1 == game_get_piece_orientation(g1, i, j)) {
        sens = game_get_piece_orientation(g1, i, j);
      } else {
        return false;
      }
      game_play_move(g1, i, j, -1);
      if (sens == 0) {
        sens = 4;
      }
      if (sens - 1 != game_get_piece_orientation(g1, i, j)) {
        return false;
      }
    }
  }
  game_delete(g1);
  return true;
}

/* ********** TEST game_reset_orientation ********** */

bool test_reset_orientation(void) {
  game g1 = game_new_empty_ext(5, 10, true);
  game_reset_orientation(g1);
  for (int i = 0; i < game_nb_rows(g1); i++) {
    for (int j = 0; j < game_nb_cols(g1); j++) {
      if (game_get_piece_orientation(g1, i, j) != 0) {
        game_delete(g1);
        return false;
      }
    }
  }
  // Check if it erases the history
  game g = game_default();
  for (int i = 0; i < game_nb_rows(g); i++) {
    game_play_move(g, i % game_nb_rows(g), i % game_nb_cols(g),
                   (i + 1) % NB_DIRS);
  }
  game_reset_orientation(g);
  game copy = game_copy(g);
  game_undo(g);
  bool test = game_equal(g, copy, false);
  game_delete(g);
  game_delete(g1);
  game_delete(copy);
  return test;
}

/* ********** TEST game_get_piece_orientation ********** */

bool test_get_piece_orientation(void) {
  game g1 = game_new_empty_ext(5, 10, true);
  for (unsigned int i = 0; i < NB_DIRS; i++) {
    if (game_get_piece_orientation(g1, 0, 0) != i) {
      game_delete(g1);
      return false;
    }
    game_play_move(g1, 0, 0, 1);
  }
  game_delete(g1);
  return true;
}

/* ********** TEST game_random ********** */

bool test_game_random(void) {
  // test that the game is valid and is won
  game g = game_random(5, 5, 0, 0, 0);
  bool test = game_won(g);

  // test that the number of empty case is correct
  game g1 = game_random(5, 5, 0, 3, 0);
  if (game_equal(g, g1, false)) return false;
  int nb_empty = 0;
  for (int i = 0; i < game_nb_rows(g1); i++) {
    for (int j = 0; j < game_nb_cols(g1); j++) {
      if (game_get_piece_shape(g1, i, j) == EMPTY) {
        nb_empty++;
      }
    }
  }
  game_delete(g1);
  game_delete(g);
  // Test with wrong arguments
  g = game_random(1, 1, true, 0, 0);
  g1 = game_random(1, 2, true, 1, 0);
  if (g != NULL || g1 != NULL) {
    game_delete(g1);
    game_delete(g);
    return false;
  }
  g = game_random(1, 2, true, 0, 3);
  g1 = game_random(1, 2, false, 0, 1);
  if (g1 != NULL || g == NULL) {
    game_delete(g1);
    return false;
  }
  game_delete(g);
  bool test1 = (nb_empty == 3);

  return test && test1;
}

/* ********** TEST game_load ********** */

bool test_game_load(void) {
  // load the default game and compare it
  game g_default = game_default();
  game load_default = game_load("../ressources/test_files/default.txt");
  bool test = game_equal(g_default, load_default, false);

  // load the default solution and compare it
  game g_default_sol = game_default_solution();
  game load_default_sol = game_load("../ressources/test_files/default.sol");
  bool test1 = game_equal(g_default_sol, load_default_sol, false);

  game_delete(g_default);
  game_delete(g_default_sol);
  game_delete(load_default);
  game_delete(load_default_sol);
  return test && test1;
}

/* ********** USAGE ********** */

void usage(int argc, char *argv[]) {
  fprintf(stderr, "Usage: %s <testname> [<...>]\n", argv[0]);
  exit(EXIT_FAILURE);
}

/* ********** MAIN ROUTINE ********** */

int main(int argc, char *argv[]) {
  if (argc == 1) usage(argc, argv);

  // start test
  fprintf(stderr, "=> Start test \"%s\"\n", argv[1]);
  bool ok = false;
  if (strcmp("dummy", argv[1]) == 0)
    ok = test_dummy();
  else if (strcmp("new_empty", argv[1]) == 0)
    ok = test_new_empty();
  else if (strcmp("won", argv[1]) == 0)
    ok = test_won();
  else if (strcmp("default", argv[1]) == 0)
    ok = test_default();
  else if (strcmp("copy", argv[1]) == 0)
    ok = test_copy();
  else if (strcmp("play_move", argv[1]) == 0)
    ok = test_play_move();
  else if (strcmp("reset_orientation", argv[1]) == 0)
    ok = test_reset_orientation();
  else if (strcmp("get_piece_orientation", argv[1]) == 0)
    ok = test_get_piece_orientation();
  else if (strcmp("new", argv[1]) == 0)
    ok = test_new();
  else if (strcmp("get_piece_shape", argv[1]) == 0)
    ok = test_get_piece_shape();
  else if (strcmp("random", argv[1]) == 0)
    ok = test_game_random();
  else if (strcmp("load", argv[1]) == 0)
    ok = test_game_load();
  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;
  }
}
