/*
 * OpenKropki
 *
 * Copyright (C) 2014-2020 Mateusz Viste
 *
 * MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>           /* memset() */
#include <SDL2/SDL_thread.h>

#include "ai.h"
#include "drv_gra.h"
#include "ui.h"
#include "kropki_core.h"
#include "version.h"
#include "media/data.h"       /* contains the font data, sprites etc */


struct rozgrywka_t {
  int width;
  int height;
  int scrollvector[2];
  int gameended;
  int endgamesfxplayed;
  int kratkasize;
  int points[2]; /* remember last state of points (to detect new wins and to draw screen without re-counting while AI operates) */
  SDL_Thread *ai_thread;
  int ai_in_progress; /* a flag that is set to zero whenever the AI finishes processing - this may be used as a semaphore for multithreaded operations */
  struct kropki_game *game;
};


static void redraw_screen(struct rozgrywka_t *r, char *msg, int msgplayercolor) {
  int x, y;
  struct kropki_wallelement *wallsptr;
  ui_drawKartka(r->width, r->height, r->scrollvector);
  /* draw walls */
  for (wallsptr = r->game->walls; wallsptr != NULL; wallsptr = wallsptr->next) {
    graph_drawWall(wallsptr->x1, wallsptr->y1, wallsptr->x2, wallsptr->y2, wallsptr->player, r->scrollvector);
  }
  /* draw dots */
  for (y = 0; y < r->height; y++) {
    for (x = 0; x < r->width; x++) {
      int fieldval;
      fieldval = kropki_getpos(r->game, x, y);
      if ((fieldval & 3) != 0) {
        int activeflag = 0;
        if ((r->game->lastmovex == x) && (r->game->lastmovey == y)) activeflag = 1;
        graph_drawKropka(x, y, fieldval & 3, activeflag, r->scrollvector);
      }
    }
  }
  /* apply the fade-out border */
  /* ui_applyfaderect(25); */
  /* Draw scores */
  ui_drawscores(r->points, msg, msgplayercolor);
  /* refresh the screen */
  gra_refresh();
}



#define WIN_WIDTH  900
#define WIN_HEIGHT 600
#define COLORPLAYER1 0x3030FF
#define COLORPLAYER2 0xFF0000
#define INITIAL_KRATKA_SIZE 30

static void centerscrollvector(struct rozgrywka_t *rozgrywka) {
  int w, h;
  gra_getwinsize(&w, &h);
  if (w > rozgrywka->width) {
    rozgrywka->scrollvector[0] = (w - (rozgrywka->width + 1) * rozgrywka->kratkasize) / 2;
  } else {
    rozgrywka->scrollvector[0] = ((rozgrywka->width + 1) * rozgrywka->kratkasize - w) / 2;
  }
  if (h > rozgrywka->height) {
    rozgrywka->scrollvector[1] = (h - (rozgrywka->height + 1) * rozgrywka->kratkasize) / 2;
  } else {
    rozgrywka->scrollvector[1] = ((rozgrywka->height + 1) * rozgrywka->kratkasize - h) / 2;
  }
}


/* a wrapper around ai_play() that can be called as a thread */
static int ai_caller(void *rozgrywka) {
  struct rozgrywka_t *r = rozgrywka;
  ai_play(2, r->game);
  r->ai_in_progress = 0;
  return(0);
}


#define MAINMENU_NEWGAME 1u
#define MAINMENU_RESUME 2u
#define MAINMENU_ABOUT 3u
#define MAINMENU_QUIT 40u

/* main menu */
static int menu_main(struct rozgrywka_t *rozgrywka) {
  struct ui_menu_description_t menudesc[8];
  int menucount = 0;
  int menupos;

  /* zero out the menudesc array */
  memset(menudesc, 0, sizeof(menudesc));

  /* prepare the menu description */
  /* frontpic goes first, so it is never rendered over a label */
  menudesc[menucount].sprite[0] = gra_loadgzbmp(frontpic_bmp_gz, frontpic_bmp_gz_len);
  menudesc[menucount].propxpos = 0;
  menudesc[menucount].propypos = 100;
  menucount++;
  /* OpenKropki title */
  menudesc[menucount].sprite[0] = gra_text2sprite("OpenKropki v" PVER, DroidSansBold_ttf, DroidSansBold_ttf_len, 40, 0, 0, 0, 0xff, -1);
  menudesc[menucount].propxpos = 0xff;
  menudesc[menucount].propypos = 16;
  menucount++;
  /* copyright subtitle */
  menudesc[menucount].sprite[0] = gra_text2sprite("Copyright © " PDATE " Mateusz Viste", DroidSansBold_ttf, DroidSansBold_ttf_len, 16, 0, 0, 0, 0x60, -1);
  menudesc[menucount].propxpos = 0xff;
  menudesc[menucount].propypos = 0xff;
  menucount++;
  /* */
  menudesc[menucount].sprite[0] = gra_text2sprite("New game", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 0, 255, -1);
  menudesc[menucount].sprite[1] = gra_text2sprite("New game", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 255, 255, -1);
  menudesc[menucount].clickid = MAINMENU_NEWGAME;
  menudesc[menucount].propxpos = 0xff;
  menudesc[menucount].propypos = 42;
  menucount++;
  /* */
  if (rozgrywka->game == NULL) {
    menudesc[menucount].sprite[0] = gra_text2sprite("Resume",   DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 0, 90, -1);
  } else {
    menudesc[menucount].sprite[0] = gra_text2sprite("Resume",   DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 0, 255, -1);
    menudesc[menucount].sprite[1] = gra_text2sprite("Resume",   DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 255, 255, -1);
  }
  menudesc[menucount].clickid = MAINMENU_RESUME;
  menudesc[menucount].propxpos = 0xff;
  menudesc[menucount].propypos = 42+12;
  menucount++;
  /* */
  menudesc[menucount].sprite[0] = gra_text2sprite("About", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 0, 255, -1);
  menudesc[menucount].sprite[1] = gra_text2sprite("About", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 255, 255, -1);
  menudesc[menucount].clickid = MAINMENU_ABOUT;
  menudesc[menucount].propxpos = 0xff;
  menudesc[menucount].propypos = 42+12+12;
  menucount++;
  /* */
  menudesc[menucount].sprite[0] = gra_text2sprite("Exit", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 0, 255, -1);
  menudesc[menucount].sprite[1] = gra_text2sprite("Exit", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 255, 255, -1);
  menudesc[menucount].clickid = MAINMENU_QUIT;
  menudesc[menucount].propxpos = 0xff;
  menudesc[menucount].propypos = 42+12+12+12;
  menucount++;
  /* compute menu */
  menupos = ui_menu(menudesc, menucount);
  gra_playwavgz(sfx_click_wav_gz, sfx_click_wav_gz_len);

  /* free sprite resources */
  while (--menucount >= 0) {
    gra_freesprite(menudesc[menucount].sprite[0]);
    gra_freesprite(menudesc[menucount].sprite[1]);
  }
  return(menupos);
}


/* "select playfield size" menu */
static int menu_playsize(void) {
  struct ui_menu_description_t menudesc[8];
  int count = 0;
  int menupos;

  /* zero out the menudesc array */
  memset(menudesc, 0, sizeof(menudesc));

  /* frontpic goes first, so it is never rendered over a label */
  menudesc[count].sprite[0] = gra_loadgzbmp(frontpic_bmp_gz, frontpic_bmp_gz_len);
  menudesc[count].propxpos = 0;
  menudesc[count].propypos = 100;
  count++;
  menudesc[count].propxpos = 0xff;
  menudesc[count].propypos = 20;
  menudesc[count].sprite[0] = gra_text2sprite("Select your playfield size", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 0, 255, -1);
  count++;
  menudesc[count].clickid = 7;
  menudesc[count].propxpos = 0xff;
  menudesc[count].propypos = 42;
  menudesc[count].sprite[0] = gra_text2sprite("A7 (20x14)", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 0, 255, -1);
  menudesc[count].sprite[1] = gra_text2sprite("A7 (20x14)", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 255, 255, -1);
  count++;
  menudesc[count].clickid = 6;
  menudesc[count].propxpos = 0xff;
  menudesc[count].propypos = 52;
  menudesc[count].sprite[0] = gra_text2sprite("A6 (28x20)", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 0, 255, -1);
  menudesc[count].sprite[1] = gra_text2sprite("A6 (28x20)", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 255, 255, -1);
  count++;
  menudesc[count].clickid = 5;
  menudesc[count].propxpos = 0xff;
  menudesc[count].propypos = 62;
  menudesc[count].sprite[0] = gra_text2sprite("A5 (42x28)", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 0, 255, -1);
  menudesc[count].sprite[1] = gra_text2sprite("A5 (42x28)", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 255, 255, -1);
  count++;
  menudesc[count].clickid = 4;
  menudesc[count].propxpos = 0xff;
  menudesc[count].propypos = 72;
  menudesc[count].sprite[0] = gra_text2sprite("A4 (58x42)", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 0, 255, -1);
  menudesc[count].sprite[1] = gra_text2sprite("A4 (58x42)", DroidSansBold_ttf, DroidSansBold_ttf_len, 30, 0, 0, 255, 255, -1);
  count++;
  menudesc[count].clickid = 200;
  menudesc[count].propxpos = 0xff;
  menudesc[count].propypos = 92;
  menudesc[count].sprite[0] = gra_text2sprite("<< Back to main menu", DroidSansBold_ttf, DroidSansBold_ttf_len, 22, 0, 0, 0, 255, -1);
  menudesc[count].sprite[1] = gra_text2sprite("<< Back to main menu", DroidSansBold_ttf, DroidSansBold_ttf_len, 22, 0, 0, 255, 255, -1);
  count++;
  /* compute menu */
  menupos = ui_menu(menudesc, count);
  gra_playwavgz(sfx_click_wav_gz, sfx_click_wav_gz_len);

  /* free sprite resources */
  while (--count >= 0) {
    gra_freesprite(menudesc[count].sprite[0]);
    gra_freesprite(menudesc[count].sprite[1]);
  }
  if (menupos == 200) menupos = UI_MENU_BACK;
  return(menupos);
}


static int menu_about(void) {
  struct ui_menu_description_t menudesc[8];
  int count = 0;
  int menupos;
  int winwidth;
  const char *about =
  "OpenKropki is an open-source implementation of 'kropki', an eastern-European game similar to Go, but adapted to on-paper gameplay:\n"
  "\n"
  "- each player take turns into putting a dot on the field,\n"
  "- when a player manages to put his dots all around a group of enemy dots, he creates a 'base', capturing the enemy's dots that lie inside,\n"
  "- a base cannot be trespassed by the enemy, but it can be captured,\n"
  "- the game ends when there is no place left on the field: the player that captured the highest number of enemy dots wins.\n"
  "\n"
  "OpenKropki is published under the terms of the MIT license.\n"
  "homepage: http://openkropki.sourceforge.net";

  /* zero out the menudesc array */
  memset(menudesc, 0, sizeof(menudesc));

  /* get window's width */
  gra_getwinsize(&winwidth, NULL);

  /* frontpic goes first, so it is never rendered over a label */
  menudesc[count].propxpos = 0;
  menudesc[count].propypos = 100;
  menudesc[count].sprite[0] = gra_loadgzbmp(frontpic_bmp_gz, frontpic_bmp_gz_len);
  count++;
  /* OpenKropki title */
  menudesc[count].sprite[0] = gra_text2sprite("OpenKropki v" PVER, DroidSansBold_ttf, DroidSansBold_ttf_len, 40, 0, 0, 0, 0xff, -1);
  menudesc[count].propxpos = 0xff;
  menudesc[count].propypos = 16;
  count++;
  /* copyright subtitle */
  menudesc[count].sprite[0] = gra_text2sprite("Copyright © " PDATE " Mateusz Viste", DroidSansBold_ttf, DroidSansBold_ttf_len, 16, 0, 0, 0, 0x60, -1);
  menudesc[count].propxpos = 0xff;
  menudesc[count].propypos = 0xff;
  count++;
  /* about content */
  menudesc[count].sprite[0] = gra_text2sprite(about, DroidSans_ttf, DroidSans_ttf_len, 21, 0, 0, 0, 210, winwidth * 73 / 100);
  menudesc[count].propxpos = 100;
  menudesc[count].propypos = 58;
  count++;
  menudesc[count].clickid = 1;
  menudesc[count].propxpos = 0xff;
  menudesc[count].propypos = 92;
  menudesc[count].sprite[0] = gra_text2sprite("<< Back to main menu", DroidSansBold_ttf, DroidSansBold_ttf_len, 22, 0, 0, 0, 255, -1);
  menudesc[count].sprite[1] = gra_text2sprite("<< Back to main menu", DroidSansBold_ttf, DroidSansBold_ttf_len, 22, 0, 0, 255, 255, -1);
  count++;

  /* compute menu */
  menupos = ui_menu(menudesc, count);
  gra_playwavgz(sfx_click_wav_gz, sfx_click_wav_gz_len);

  /* free sprite resources */
  while (--count >= 0) {
    gra_freesprite(menudesc[count].sprite[0]);
    gra_freesprite(menudesc[count].sprite[1]);
  }
  if (menupos == 1) menupos = UI_MENU_BACK;
  return(menupos);
}


/* wait for AI thread to terminate, if it is pending and displays a message
 * about this, unless nomsg is non-zero. */
static void finish_ai_thread(struct rozgrywka_t *r) {
  if ((r != NULL) && (r->ai_thread != NULL)) {
    ui_changecursor(UI_CURSOR_WAIT);
    if (r->ai_in_progress != 0) ui_throwmsg("[ WAITING FOR A.I. ENGINE... ]");
    SDL_WaitThread(r->ai_thread, NULL);
    ui_changecursor(UI_CURSOR_NORMAL);
    r->ai_thread = NULL;
  }
}


int main(void) {
  struct rozgrywka_t rozgrywka = {0};
  int exitflag = 0;

  ui_init(WIN_WIDTH, WIN_HEIGHT);

  while (exitflag == 0) {
    int menupos;

    menupos = menu_main(&rozgrywka);

    if ((menupos == MAINMENU_QUIT) || (menupos == UI_MENU_BACK) || (menupos == UI_MENU_EXIT)) {
      break;
    } else if (menupos == MAINMENU_NEWGAME) {
      int playsize, w, h;
      /* playfield size menu */
      playsize = menu_playsize();
      switch (playsize) {
        case 4:
          w = 58;
          h = 42;
          break;
        case 5:
          w = 42;
          h = 28;
          break;
        case 6:
          w = 28;
          h = 20;
          break;
        case 7:
          w = 20;
          h = 14;
          break;
        case UI_MENU_EXIT:
          exitflag = 1;
        case UI_MENU_BACK: /* FALLTHRU */
        default:
          continue;
      }
      /* make sure no AI thread is running before freeing a previous game! */
      finish_ai_thread(&rozgrywka);
      kropki_freegame(rozgrywka.game);
      /* */
      memset(&rozgrywka, 0, sizeof(rozgrywka));
      rozgrywka.width = w;
      rozgrywka.height = h;
      rozgrywka.kratkasize = INITIAL_KRATKA_SIZE;
      rozgrywka.game = kropki_newgame(rozgrywka.width, rozgrywka.height);
    } else if (menupos == MAINMENU_ABOUT) {
      int choice = menu_about();
      if (choice == UI_MENU_EXIT) exitflag = 1;
      continue;
    } else if (menupos == MAINMENU_RESUME) {
      /* nothing to do */
    } else {
      /* abnormal choice, let's play it safe and ignore it */
      continue;
    }
    centerscrollvector(&rozgrywka);

    if (ui_setparams(rozgrywka.width, rozgrywka.height, rozgrywka.kratkasize, 2, 0xA0A0A0, COLORPLAYER1, COLORPLAYER2, 0xFEFEFE, rozgrywka.kratkasize/10, rozgrywka.kratkasize/4) != 0) return(0);

    while (exitflag == 0) {
      struct ui_event_t event;
      char msg[64] = {0};
      int msgplayercolor = 0; /* 0=no player  1=player A  2=player B */

      /* check whether the game ended (do not check if AI thinks) */
      if (rozgrywka.ai_thread == NULL) {
        int newpoints[2];
        rozgrywka.gameended = kropki_findfirstfreefield(rozgrywka.game, NULL, NULL);
        /* count points */
        kropki_getpoints(rozgrywka.game, newpoints);
        if ((newpoints[0] != rozgrywka.points[0]) || (newpoints[1] != rozgrywka.points[1])) {
          gra_playwavgz(sfx_collect_wav_gz, sfx_collect_wav_gz_len);
          rozgrywka.points[0] = newpoints[0];
          rozgrywka.points[1] = newpoints[1];
        }
      }
      if (rozgrywka.gameended) {
        if (rozgrywka.points[0] > rozgrywka.points[1]) {
          if (rozgrywka.endgamesfxplayed == 0) gra_playwavgz(sfx_winfantasia_wav_gz, sfx_winfantasia_wav_gz_len);
          msgplayercolor = 1;
          sprintf(msg, "YOU WIN!");
         } else if (rozgrywka.points[0] < rozgrywka.points[1]) {
          if (rozgrywka.endgamesfxplayed == 0) gra_playwavgz(sfx_death_wav_gz, sfx_death_wav_gz_len);
          msgplayercolor = 2;
          sprintf(msg, "OPPONENT WINS!");
        } else {
          if (rozgrywka.endgamesfxplayed == 0) gra_playwavgz(sfx_death_wav_gz, sfx_death_wav_gz_len);
          msgplayercolor = 0;
          sprintf(msg, "NOBODY WIN");
        }
        rozgrywka.endgamesfxplayed = 1; /* used to avoid playing 'win' sfx many times */
      } else {
        switch (rozgrywka.game->lastplayer) {
          case 1:
            sprintf(msg, "Opponent thinks...");
            msgplayercolor = 2;
            break;
          case 2:
            sprintf(msg, "Your turn");
            msgplayercolor = 1;
            break;
        }
      }
      redraw_screen(&rozgrywka, msg, msgplayercolor);

      /* if AI thinks, then check if results are available (but not too soon) */
      if ((rozgrywka.ai_thread != NULL) && (rozgrywka.ai_in_progress == 0)) {
        gra_playwavgz(sfx_pop3_wav_gz, sfx_pop3_wav_gz_len);
        finish_ai_thread(&rozgrywka);
      }

      /* is it time for AI to play? */
      if ((!rozgrywka.gameended) && (rozgrywka.ai_thread == NULL) && (rozgrywka.game->lastplayer == 1)) { /* AI plays */
        rozgrywka.ai_in_progress = 1; /* this is reset by the thread once job is done */
        rozgrywka.ai_thread = SDL_CreateThread(ai_caller, "openkropki_ai", &rozgrywka);
      }

      /* wait for an event */
      ui_getevent(&event, 0);

      if (event.type == Event_QUIT) {
        exitflag = 1;
        break;
      } else if ((event.type == Event_CLICK) && (rozgrywka.ai_thread == NULL) && (rozgrywka.game->lastplayer != 1)) { /* check clicks only if it's my turn to play */
        if (event.button == 1) {
          int fieldx, fieldy;
          graph_mousemap(event.x, event.y, &fieldx, &fieldy, rozgrywka.scrollvector);
          if ((fieldx >= 0) && (fieldy >= 0)) {
            kropki_setpos(rozgrywka.game, fieldx, fieldy, 1);
            gra_playwavgz(sfx_pop1_wav_gz, sfx_pop1_wav_gz_len);
          }
        } else if (event.button == 2) {
          rozgrywka.kratkasize = INITIAL_KRATKA_SIZE;
          ui_setparams(rozgrywka.width, rozgrywka.height, rozgrywka.kratkasize, 2, 0xA0A0A0, COLORPLAYER1, COLORPLAYER2, 0xFEFEFE, rozgrywka.kratkasize/10, rozgrywka.kratkasize/4);
          centerscrollvector(&rozgrywka);
        }
      } else if (event.type == Event_KEYPRESS) {
        if (event.button == 0x1B) break; /* ESC */
      } else if (event.type == Event_SWEEP) {
        /* printf("SWEEP WITH BUTTON %d BY VECTOR (%d,%d)\n", z, x, y); */
        rozgrywka.scrollvector[0] -= event.x;
        rozgrywka.scrollvector[1] -= event.y;
      } else if (event.type == Event_SCROLL) {
        int step = 1 + (rozgrywka.kratkasize / 32);
        if (event.y > 0) rozgrywka.kratkasize += step;
        if (event.y < 0) rozgrywka.kratkasize -= step;
        /* enforce upper and lower zoom boundaries */
        if (rozgrywka.kratkasize > 512) rozgrywka.kratkasize = 512;
        if (rozgrywka.kratkasize < 4) rozgrywka.kratkasize = 4;
        ui_setparams(rozgrywka.width, rozgrywka.height, rozgrywka.kratkasize, 2, 0xA0A0A0, COLORPLAYER1, COLORPLAYER2, 0xFEFEFE, rozgrywka.kratkasize/10, rozgrywka.kratkasize/4);
      } else if (event.type != Event_NONE) {
        /* puts("got an unknown event"); */
      }

    }
  }

  /* clean up and quit */
  finish_ai_thread(&rozgrywka);
  kropki_freegame(rozgrywka.game);
  gra_close();

  return(0);
}
