/*
 * This file is part of OpenKropki.
 * It provides the core logic of the board.
 *
 * 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>        /* snprintf() */
#include <stdlib.h>       /* malloc(), calloc() */
#include <string.h>       /* mempcy() */

#include "okp_parse.h"    /* okp-parsing routines */
#include "binary.h"       /* binary notation support (eg. 'bx00100011') */

#include "kropki_core.h"  /* include self for control */

/* translate (x,y) field coordinates into an offset of the 1-dimensional data array */
#define GOFFS(g, x, y) ((g->field_width + 2) * (y) + (x))


/* This function floods the playfield looking only at the two MSB bits (hence
 * you have to set them accordingly to your need before calling floodfill)  */
static void floodfill(struct kropki_game *game, int x, int y, int maxx, int maxy, int fillval) {
  if ((game->field[GOFFS(game, x, y)] & bx11000000) == 0) {
    game->field[GOFFS(game, x, y)] |= fillval;
    if (x > 0) floodfill(game, x-1, y, maxx, maxy, fillval);
    if (x < maxx) floodfill(game, x+1, y, maxx,maxy, fillval);
    if (y > 0) floodfill(game, x, y-1, maxx, maxy, fillval);
    if (y < maxy) floodfill(game, x, y+1, maxx, maxy, fillval);
  }
}


/* adds a new wall entry to the top of the walls linked list */
static void addWall(struct kropki_game *game, int x1, int y1, int x2, int y2, int player) {
  struct kropki_wallelement *newwall;
  /* first check if such wall doesn't exist already */
  for (newwall = game->walls; newwall != NULL; newwall = newwall->next) {
    if (newwall->x1 == x1-1) {
      if (newwall->y1 == y1-1) {
        if (newwall->x2 == x2-1) {
          if (newwall->y2 == y2-1) {
            /* printf("avoided double wall!\n"); */
            return;
          }
        }
      }
    }
  }
  /* such wall doesn't exist yet - create it */
  newwall = malloc(sizeof(struct kropki_wallelement));
  newwall->x1 = x1 - 1;
  newwall->y1 = y1 - 1;
  newwall->x2 = x2 - 1;
  newwall->y2 = y2 - 1;
  newwall->player = player;
  newwall->next = game->walls;
  game->field[GOFFS(game, x1, y1)] |= (player << 4);
  game->field[GOFFS(game, x2, y2)] |= (player << 4);
  game->walls = newwall;
  /* printf("NEW WALL: (%d,%d) - (%d,%d)\n", newwall->x1, newwall->y1, newwall->x2, newwall->y2); */
}


static void createwallifneeded(struct kropki_game *g, int x0, int y0, int xvect, int yvect) {
  /* Checks fields around a newly captured dot and builds walls whenever
   * necessary. This function processes only a quarter of a circle around the
   * x0,y0 dot! Which quarter it will process depends on xvect and yvect.
   *
   *    2  .
   *    1  . .
   *       x . .
   *
   *         1 2
   */
  int x1, x2, y1, y2;

  /* prepare the coordinates system */
  x1 = x0 + xvect;
  x2 = x0 + xvect + xvect;
  y1 = y0 + yvect;
  y2 = y0 + yvect + yvect;

  /* can I build a diagonal wall? (x1,y0)-(x0,y1) */
  if (((g->field[GOFFS(g, x1, y0)] & bx11000000) == bx10000000) && ((g->field[GOFFS(g, x0, y1)] & bx11000000) == bx10000000)) {
    /* if the dot behind got captured as well, then do not build a wall here */
    if ((g->field[GOFFS(g, x1, y1)] & bx11000000) != bx01000000) {
      addWall(g, x1, y0, x0, y1, g->lastplayer);
    }
    return; /* stop there - no need to look further once we have a direct diagonal wall */
  }

  /* perhaps a horizontal wall? (x,y)-(x3,y3) */
  if (((g->field[GOFFS(g, x1, y0)] & bx11000000) == bx10000000) && ((g->field[GOFFS(g, x1, y1)] & bx11000000) == bx10000000)) {
    if ((g->field[GOFFS(g, x2, y0)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
      addWall(g, x1, y0, x1, y1, g->lastplayer);
    }
  }

  /* what about a horizontal wall? */
  if (((g->field[GOFFS(g, x1, y1)] & bx11000000) == bx10000000) && ((g->field[GOFFS(g, x0, y1)] & bx11000000) == bx10000000)) {
    if ((g->field[GOFFS(g, x0, y2)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
      addWall(g, x1, y1, x0, y1, g->lastplayer);
    }
  }
}


/* this is called by lookfornewcaptures() iteratively for every field on the
 * playfield, after having set playablefields to 0 and playablefieldx to -1 */
static void update_playablefields(struct kropki_game *g, int x, int y) {
  if ((x < 1) || (y < 1) || (x > g->field_width) || (y > g->field_height)) return;
  if ((g->field[GOFFS(g, x, y)] & bx00001111) != 0) return;
  g->playablefields++;
  if (g->playablefieldx >= 0) return;
  g->playablefieldx = x - 1;
  g->playablefieldy = y - 1;
}


/* this is executed each time a move is performed, to check for newly created
 * walls, captured dots, update scores... */
static void lookfornewcaptures(struct kropki_game *game, int flags) {
  int xx, yy, newcapture = 0;
  /* check if a base has been closed: prepare flood terrain */
  /* ALSO: locate playable fields (but be prepared to do this again later if
   *       anything gets captured) */
  game->playablefields = 0;
  game->playablefieldx = -1;
  game->playablefieldy = -1;
  for (yy = 0; yy <= game->field_height + 1; yy++) {
    for (xx = 0; xx <= game->field_width + 1; xx++) {
      /* first reset the two MSB bits */
      game->field[GOFFS(game, xx, yy)] &= bx00111111;
      /* if it's a player's non-captive dot, set the MSB bit */
      if ((game->field[GOFFS(game, xx, yy)] & bx00001111) == game->lastplayer) {
        game->field[GOFFS(game, xx, yy)] |= bx10000000;
      }
      update_playablefields(game, xx, yy);
    }
  }
  /* apply the flood */
  floodfill(game, 0, 0, game->field_width + 1, game->field_height + 1, bx11000000);
  /* check all (non-captured) fields with opponent's dots or walls - is there
   * any non-flooded? if so, it got captured now: flood with another value to
   * map the captured area */
  for (yy = 2; yy <= game->field_height; yy++) {  /* start at 2 because margins do not have */
    for (xx = 2; xx <= game->field_width; xx++) { /* dots and borders can never be captured */
      if ((game->field[GOFFS(game, xx, yy)] & bx11001111) == (3 - game->lastplayer)) {
        /* printf("New encircled area found starting at [%d.%d]\n", xx, yy); */
        floodfill(game, xx, yy, game->field_width + 1, game->field_height + 1, bx01000000);
        newcapture = 1; /* remember that something got captures so I should look for walls next */
      }
    }
  }
  /* if nothing new captured, stop here (otherwise look what walls to build) */
  if (newcapture == 0) return;
  /* set ownership of captured fields and build walls - and update points */
  /* ALSO: locate playable fields (again, yes - previous results are invalid
   *       since some areas got captured in the meantime) */
  game->playablefields = 0;
  game->playablefieldx = -1;
  game->playablefieldy = -1;
  for (yy = 1; yy <= game->field_height; yy++) {  /* I don't check margins */
    for (xx = 1; xx <= game->field_width; xx++) {
      unsigned char fieldval = game->field[GOFFS(game, xx, yy)];
      /* is it a newly-captured area? */
      if ((fieldval & bx11000000) == bx01000000) {
        /* was this a dot (or wall) captured by opponent? subtract a point then */
        if ((fieldval & 15) == bx00000110) game->score[0]--; /* player b dot captured by player a */
        if ((fieldval & 15) == bx00001001) game->score[1]--; /* player a dot captured by player b */
        /* mark the field as 'captured' by player) */
        fieldval &= bx11110011; /* reset ownership bits in case the dot was captured by someone else before */
        fieldval |= (game->lastplayer << 2);
        game->field[GOFFS(game, xx, yy)] = fieldval; /* update field on playfield */
        /* update score (add a point if necessary) */
        if ((fieldval & 15) == bx00000110) game->score[0]++; /* player b dot captured by player a */
        if ((fieldval & 15) == bx00001001) game->score[1]++; /* player a dot captured by player b */
        /* if walls not disabled, check what kind of wall I should build around (if any) */
        if (flags & kropki_NO_WALLS) continue;
        createwallifneeded(game, xx, yy, -1, -1); /* process upper left corner */
        createwallifneeded(game, xx, yy,  1, -1); /* process upper right corner */
        createwallifneeded(game, xx, yy, -1,  1); /* process lower left corner */
        createwallifneeded(game, xx, yy,  1,  1); /* process lower right corner */
      }
      /* once the wall business is done, update playable fields */
      update_playablefields(game, xx, yy);
    }
  }
}


static int computegamestructsize(int width, int height) {
  /* the internal datatable have a 1-position margin around the playable field
   * to make it easier to detect closed areas */
  return((int)sizeof(struct kropki_game) + (height + 2) * (width + 2));
}


struct kropki_game *kropki_newgame(int width, int height, int whoplaysfirst) {
  struct kropki_game *res;
  /* validate arguments */
  if ((width < 1) || (height < 1) || (whoplaysfirst < 0) || (whoplaysfirst > 2)) return(NULL);
  /* alloc and fill */
  res = calloc(1, (size_t)computegamestructsize(width, height));
  if (res == NULL) return(NULL);
  res->field_width = width;
  res->field_height = height;
  res->walls = NULL;
  res->nextplayer = whoplaysfirst;
  res->lastplayer = 0;   /* nobody played yet */
  res->lastmovex = 0;
  res->lastmovey = 0;
  res->playablefields = width * height;
  return(res);
}


void kropki_freegame(struct kropki_game *game) {
  struct kropki_wallelement *nextwall;
  if (game == NULL) return;
  /* free the memory allocated for the walls linked list */
  while (game->walls != NULL) {
    nextwall = game->walls->next;
    free(game->walls);
    game->walls = nextwall;
  }
  /* free the struct itself */
  free(game);
}


int kropki_getpos(const struct kropki_game *game, int x, int y) {
  if (game == NULL) return(-1);
  if ((x >= game->field_width) || (y >= game->field_height)) return(-1);
  if ((x < 0) || (y < 0)) return(-1);
  return(KROPKI_GETPOS(game, x, y)); /* return only 6 lowest bits - other contain potential garbage */
}


int kropki_setpos(struct kropki_game *game, int x, int y, int player, int flags) {
  if (game == NULL) return(-1); /* no allocated playfield */
  /* out of bounds checks */
  if ((x >= game->field_width) || (y >= game->field_height)) return(-1);
  if ((x < 0) || (y < 0)) return(-2);
  /* player validation */
  if ((player < 1) || (player > 2)) return(-3);
  if ((game->nextplayer != 0) && (player != game->nextplayer)) return(-4);
  /* 'normalize' the coordinates (because of our 1-dot margin around the playfield) */
  x += 1;
  y += 1;
  /* is the field playable? */
  if ((game->field[GOFFS(game, x, y)] & bx00001111) != 0) return(-5);  /* not an empty (or non-captured) field */
  /* */
  game->field[GOFFS(game, x, y)] |= player;
  game->nextplayer = 3 - player; /* 1->2  2->1*/
  game->lastplayer = player;
  game->lastmovex = x - 1;
  game->lastmovey = y - 1;
  /* printf("New dot at [%d:%d]\n", x, y); */
  lookfornewcaptures(game, flags);
  /* is the game over? if so, reset nextplayer */
  if (game->playablefieldx < 0) game->nextplayer = 0;
  return(0);
}


int kropki_pass(struct kropki_game *g, int player) {
  if ((player < 1) || (player > 2)) return(-1);
  if ((g->nextplayer != 0) && (g->nextplayer != player)) return(-2);
  g->nextplayer = 3 - player; /* 1->2  2->1*/
  return(0);
}


/* copies an entire game (including the field, walls, etc) */
struct kropki_game *kropki_copygame(const struct kropki_game *game, int flags) {
  int gamebytesize;
  struct kropki_game *res;
  struct kropki_wallelement *origwall, *newwall;
  if (game == NULL) return(NULL);
  /* allocate and check */
  gamebytesize = computegamestructsize(game->field_width, game->field_height);
  res = malloc((size_t)gamebytesize);
  if (res == NULL) return(NULL);
  /* copy flat data over and clear out the walls ptr */
  memcpy(res, game, (size_t)gamebytesize);
  res->walls = NULL;
  if (flags & kropki_NO_WALLS) return(res);
  /* copy the walls linked list */
  for (origwall = game->walls; origwall != NULL; origwall = origwall->next) {
    newwall = malloc(sizeof(struct kropki_wallelement));
    memcpy(newwall, origwall, sizeof(struct kropki_wallelement));
    /* add the new node to linked list */
    newwall->next = res->walls;
    res->walls = newwall;
  }
  return(res);
}


/* finds the first available position to play. returns 0 on success, non-zero otherwise. */
int kropki_findfirstfreefield(const struct kropki_game *game, int *resx, int *resy) {
  if (game->playablefieldx < 0) return(-1);
  if (resx != NULL) *resx = game->playablefieldx;
  if (resy != NULL) *resy = game->playablefieldy;
  return(0);
}


/* set name (nick) of player p (0 or 1) */
void kropki_setplayername(struct kropki_game *g, int p, const char *name) {
  if ((p < 0) || (p > 1)) return;
  snprintf(g->playername[p], sizeof(g->playername[p]), "%s", name);
}


/* set name (nick) of player p (0 or 1) */
const char *kropki_getplayername(const struct kropki_game *g, int p) {
  if ((p < 0) || (p > 1)) return(NULL);
  return(g->playername[p]);
}


/****************************************************************************
 *              BELOW ARE EXPORT-IMPORT OKP-RELATED FUNCTIONS               *
 ****************************************************************************/


/* return first chunk of t type in list l, or NULL if not found */
static struct okp_chunklist *findchunk(struct okp_chunklist *l, enum okp_chunktype t) {
  struct okp_chunklist *c;
  for (c = l; c != NULL; c = c->next) {
    if (c->id == t) return(c);
  }
  return(NULL);
}


struct kropki_game *kropki_okp2struct(const char *okp) {
  struct kropki_game *game;
  struct okp_chunklist *okp_list, *okp_chunk;

  okp_list = okp_parse(okp);
  if (okp_list == NULL) {
    return(NULL);
  }

  /* look for field size */
  okp_chunk = findchunk(okp_list, OKP_FS);
  if (okp_chunk == NULL) {
    okp_freelist(okp_list);
    return(NULL);
  }

  /* init a new game struct */
  game = kropki_newgame(okp_chunk->data.fs.w, okp_chunk->data.fs.h, 0);

  /* look for field content */
  okp_chunk = findchunk(okp_list, OKP_FC);
  if (okp_chunk != NULL) {
    int x, y, i = 0;
    if (strlen(okp_chunk->data.fc.playfield) != (unsigned long)(game->field_width * game->field_height)) {
      /* inconsistent playfield lenght */
      kropki_freegame(game);
      okp_freelist(okp_list);
      return(NULL);
    }
    for (y = 0; y < game->field_height; y++) {
      for (x = 0; x < game->field_width; x++) {
        char okpchar = okp_chunk->data.fc.playfield[i++];
        unsigned char wallflag = 0;
        unsigned char capturedflag = 0;
        unsigned char fieldval;
        if ((okpchar >= 'A') && (okpchar <= 'Z')) {
          okpchar += 32;
          wallflag = 1;
        }
        if ((okpchar >= 'd') && (okpchar <= 'f')) {
          okpchar -= 3;
          capturedflag = 1 << 2;
          if (wallflag) wallflag = 1 << 4;
        } else if ((okpchar >= 'g') && (okpchar <= 'i')) {
          okpchar -= 6;
          capturedflag = 2 << 2;
          if (wallflag) wallflag = 2 << 4;
        }
        /* the resulting char must be a, b or c */
        if (okpchar == 'a') {
          fieldval = kropki_PL1_DOT;
          if (wallflag) wallflag = 1 << 4;
        } else if (okpchar == 'b') {
          fieldval = kropki_PL2_DOT;
          if (wallflag) wallflag = 2 << 4;
        } else if (okpchar == 'c') {
          fieldval = kropki_EMPTY;
        } else { /* invalid value */
          kropki_freegame(game);
          okp_freelist(okp_list);
          return(NULL);
        }
        /* compute the game->field value */
        game->field[GOFFS(game, x+1, y+1)] = fieldval | capturedflag | wallflag;
      }
    }
  }

  /* look for last move */
  okp_chunk = findchunk(okp_list, OKP_LM);
  if (okp_chunk != NULL) {
    game->lastmovex = okp_chunk->data.lm.x;
    game->lastmovey = okp_chunk->data.lm.y;
    game->lastplayer = okp_chunk->data.lm.p + 1;
  }

  /* look for scores */
  okp_chunk = findchunk(okp_list, OKP_SC);
  if (okp_chunk != NULL) {
    game->score[0] = okp_chunk->data.sc.a;
    game->score[1] = okp_chunk->data.sc.b;
  }

  /* fill in nextplayer based on lastmove */
  if (game->lastplayer != 0) game->nextplayer = 3 - game->lastplayer;

  /* count playable fields and set playablefieldx/y */
  {
    int xx, yy, playablefound = 0;
    game->playablefieldx = -1;
    game->playablefieldy = -1;
    game->playablefields = 0;
    for (yy = 0; yy < game->field_height; yy++) {
      for (xx = 0; xx < game->field_width; xx++) {
        if (kropki_getpos(game, xx, yy) != 0) continue;
        game->playablefields++;
        if (!playablefound) {
          game->playablefieldx = xx;
          game->playablefieldy = yy;
          playablefound = 1;
        }
      }
    }
  }

  /* reset nextplayer if game ended */
  if (game->playablefields == 0) game->nextplayer = 0;

  /* handle the 'pa' and 'pb' chunks */
  okp_chunk = findchunk(okp_list, OKP_PA);
  if (okp_chunk != NULL) {
    snprintf(game->playername[0], sizeof(game->playername[0]), "%s", okp_chunk->data.pa.name);
  }
  okp_chunk = findchunk(okp_list, OKP_PB);
  if (okp_chunk != NULL) {
    snprintf(game->playername[1], sizeof(game->playername[1]), "%s", okp_chunk->data.pb.name);
  }

  /* process walls */
  for (okp_chunk = okp_list; okp_chunk != NULL; okp_chunk = okp_chunk->next) {
    struct kropki_wallelement *we;
    if (okp_chunk->id != OKP_WL) continue; /* skip non-walls */
    we = calloc(sizeof(struct kropki_wallelement), 1);
    we->x1 = okp_chunk->data.wl.x1;
    we->y1 = okp_chunk->data.wl.y1;
    we->x2 = okp_chunk->data.wl.x2;
    we->y2 = okp_chunk->data.wl.y2;
    we->player = kropki_getpos(game, we->x1, we->y1) & 3; /* find out who the wall belongs to */
    /* attach the new wall to game */
    we->next = game->walls;
    game->walls = we;
  }

  okp_freelist(okp_list);

  return(game);
}

/* return an OKP string that represents the kropki game present in struct g */
char *kropki_struct2okp(const struct kropki_game *g) {
  int x, y;
  size_t rsz, i;
  char *r;
  struct kropki_wallelement *we;

  /* alloc the result string */
  rsz = 64 + (size_t)(g->field_height * g->field_width);
  r = calloc(rsz, 1);
  if (r == NULL) return(NULL);

  /* generate the easy parts */
  i = snprintf(r, rsz, "fs%d.%d$sc%d.%d$", g->field_width, g->field_height, g->score[0], g->score[1]);
  if (g->lastplayer > 0) {
    i += snprintf(r + i, rsz - i, "lm%d.%d.%d$", g->lastmovex, g->lastmovey, g->lastplayer - 1); /* lastplayer is 0..1 for OKP, but 1..2 for struct */
  }

  /* add player types */
  i += snprintf(r + i, rsz - i, "pa%s$pb%s$", g->playername[0], g->playername[1]);

  /* compute field content */
  i += snprintf(r + i, rsz - i, "fc");
  for (y = 0; y < g->field_height; y++) {
    for (x = 0; x < g->field_width; x++) {
      unsigned char fieldval = kropki_getpos(g, x, y);
      char resultval = 0;
      /* is there a dot? */
      switch (fieldval & 3) {
        case 0:
          resultval = 'c';
          break;
        case 1:
          resultval = 'a';
          break;
        case 2:
          resultval = 'b';
          break;
      }
      /* is this field captured? */
      switch ((fieldval >> 2) & 3) {
        case 0:
          break;
        case 1:
          resultval += 3;
          break;
        case 2:
          resultval += 6;
          break;
      }
      /* is this a wall? */
      if ((fieldval & bx00110000) != 0) resultval -= 32;
      /* add to string */
      i += snprintf(r + i, rsz - i, "%c", resultval);
    }
  }
  i += snprintf(r + i, rsz - i, "$");

  /* add walls */
  for (we = g->walls; we != NULL; we = we->next) {
    /* if there's less than 32 bytes left in r, resize it */
    if (rsz - i < 32) {
      r = realloc(r, rsz + 64);
      rsz += 64;
    }
    /* add wall element */
    i += snprintf(r + i, rsz - i, "wl%d.%d.%d.%d$", we->x1, we->y1, we->x2, we->y2);
  }

  return(r);
}
