#include "game_aux.h"

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

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

/**
 * @brief Verify the validity of a pointer
 * @param p the pointer to verify
 * @param msg the error message to show if the pointer is invalid
 **/
void verify_p(void* p, char* msg) {
  if (p == NULL) {
    fprintf(stderr, "%s\n", msg);
    exit(EXIT_FAILURE);
  }
}

/**
 * @brief Check the validity of thes coordinates of a game (prevent to try to
 *acces out of the game grid)
 * @param g the game where the coordinated will be used
 * @param i the row of the game whose need to be checked
 * @param j the cols of the game whose need to be checked
 * @pre @p g must be a valid pointer toward a game structure.
 **/
void verify_coord(void* g, uint i, uint j, char* name_func) {
  if (i < 0 || j < 0 || i >= game_nb_rows(g) || j >= game_nb_cols(g)) {
    fprintf(stderr, "Invalid coordinates for %s!\n", name_func);
    exit(EXIT_FAILURE);
  }
}

/**
 * @brief Verify that an value if in range between min and max.
 * @param val the value to check
 * @param min minimum value the val should be
 * @param max maximum value the val should be
 * @param msg the message to show if the value is invalid
 **/
void verify_val_range(uint val, uint min, int max, char* msg) {
  if ((val < min || (max != -1 && val > max))) {
    fprintf(stderr, "%s\n", msg);
    exit(EXIT_FAILURE);
  }
}

void game_print(cgame g) {
  printf("   ");  // Spacing for column numbers
  // We can't print more than 10 cols/rows
  int nb_cols = game_nb_cols(g);
  if (game_nb_cols(g) > 10) nb_cols = 10;
  int nb_rows = game_nb_rows(g);
  if (game_nb_rows(g) > 10) nb_rows = 10;

  for (uint j = 0; j < nb_cols; j++) {
    printf("%d ", j);
  }
  printf("\n");

  printf("   ");  // Start of the top border
  for (uint j = 0; j < nb_cols; j++) {
    printf("--");
  }
  printf("-\n");
  for (uint i = 0; i < nb_rows; i++) {
    printf("%d |", i);  // Row number followed by the left border
    for (uint j = 0; j < nb_cols; j++) {
      uint pos = i * game_nb_cols(g) + j;
      if (g->shapes[pos] == EMPTY) {
        printf("  ");
      } else if (g->shapes[pos] == CROSS) {
        printf("+ ");
      } else if (g->shapes[pos] == SEGMENT) {
        if (g->directions[pos] == NORTH ||
            g->directions[pos] == SOUTH) {  // It's thhe same shape so we don't
                                            // need to add more useless code
          printf("| ");
        }
        if (g->directions[pos] == WEST ||
            g->directions[pos] == EAST) {  // Same thing
          printf("- ");
        }
      } else if (g->shapes[pos] == ENDPOINT) {
        switch (g->directions[pos]) {  // these three have a different shape for
                                       // each direction so a switch is better
          case NORTH:
            printf("^ ");
            break;
          case EAST:
            printf("> ");
            break;
          case SOUTH:
            printf("v ");
            break;
          case WEST:
            printf("< ");
            break;
          default:
            break;
        }
      } else if (g->shapes[pos] == CORNER) {
        switch (g->directions[pos]) {
          case NORTH:
            printf("└ ");
            break;
          case EAST:
            printf("┌ ");
            break;
          case SOUTH:
            printf("┐ ");
            break;
          case WEST:
            printf("┘ ");
            break;
          default:
            break;
        }
      } else if (g->shapes[pos] == TEE) {
        switch (g->directions[pos]) {
          case SOUTH:
            printf("┬ ");
            break;
          case NORTH:
            printf("┴ ");
            break;
          case EAST:
            printf("├ ");
            break;
          case WEST:
            printf("┤ ");
            break;
          default:
            break;
        }
      }
    }
    printf("|\n");  // Right border
  }

  printf("   ");  // Start of the bottom border
  for (uint j = 0; j < nb_cols; j++) {
    printf("--");
  }
  printf("-\n");
}

// for game_default and game_default_solution we juste did the tab by hand based
// on the library given by the teacher
game game_default(void) {
  shape shapes[DEFAULT_SIZE * DEFAULT_SIZE] = {
      CORNER,  ENDPOINT, ENDPOINT, CORNER,   ENDPOINT, TEE,     TEE,
      TEE,     TEE,      TEE,      ENDPOINT, ENDPOINT, TEE,     ENDPOINT,
      SEGMENT, ENDPOINT, TEE,      TEE,      CORNER,   SEGMENT, ENDPOINT,
      TEE,     ENDPOINT, ENDPOINT, ENDPOINT};

  direction orientations[DEFAULT_SIZE * DEFAULT_SIZE] = {
      WEST, NORTH, WEST,  NORTH, SOUTH, SOUTH, WEST,  NORTH, EAST,
      EAST, EAST,  NORTH, WEST,  WEST,  EAST,  SOUTH, SOUTH, NORTH,
      WEST, NORTH, EAST,  WEST,  SOUTH, EAST,  SOUTH};
  game g = game_new(shapes, orientations);
  return g;
}

game game_default_solution(void) {
  shape shapes[DEFAULT_SIZE * DEFAULT_SIZE] = {
      CORNER,  ENDPOINT, ENDPOINT, CORNER,   ENDPOINT, TEE,     TEE,
      TEE,     TEE,      TEE,      ENDPOINT, ENDPOINT, TEE,     ENDPOINT,
      SEGMENT, ENDPOINT, TEE,      TEE,      CORNER,   SEGMENT, ENDPOINT,
      TEE,     ENDPOINT, ENDPOINT, ENDPOINT};

  direction orientations[DEFAULT_SIZE * DEFAULT_SIZE] = {
      EAST,  WEST,  EAST,  SOUTH, SOUTH, EAST,  SOUTH, SOUTH, NORTH,
      WEST,  NORTH, NORTH, EAST,  WEST,  SOUTH, EAST,  SOUTH, NORTH,
      SOUTH, SOUTH, EAST,  NORTH, WEST,  NORTH, NORTH};

  game g = game_new(shapes, orientations);
  return g;
}

bool game_get_ajacent_square(cgame g, uint i, uint j, direction d,
                             uint* pi_next, uint* pj_next) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_get_ajacent_square!");
  // Verify if the parameters i and j are valid
  verify_coord((void*)g, i, j, "game_get_ajacent_square");
  // Verify if d is really an existing direction
  verify_val_range(d, 0, NB_DIRS,
                   "Invalid direction for game_get_ajacent_square !");
  // Check if it won't be out of bounds (and without wrapping!)
  if ((d == NORTH && i == 0) || (d == SOUTH && i == game_nb_rows(g) - 1) ||
      (d == WEST && j == 0) || (d == EAST && j == game_nb_cols(g) - 1)) {
    if (!game_is_wrapping(g)) {
      return false;
    } else {
      if (d == NORTH) {
        i = game_nb_rows(g);
      }  // i-1 will be nb_rows-1
      if (d == SOUTH) {
        i = -1;
      }  // i+1 will be 0
      if (d == WEST) {
        j = game_nb_cols(g);
      }  // j-1 will be nb_cols-1
      if (d == EAST) {
        j = -1;
      }  // j+1 will be 0
    }
  }
  // Set the i and j value of the adjacent for every direction
  if (d == NORTH) {
    *pi_next = i - 1;
    *pj_next = j;
  }
  if (d == SOUTH) {
    *pi_next = i + 1;
    *pj_next = j;
  }
  if (d == WEST) {
    *pi_next = i;
    *pj_next = j - 1;
  }
  if (d == EAST) {
    *pi_next = i;
    *pj_next = j + 1;
  }
  return true;
}

bool game_has_half_edge(cgame g, uint i, uint j, direction d) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_has_half_edge!");
  // Verify if the parameters i and j are valid
  verify_coord((void*)g, i, j, "game_has_half_edge");
  // Verify if d is really an existing direction
  verify_val_range(d, 0, NB_DIRS, "Invalid direction for game_has_half_edge !");
  // We get the properties of the chosen piece
  shape s = game_get_piece_shape(g, i, j);
  // Always false when it's EMPTY
  if (s == EMPTY) {
    return false;
  }
  // Always true when it's a cross
  if (s == CROSS) {
    return true;
  }
  direction dpiece = game_get_piece_orientation(g, i, j);
  // When it's an endpoint, it needs to be in the same direction
  if (s == ENDPOINT && d == dpiece) {
    return true;
  }
  // Segments have an half edge in the direction of the piece and its opposite
  if (s == SEGMENT && (d == dpiece || d == (dpiece + 2) % NB_DIRS)) {
    return true;
  }
  // Two possibilities, the direction of the piece or the direction+1!
  if (s == CORNER && (d == dpiece || d == (dpiece + 1) % NB_DIRS)) {
    return true;
  }
  // There's an half edge everywhere except at the opposite direction of the
  // piece (ex: dpiece=NORTH, only SOUTH doesn't have and half edge)
  if (s == TEE && d != (dpiece + 2) % NB_DIRS) {
    return true;
  }
  return false;
}

edge_status game_check_edge(cgame g, uint i, uint j, direction d) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_check_edge!");
  // Verify if the parameters i and j are valid
  verify_coord((void*)g, i, j, "game_check_edge");
  // Verify if d is really an existing direction
  verify_val_range(d, 0, NB_DIRS, "Invalid direction for game_check_edge !");
  // Get the other piece
  uint inext, jnext;
  // we need to know if the piece in the given direction is out of bounds!
  if (game_get_ajacent_square(g, i, j, d, &inext, &jnext)) {
    bool thispiece = game_has_half_edge(g, i, j, d);
    bool nextpiece = game_has_half_edge(g, inext, jnext, (d + 2) % NB_DIRS);
    // When the two pieces don't have an half edge: NOEDGE
    if (!thispiece && !nextpiece) {
      return NOEDGE;
    }
    // When the two pieces have an half edge, it's a MATCH !
    if (thispiece && nextpiece) {
      return MATCH;
    }
    // If none of the two previous conditions are true, then there's a mismatch
    return MISMATCH;
  } else {
    // When the other piece is out of bounds, there's a mismatch only if it has
    // an half edge!
    if (game_has_half_edge(g, i, j, d)) {
      return MISMATCH;
    } else {
      return NOEDGE;
    }
  }
}

bool game_is_well_paired(cgame g) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_is_well_paired!");
  // Go around the grid
  for (int i = 0; i < game_nb_rows(g); i++) {
    for (int j = 0; j < game_nb_cols(g); j++) {
      // Check for a mismatch in direction NORTH and WEST, because it's checking
      // for EAST at (0,0) and WEST at (0,1) is the same (same with columns)
      if (game_check_edge(g, i, j, WEST) == MISMATCH ||
          game_check_edge(g, i, j, NORTH) == MISMATCH) {
        return false;
      }
      if (!game_is_wrapping(g) &&
          ((j == game_nb_cols(g) - 1 &&
            game_check_edge(g, i, j, EAST) == MISMATCH) ||
           (i == game_nb_rows(g) - 1 &&
            game_check_edge(g, i, j, SOUTH) == MISMATCH))) {
        return false;
      }
    }
  }
  return true;
}

void game_is_connected_rec(cgame g, uint i, uint j, bool* connected) {
  connected[i * game_nb_cols(g) + j] = true;
  for (int d = 0; d < NB_DIRS; d++) {
    uint inext, jnext;
    if (game_get_ajacent_square(g, i, j, d, &inext, &jnext) &&
        !connected[inext * game_nb_cols(g) + jnext])
      if (game_check_edge(g, i, j, d) == MATCH) {
        game_is_connected_rec(g, inext, jnext, connected);
      }
  }
}

bool game_is_connected(cgame g) {
  // Check that g is a valid pointer
  verify_p((void*)g, "Invalid game pointer for game_is_connected!");
  int nb_rows = game_nb_rows(g), nb_cols = game_nb_cols(g);
  // Set a table to check if all cases are connected
  bool connected[nb_rows * nb_cols];
  verify_p((void*)connected, "NULL pointer to table in game_is_connected_!");
  memset(connected, false, nb_rows * nb_cols * sizeof(bool));
  // Set variables for the starting point and the number of pieces not empty
  int pos = 0;
  while (pos < nb_cols * nb_rows &&
         game_get_piece_shape(g, pos / nb_cols, pos % nb_cols) == EMPTY) {
    pos++;
  }
  if (pos == nb_rows * nb_cols) return true;

  int i_start = pos / nb_cols, j_start = pos % nb_cols;
  bool not_alone = false;
  direction d = SOUTH;
  uint inext = 0, jnext = 0;
  while (!not_alone && d != EAST) {
    if (game_get_ajacent_square(g, i_start, j_start, d, &inext, &jnext) &&
        game_get_piece_shape(g, inext, jnext) != EMPTY) {
      not_alone = true;
    }
    d = EAST;
  }
  if (!not_alone) return false;

  // Verify if the parameters i and j are valid
  verify_coord((void*)g, i_start, j_start, "game_is_connected_rec");
  game_is_connected_rec(g, i_start, j_start, connected);
  for (int i = 0; i < nb_rows; i++) {
    for (int j = 0; j < nb_cols; j++) {
      // Check if a piece isn't connected in the "connected" table
      if (connected[i * nb_cols + j] == false &&
          game_get_piece_shape(g, i, j) != EMPTY)
        return false;
    }
  }
  return true;
}