#include "game.h"

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

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

// Declaration of functions present in game_aux.c
void verify_p(void* p, char* msg);
void verify_coord(void* g, uint i, uint j, char* name_func);
void verify_val_range(uint val, uint min, uint max, char* msg);

/**
 * @brief Delete the history of the next moves.
 * @param g the game who the next history is to be deleted
 * @return the copy of the game
 * @pre @p g must be a valid pointer toward a game structure.
 **/
void remove_next_history(game g) {
  verify_p(g, "NULL pointer for remove_next_history.\n");
  if (g->history != NULL) {
    history temp = g->history->next;
    while (temp != NULL) {
      history next = temp->next;
      free(temp);
      temp = next;
    }
    g->history->next = NULL;
  }
}

/**
 * @brief Delete a history.
 * @param g the game who history is to be deleted
 * @return the game without history
 * @pre @p g must be a valid pointer toward a game structure.
 **/
void remove_history(game g) {
  remove_next_history(g);
  // Remove previous history
  while (g->history != NULL) {
    history prev = g->history->previous;
    free(g->history);
    g->history = prev;
  }
}

/**
 * @brief create a history if there are none or add a move if an history exist
 * @param g the game where you add a move to the history or create one
 * @return the game with a updated history
 * @pre @p g must be a valid pointer toward a game structure.
 **/
void new_history(game g, uint i, uint j, int nb_turns) {
  verify_p(g, "NULL pointer for new_history.\n");
  if (g->history == NULL && i == 0 && j == 0 && nb_turns == 0) {
    g->history = malloc(sizeof(struct history_s));
    // Check that history is allocated
    verify_p((void*)g->history, "Not enough memory !");
    g->history->previous = NULL;
  } else {
    g->history->next = malloc(sizeof(struct history_s));
    verify_p((void*)g->history->next, "Not enough memory !");
    g->history->next->previous = g->history;
    g->history = g->history->next;
  }
  g->history->next = NULL;
  g->history->i = i;
  g->history->j = j;
  g->history->nb_turns = nb_turns;
}

game game_new_empty(void) { return game_new(NULL, NULL); }

game game_new(shape* shapes, direction* orientations) {
  // Create an uninitialised game
  game g = malloc(sizeof(struct game_s));
  // Check that g is allocated
  verify_p((void*)g, "Not enough memory !");
  g->shapes = malloc(sizeof(shape) * (DEFAULT_SIZE * DEFAULT_SIZE));
  g->directions = malloc(sizeof(direction) * (DEFAULT_SIZE * DEFAULT_SIZE));
  // Check that shapes and orientations are allocated
  verify_p((void*)g->shapes, "Not enough memory !");
  verify_p((void*)g->directions, "Not enough memory !");

  g->rows = DEFAULT_SIZE;
  g->cols = DEFAULT_SIZE;
  g->wrapping = false;

  // If  there are shapes or directions set them
  if (shapes)
    memcpy(g->shapes, shapes, sizeof(shape) * (DEFAULT_SIZE * DEFAULT_SIZE));
  if (orientations)
    memcpy(g->directions, orientations,
           sizeof(direction) * (DEFAULT_SIZE * DEFAULT_SIZE));
  if (!shapes || !orientations) {
    // Go around the 2D grid
    for (unsigned int i = 0; i < DEFAULT_SIZE; i++) {
      for (unsigned j = 0; j < DEFAULT_SIZE; j++) {
        // If the values of shape or orientation is NULL set it to default
        if (!shapes) {
          g->shapes[i * DEFAULT_SIZE + j] = EMPTY;
        }
        if (!orientations) {
          g->directions[i * DEFAULT_SIZE + j] = NORTH;
        }
      }
    }
  }
  // Initilize the history and in need to set it to NULL because it verify if it
  // exist and here it not initilized so it can cause problem
  g->history = NULL;
  new_history(g, 0, 0, 0);
  return g;
}

game game_copy(cgame g) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_copy !");
  return game_new_ext(game_nb_rows(g), game_nb_cols(g), g->shapes,
                      g->directions, game_is_wrapping(g));
}

bool game_equal(cgame g1, cgame g2, bool ignore_orientation) {
  // Check that g and g2 are valids pointers
  verify_p((void*)g1, "Invalid game pointer for game_equal !");
  verify_p((void*)g2, "Invalid game pointer for game_equal !");
  // CCheck if the games have the same size
  if (game_nb_rows(g1) != game_nb_rows(g2) ||
      game_nb_cols(g1) != game_nb_cols(g2) ||
      game_is_wrapping(g1) != game_is_wrapping(g2)) {
    return false;
  }
  // Go around the 2D grid
  for (unsigned int i = 0; i < game_nb_rows(g1); i++) {
    for (unsigned int j = 0; j < game_nb_cols(g1); j++) {
      // Check that the pieces are the same
      if (g1->shapes[i * game_nb_cols(g1) + j] !=
          g2->shapes[i * game_nb_cols(g1) + j]) {
        return false;
      }

      // Check orientation if we don't ignore it
      if (!ignore_orientation && g1->directions[i * game_nb_cols(g1) + j] !=
                                     g2->directions[i * game_nb_cols(g1) + j]) {
        return false;
      }
    }
  }

  return true;
}

void game_delete(game g) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_delete !");
  if (g->shapes != NULL) {
    free(g->shapes);
  }
  if (g->directions != NULL) {
    free(g->directions);
  }
  remove_history(g);
  free(g);
}

void game_set_piece_shape(game g, uint i, uint j, shape s) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_set_piece_shape !");
  // Verify if the parameters i and j are valid
  verify_coord((void*)g, i, j, "game_set_piece_shape");
  // Verify if s is really an existing shape
  verify_val_range(s, 0, NB_SHAPES, "Invalid shape for game_set_piece_shape !");
  g->shapes[i * game_nb_cols(g) + j] = s;
}

void game_set_piece_orientation(game g, uint i, uint j, direction o) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_set_piece_orientation !");
  // Verify if the parameters i and j are valid
  verify_coord((void*)g, i, j, "game_set_piece_orientation");
  // Verify if o is really an existing direction
  verify_val_range(o, 0, NB_DIRS,
                   "Invalid direction for game_set_piece_orientation !");
  g->directions[i * game_nb_cols(g) + j] = o;
}

shape game_get_piece_shape(cgame g, uint i, uint j) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_get_piece_shape !");
  // Verify if the parameters i and j are valid
  verify_coord((void*)g, i, j, "game_get_piece_shape");
  // Return the shape of the selected piece
  return g->shapes[i * game_nb_cols(g) + j];
}

direction game_get_piece_orientation(cgame g, uint i, uint j) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_get_piece_orientation !");
  // Verify if the parameters i and j are valid
  verify_coord((void*)g, i, j, "game_get_piece_orientation");
  // Return the orientation of the selected piece
  return g->directions[i * game_nb_cols(g) + j];
}

void game_play_move(game g, uint i, uint j, int nb_quater_turns) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_play_move !");
  // Verify if the parameters i and j are valid
  verify_coord((void*)g, i, j, "game_play_move");

  // Get the current orientation of the piece at position (i, j)
  direction o = game_get_piece_orientation(g, i, j);
  // Calculate the new orientation by adding the number of quarter turns and
  // wrapping around
  int new_orientation = (o + nb_quater_turns + NB_DIRS) % NB_DIRS;
  // Set the new orientation for the piece at position (i, j)
  game_set_piece_orientation(g, i, j, new_orientation);

  remove_next_history(g);
  new_history(g, i, j, nb_quater_turns);
}

bool game_won(cgame g) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_won !");
  // Check if game is won
  return game_is_well_paired(g) && game_is_connected(g);
}

void game_reset_orientation(game g) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_reset_orientation !");
  // Go around the 2D grid
  for (unsigned int i = 0; i < game_nb_rows(g); i++) {
    for (unsigned j = 0; j < game_nb_cols(g); j++) {
      // Set all the piece orientations to NORTH
      g->directions[i * game_nb_cols(g) + j] = NORTH;
    }
  }
  // Delete the old history
  remove_history(g);
  // Recreate base history
  new_history(g, 0, 0, 0);
}

void game_shuffle_orientation(game g) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_shuffle_orientation !");
  // Go around the 2D grid
  for (int i = 0; i < game_nb_rows(g); i++) {
    for (int j = 0; j < game_nb_cols(g); j++) {
      // Draw a random value between 0 and 3 and assign it to this piece.
      game_set_piece_orientation(g, i, j, rand() % 4);
    }
  }
  // Delete the old history
  remove_history(g);
  // Recreate base history
  new_history(g, 0, 0, 0);
}
