#include "game_sdl.h"

#include <SDL.h>
#include <SDL_image.h>  // required to load transparent texture from PNG
#include <SDL_ttf.h>    // required to use TTF fonts
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

#define ENDPOINT_TEXTURE "ressources/textures/pieces/endpoint.png"
#define CORNER_TEXTURE "ressources/textures/pieces/corner.png"
#define CROSS_TEXTURE "ressources/textures/pieces/cross.png"
#define TEE_TEXTURE "ressources/textures/pieces/tee.png"
#define SEGMENT_TEXTURE "ressources/textures/pieces/segment.png"

#define ENDPOINT_TEXTURE_CONNECTED \
  "ressources/textures/pieces/endpoint_connected.png"
#define CORNER_TEXTURE_CONNECTED \
  "ressources/textures/pieces/corner_connected.png"
#define CROSS_TEXTURE_CONNECTED "ressources/textures/pieces/cross_connected.png"
#define TEE_TEXTURE_CONNECTED "ressources/textures/pieces/tee_connected.png"
#define SEGMENT_TEXTURE_CONNECTED \
  "ressources/textures/pieces/segment_connected.png"

#define MISMATCH_TEXTURE "ressources/textures/pieces/mismatch.png"

#define BUTTON_SCALE_PIECE 0.5
#define FONT "ressources/arial.ttf"
#define FONTSIZE 36
#define BACKGROUND "ressources/textures/background.png"

#define BUTTON_SHUFFLE "ressources/textures/buttons/button_shuffle.png"
#define BUTTON_SOLVE "ressources/textures/buttons/button_solve.png"
#define BUTTON_QUIT "ressources/textures/buttons/button_quit.png"
#define BUTTON_UNDO "ressources/textures/buttons/button_undo.png"
#define BUTTON_REDO "ressources/textures/buttons/button_redo.png"
#define BUTTON_RESTART_GAME "ressources/textures/buttons/button_restart.png"

/* **************************************************************** */

struct Env_t {
  SDL_Texture **shapes_textures;
  SDL_Rect *shapes_rect;
  SDL_Texture **boutons_textures;
  SDL_Rect *boutons_rect;
  SDL_Texture *background;
  SDL_Texture *text;
  game game;
  char *game_file;
  bool won;
};

enum Buttons { SHUFFLE, SOLVE, QUIT, UNDO, REDO, RESTART, NB_BUTTONS };

/* **************************************************************** */

Env *init(SDL_Window *win, SDL_Renderer *ren, int argc, char *argv[]) {
  Env *env = malloc(sizeof(struct Env_t));

  if (argc == 2) {
    env->game_file = argv[1];
    env->game = game_load(env->game_file);
  } else if (argc > 2) {
    printf("Usage ./game_sdl [<filename>] or ./game_sdl");
    exit(EXIT_FAILURE);
  } else {
    env->game_file = "DEFAULT";
    env->game = game_default();
  }
  env->won = false;

  /* init a tab to hold all the pieces textures*/
  env->shapes_textures = malloc(2 * NB_SHAPES * sizeof(SDL_Texture *));
  if (!env->shapes_textures) ERROR("Not Enough Memory\n");

  /* init a tab to hold all the pieces emplacement*/
  env->shapes_rect = malloc(
      (game_nb_cols(env->game) * game_nb_rows(env->game)) * sizeof(SDL_Rect));
  if (!env->shapes_rect) ERROR("Not Enough Memory\n");

  /* init the games pieces texture from PNG images */
  env->shapes_textures[ENDPOINT] = IMG_LoadTexture(ren, ENDPOINT_TEXTURE);
  if (!env->shapes_textures[ENDPOINT])
    ERROR("IMG_LoadTexture: %s\n", ENDPOINT_TEXTURE);
  env->shapes_textures[SEGMENT] = IMG_LoadTexture(ren, SEGMENT_TEXTURE);
  if (!env->shapes_textures[SEGMENT])
    ERROR("IMG_LoadTexture: %s\n", SEGMENT_TEXTURE);
  env->shapes_textures[CORNER] = IMG_LoadTexture(ren, CORNER_TEXTURE);
  if (!env->shapes_textures[CORNER])
    ERROR("IMG_LoadTexture: %s\n", CORNER_TEXTURE);
  env->shapes_textures[TEE] = IMG_LoadTexture(ren, TEE_TEXTURE);
  if (!env->shapes_textures[TEE]) ERROR("IMG_LoadTexture: %s\n", TEE_TEXTURE);
  env->shapes_textures[CROSS] = IMG_LoadTexture(ren, CROSS_TEXTURE);
  if (!env->shapes_textures[CROSS])
    ERROR("IMG_LoadTexture: %s\n", CROSS_TEXTURE);

  /* init the games connected pieces texture from PNG images */
  env->shapes_textures[ENDPOINT + NB_SHAPES] =
      IMG_LoadTexture(ren, ENDPOINT_TEXTURE_CONNECTED);
  if (!env->shapes_textures[ENDPOINT + NB_SHAPES])
    ERROR("IMG_LoadTexture: %s\n", ENDPOINT_TEXTURE_CONNECTED);
  env->shapes_textures[SEGMENT + NB_SHAPES] =
      IMG_LoadTexture(ren, SEGMENT_TEXTURE_CONNECTED);
  if (!env->shapes_textures[SEGMENT + NB_SHAPES])
    ERROR("IMG_LoadTexture: %s\n", SEGMENT_TEXTURE_CONNECTED);
  env->shapes_textures[CORNER + NB_SHAPES] =
      IMG_LoadTexture(ren, CORNER_TEXTURE_CONNECTED);
  if (!env->shapes_textures[CORNER + NB_SHAPES])
    ERROR("IMG_LoadTexture: %s\n", CORNER_TEXTURE_CONNECTED);
  env->shapes_textures[TEE + NB_SHAPES] =
      IMG_LoadTexture(ren, TEE_TEXTURE_CONNECTED);
  if (!env->shapes_textures[TEE + NB_SHAPES])
    ERROR("IMG_LoadTexture: %s\n", TEE_TEXTURE_CONNECTED);
  env->shapes_textures[CROSS + NB_SHAPES] =
      IMG_LoadTexture(ren, CROSS_TEXTURE_CONNECTED);
  if (!env->shapes_textures[CROSS + NB_SHAPES])
    ERROR("IMG_LoadTexture: %s\n", CROSS_TEXTURE_CONNECTED);

  // init the mismatch texture
  env->shapes_textures[2 * NB_SHAPES] = IMG_LoadTexture(ren, MISMATCH_TEXTURE);
  if (!env->shapes_textures[2 * NB_SHAPES])
    ERROR("IMG_LoadTexture: %s\n", MISMATCH_TEXTURE);

  // init the background texture
  env->background = IMG_LoadTexture(ren, BACKGROUND);
  if (!env->background) ERROR("IMG_LoadTexture: %s\n", BACKGROUND);

  /* init text texture using Arial font */
  SDL_Color color = {0, 0, 255, 255}; /* blue color in RGBA */
  TTF_Font *font = TTF_OpenFont(FONT, FONTSIZE);
  if (!font) ERROR("TTF_OpenFont: %s\n", FONT);
  TTF_SetFontStyle(font,
                   TTF_STYLE_BOLD);  // TTF_STYLE_ITALIC | TTF_STYLE_NORMAL
  SDL_Surface *surf =
      TTF_RenderText_Blended(font, "Congratulation, you won!",
                             color);  // blended rendering for ultra nice text
  env->text = SDL_CreateTextureFromSurface(ren, surf);
  SDL_FreeSurface(surf);
  TTF_CloseFont(font);

  /* init a tab to hold all the bouttons textures*/
  env->boutons_textures = malloc(NB_BUTTONS * sizeof(SDL_Texture *));
  if (!env->boutons_textures) ERROR("Not Enough Memory\n");

  /* init a tab to hold all the bouttons emplacement*/
  env->boutons_rect = malloc(NB_BUTTONS * sizeof(SDL_Rect));
  if (!env->boutons_rect) ERROR("Not Enough Memory\n");

  /* init the buttons texture from PNG images */
  env->boutons_textures[SHUFFLE] = IMG_LoadTexture(ren, BUTTON_SHUFFLE);
  if (!env->boutons_textures[SHUFFLE])
    ERROR("IMG_LoadTexture: %s\n", BUTTON_SHUFFLE);
  env->boutons_textures[SOLVE] = IMG_LoadTexture(ren, BUTTON_SOLVE);
  if (!env->boutons_textures[SOLVE])
    ERROR("IMG_LoadTexture: %s\n", BUTTON_SOLVE);
  env->boutons_textures[QUIT] = IMG_LoadTexture(ren, BUTTON_QUIT);
  if (!env->boutons_textures[QUIT]) ERROR("IMG_LoadTexture: %s\n", BUTTON_QUIT);
  env->boutons_textures[UNDO] = IMG_LoadTexture(ren, BUTTON_UNDO);
  if (!env->boutons_textures[UNDO]) ERROR("IMG_LoadTexture: %s\n", BUTTON_UNDO);
  env->boutons_textures[REDO] = IMG_LoadTexture(ren, BUTTON_REDO);
  if (!env->boutons_textures[REDO]) ERROR("IMG_LoadTexture: %s\n", BUTTON_REDO);
  env->boutons_textures[RESTART] = IMG_LoadTexture(ren, BUTTON_RESTART_GAME);
  if (!env->boutons_textures[RESTART])
    ERROR("IMG_LoadTexture: %s\n", BUTTON_RESTART_GAME);

  /* get current window size */
  int w, h;
  SDL_GetWindowSize(win, &w, &h);

  return env;
}

/* **************************************************************** */

void render(SDL_Window *win, SDL_Renderer *ren, Env *env) {
  SDL_Rect rect;

  /* get current window size */
  int w, h;
  SDL_GetWindowSize(win, &w, &h);

  /* render background texture */
  SDL_RenderCopy(ren, env->background, NULL, NULL); /* stretch it */

  int nb_cols = game_nb_cols(env->game);
  int nb_rows = game_nb_rows(env->game);
  float size_shape, game_offset_x = 0, game_offset_y = 0;
  SDL_QueryTexture(env->boutons_textures[0], NULL, NULL, &rect.w, &rect.h);
  float button_ration_xy = rect.w / rect.h;
  float button_offset_x = 0, button_offset_y = 0;
  float button_size_x, button_size_y;
  bool button_side = false;

  if (h / (nb_rows + (2 + 3 * 0.5) * BUTTON_SCALE_PIECE) >
      w / (nb_cols + 2 * BUTTON_SCALE_PIECE * button_ration_xy)) {
    size_shape = h / (nb_rows + (2 + 3 * 0.5) * BUTTON_SCALE_PIECE);
    button_size_y = size_shape * BUTTON_SCALE_PIECE;
    button_size_x = button_size_y * button_ration_xy;
    if (w < nb_cols * size_shape) {
      size_shape = w / nb_cols;
    }
    button_offset_y = h - button_size_y * (2 + 2 * 0.5);
    game_offset_x = (w - nb_cols * size_shape) / 2;
    game_offset_y =
        (h - nb_rows * size_shape - button_size_y * (2 + 3 * 0.5)) / 2;
  } else {
    button_side = true;
    size_shape = w / (nb_cols + 2 * button_ration_xy * BUTTON_SCALE_PIECE);
    button_size_y = size_shape * BUTTON_SCALE_PIECE;
    button_size_x = button_size_y * button_ration_xy;
    if (h < nb_rows * size_shape) {
      size_shape = h / nb_rows;
    }
    button_offset_x = button_size_x / 2;
    button_offset_y = (h - button_size_y * NB_BUTTONS) / (NB_BUTTONS + 1);
    game_offset_x =
        2 * button_size_x + (w - nb_cols * size_shape - 2 * button_size_x) / 2;
    game_offset_y = (h - nb_rows * size_shape) / 2;
  }

  /* render buttons */
  for (int i = 0; i < NB_BUTTONS; i++) {
    SDL_Rect button_rect;
    button_rect.w = button_size_x;
    button_rect.h = button_size_y;
    if (button_side) {
      button_rect.x = button_size_x / 2;
      button_rect.y = button_offset_y * (1 + i) + button_size_y * (i);
    } else {
      int nb_buttons_line = NB_BUTTONS / 2;
      if (i % 2 == 0) {
        button_offset_x =
            (w - button_size_x * nb_buttons_line) / (nb_buttons_line + 1);
        button_rect.y = button_offset_y;
      } else {
        nb_buttons_line += NB_BUTTONS % 2;
        button_offset_x =
            (w - button_size_x * nb_buttons_line) / (nb_buttons_line + 1);
        button_rect.y = button_offset_y + button_size_y * 1.5;
      }
      button_rect.x = button_offset_x * (1 + i / 2) + button_size_x * (i / 2);
    }
    SDL_RenderCopy(ren, env->boutons_textures[i], NULL, &button_rect);
    env->boutons_rect[i] = button_rect;
  }
  if (env->won) {
    SDL_QueryTexture(env->text, NULL, NULL, &rect.w, &rect.h);
    rect.x = w / 2 - rect.w / 2;
    rect.y = h / 2 - rect.h / 2;
    SDL_RenderCopy(ren, env->text, NULL, &rect);
  } else {
    /* render game pieces */
    for (int row = 0; row < nb_rows; row++) {
      for (int col = 0; col < nb_cols; col++) {
        bool connected = true;
        SDL_Rect shape_rect;
        shape s = game_get_piece_shape(env->game, row, col);
        shape_rect.w = size_shape;
        shape_rect.h = size_shape;
        shape_rect.x = game_offset_x + shape_rect.w * col;
        shape_rect.y = game_offset_y + shape_rect.h * row;
        if (s != ENDPOINT) {
          for (int i = 0; i < NB_DIRS; i++) {
            if (game_check_edge(env->game, row, col, i) == MISMATCH) {
              connected = false;
            }
          }
        }
        if (connected || s == ENDPOINT) {
          SDL_QueryTexture(env->shapes_textures[s + NB_SHAPES], NULL, NULL,
                           NULL, NULL);
          SDL_RenderCopyEx(ren, env->shapes_textures[s + NB_SHAPES], NULL,
                           &shape_rect,
                           90 * game_get_piece_orientation(env->game, row, col),
                           NULL, SDL_FLIP_NONE);
        } else {
          SDL_QueryTexture(env->shapes_textures[s], NULL, NULL, NULL, NULL);
          SDL_RenderCopyEx(ren, env->shapes_textures[s], NULL, &shape_rect,
                           90 * game_get_piece_orientation(env->game, row, col),
                           NULL, SDL_FLIP_NONE);
        }
        env->shapes_rect[row * nb_cols + col] = shape_rect;

        // j'ai du dupliqué le code plus haut pour mettre les mismatch au dessus
        // des shapes si tu as une meilleure méthode vas y
        for (int i = 0; i < NB_DIRS; i++) {
          if (game_check_edge(env->game, row, col, i) == MISMATCH) {
            SDL_QueryTexture(env->shapes_textures[2 * NB_SHAPES], NULL, NULL,
                             NULL, NULL);
            SDL_RenderCopyEx(ren, env->shapes_textures[2 * NB_SHAPES], NULL,
                             &shape_rect, 90 * i, NULL, SDL_FLIP_NONE);
          }
        }
      }
    }
  }
}

/* **************************************************************** */

bool process(SDL_Window *win, SDL_Renderer *ren, Env *env, SDL_Event *e) {
  /* get current window size */
  int w, h;
  SDL_GetWindowSize(win, &w, &h);

  if (e->type == SDL_QUIT) {
    return true;
  }

  // if the mouse is clicked we get in there
  if (e->type == SDL_MOUSEBUTTONDOWN) {
    SDL_Point mouse;
    SDL_GetMouseState(&mouse.x, &mouse.y);
    env->won = false;

    if (game_won(env->game)) {
      env->won = true;
    }

    // if it's a left click we first verify the button because there is only 7
    // of them so it's shorter
    if (e->button.button == SDL_BUTTON_LEFT) {
      for (int i = 0; i < NB_BUTTONS; i++) {
        if (SDL_PointInRect(&(SDL_Point){mouse.x, mouse.y},
                            &env->boutons_rect[i])) {
          if (i == SHUFFLE) {
            game_shuffle_orientation(env->game);
            env->won = false;
          } else if (i == SOLVE && !env->won) {
            game_solve(env->game);
          } else if (i == QUIT) {
            return true;
          } else if (i == UNDO && !env->won) {
            game_undo(env->game);
          } else if (i == REDO && !env->won) {
            game_redo(env->game);
          } else if (i == RESTART) {
            game_delete(env->game);
            if (!strcmp(env->game_file, "DEFAULT")) {
              env->game = game_default();
            } else {
              env->game = game_load(env->game_file);
            }
            env->won = false;
          }
        }
      }
    }

    // cut useless checks
    if (env->won) {
      return false;
    }

    // else we verify every case of the game
    for (int i = 0; i < (game_nb_cols(env->game) * game_nb_rows(env->game));
         i++) {
      if (SDL_PointInRect(&(SDL_Point){mouse.x, mouse.y},
                          &env->shapes_rect[i])) {
        int row = i / game_nb_cols(env->game);
        int col = i % game_nb_cols(env->game);
        if (e->button.button == SDL_BUTTON_LEFT) {
          game_play_move(env->game, row, col, 1);
        } else if (e->button.button == SDL_BUTTON_RIGHT) {
          game_play_move(env->game, row, col, -1);
        }
      }
    }
  }
  return false;
}

/* **************************************************************** */

void clean(SDL_Window *win, SDL_Renderer *ren, Env *env) {
  if (!env) {
    printf("Error: env is NULL, unable to clean.\n");
    return;
  }

  // Releasing piece textures
  if (env->shapes_textures) {
    for (int i = 0; i < NB_SHAPES; i++) {
      if (env->shapes_textures[i]) {
        SDL_DestroyTexture(env->shapes_textures[i]);
        env->shapes_textures[i] = NULL;
      }
    }
    free(env->shapes_textures);
    env->shapes_textures = NULL;
  }

  // Releasing button textures
  if (env->boutons_textures) {
    for (int i = 0; i < NB_BUTTONS; i++) {
      if (env->boutons_textures[i]) {
        SDL_DestroyTexture(env->boutons_textures[i]);
        env->boutons_textures[i] = NULL;
      }
    }
    free(env->boutons_textures);
    env->boutons_textures = NULL;
  }

  // Releasing rectangle arrays
  if (env->shapes_rect) {
    free(env->shapes_rect);
    env->shapes_rect = NULL;
  }
  if (env->boutons_rect) {
    free(env->boutons_rect);
    env->boutons_rect = NULL;
  }

  // Releasing game
  if (env->game) {
    game_delete(env->game);
    env->game = NULL;
  }

  // Releasing game file...
  if (env->game_file && strcmp(env->game_file, "DEFAULT") != 0) {
    free(env->game_file);
    env->game_file = NULL;
  }

  // Releasing env structure...
  free(env);
  env = NULL;
  printf("Cleanup completed successfully.\n");
}

/* **************************************************************** */

int main(int argc, char *argv[]) {
  /* initialize SDL2 and some extensions */
  if (SDL_Init(SDL_INIT_VIDEO) != 0)
    ERROR("Error: SDL_Init VIDEO (%s)", SDL_GetError());
  if ((IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG) != IMG_INIT_PNG)
    ERROR("Error: IMG_Init PNG (%s)", SDL_GetError());
  if (TTF_Init() != 0) ERROR("Error: TTF_Init (%s)", SDL_GetError());

  /* create window and renderer */
  SDL_Window *win = SDL_CreateWindow(
      APP_NAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH,
      SCREEN_HEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
  if (!win) ERROR("Error: SDL_CreateWindow (%s)", SDL_GetError());
  SDL_Renderer *ren = SDL_CreateRenderer(
      win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
  if (!ren) ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_SOFTWARE);
  if (!ren) ERROR("Error: SDL_CreateRenderer (%s)", SDL_GetError());

  /* initialize your environment */
  Env *env = init(win, ren, argc, argv);

  /* main render loop */
  SDL_Event e;
  bool quit = false;
  while (!quit) {
    /* manage events */
    while (SDL_PollEvent(&e)) {
      /* process your events */
      quit = process(win, ren, env, &e);
      if (quit) break;
    }

    /* background in gray */
    SDL_SetRenderDrawColor(ren, 0xA0, 0xA0, 0xA0, 0xFF);
    SDL_RenderClear(ren);

    /* render all what you want */
    render(win, ren, env);
    SDL_RenderPresent(ren);
    SDL_Delay(DELAY);
  }

  /* clean your environment */
  clean(win, ren, env);

  SDL_DestroyRenderer(ren);
  SDL_DestroyWindow(win);
  IMG_Quit();
  TTF_Quit();
  SDL_Quit();

  return EXIT_SUCCESS;
}