/*
 * This file is part of OpenKropki.
 * It provides the core logic of the board.
 *
 * Copyright (C) 2014-2022 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 "kropcore.h"     /* include self for control */

#define GOFFS(g,x,y) KROPKI_GOFFS(g,x,y)


static void floodfill(struct kropki_game *game, unsigned char x, unsigned char y, unsigned char maxx, unsigned char maxy, unsigned char fillval) {
  unsigned short offset;

  offset = KROPKI_GOFFS(game, x, y);
  REP:

  game->field[offset] |= fillval;

  if ((x > 0) && ((game->field[offset - 1] & KROPKI_FLGX) == 0)) floodfill(game, x-1, y, maxx, maxy, fillval);
  if ((x < maxx) && ((game->field[offset + 1] & KROPKI_FLGX) == 0)) floodfill(game, x+1, y, maxx, maxy, fillval);
  if ((y > 0) && ((game->field[KROPKI_GOFFS(game, x, y-1)] & KROPKI_FLGX) == 0)) floodfill(game, x, y-1, maxx,maxy, fillval);

  offset = KROPKI_GOFFS(game, x, y+1);
  if ((y < maxy) && ((game->field[offset] & KROPKI_FLGX) == 0)) {
    y++;
    goto REP;
  }
}


/* looks into the list of walls for a match and return a pointer to it if found, NULL otherwise
 * player can be 1 or 2 for an actual player, or 0 for 'any' */
static struct kropki_wallelement *findWall(struct kropki_game *g, unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2, unsigned char player) {
  struct kropki_wallelement *r;
  for (r = g->walls; r != NULL; r = r->next) {
    /* skip right away if player does not match */
    if ((player != 0) && (r->player != player)) continue;
    /* look for exact match */
    if ((r->x1 == x1) &&
        (r->y1 == y1) &&
        (r->x2 == x2) &&
        (r->y2 == y2)) return(r);
    /* look for reverse match (wall A-B is same as wall B-A) */
    if ((r->x1 == x2) &&
        (r->y1 == y2) &&
        (r->x2 == x1) &&
        (r->y2 == y1)) return(r);
  }
  /* no match found */
  return(NULL);
}


/* adds a new wall entry to the top of the walls linked list */
static void addWall(struct kropki_game *game, unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2, unsigned char player) {
  struct kropki_wallelement *newwall;

  /* normalize x1,y1,x2,y2 so it reflect coordinates without the flood margins */
  x1--;
  y1--;
  x2--;
  y2--;

  /* first check if such wall doesn't exist already */
  if (findWall(game, x1, y1, x2, y2, 0) != NULL) return;

  /* such wall doesn't exist yet - create it */
  newwall = malloc(sizeof(struct kropki_wallelement));
  if (newwall == NULL) return; /* OOM: skip this wall rather than crash */
  newwall->x1 = x1;
  newwall->y1 = y1;
  newwall->x2 = x2;
  newwall->y2 = y2;
  newwall->player = player;
  newwall->next = game->walls;
  game->field[GOFFS(game, x1 + 1, y1 + 1)] |= KROPKI_WALL;
  game->field[GOFFS(game, x2 + 1, y2 + 1)] |= KROPKI_WALL;
  game->walls = newwall;

  /* update the lastchg rectangle so the UI can redraw selected parts */
  if (x1 < game->chgrectx1) game->chgrectx1 = x1;
  if (x2 < game->chgrectx1) game->chgrectx1 = x2;
  if (x1 > game->chgrectx2) game->chgrectx2 = x1;
  if (x2 > game->chgrectx2) game->chgrectx2 = x2;
  if (y1 < game->chgrecty1) game->chgrecty1 = y1;
  if (y2 < game->chgrecty1) game->chgrecty1 = y2;
  if (y1 > game->chgrecty2) game->chgrecty2 = y1;
  if (y2 > game->chgrecty2) game->chgrecty2 = y2;

  /* printf("NEW WALL: (%d,%d) - (%d,%d)\n", newwall->x1, newwall->y1, newwall->x2, newwall->y2); */
}


static void createwallifneeded(struct kropki_game *g, unsigned char x0, unsigned char y0, signed char xvect, signed char 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
   */
  unsigned char 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)] & KROPKI_FLGAB) == KROPKI_FLGB) && ((g->field[GOFFS(g, x0, y1)] & KROPKI_FLGAB) == KROPKI_FLGB)) {
    /* if the dot behind got captured as well, then do not build a wall here */
    if ((g->field[GOFFS(g, x1, y1)] & KROPKI_FLGAB) != KROPKI_FLGA) {
      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)] & KROPKI_FLGAB) == KROPKI_FLGB) && ((g->field[GOFFS(g, x1, y1)] & KROPKI_FLGAB) == KROPKI_FLGB)) {
    if ((g->field[GOFFS(g, x2, y0)] & KROPKI_FLGAB) != KROPKI_FLGA) { /* 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)] & KROPKI_FLGAB) == KROPKI_FLGB) && ((g->field[GOFFS(g, x0, y1)] & KROPKI_FLGAB) == KROPKI_FLGB)) {
    if ((g->field[GOFFS(g, x0, y2)] & KROPKI_FLGAB) != KROPKI_FLGA) { /* 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 */
static void update_playablefields(struct kropki_game *g, unsigned char x, unsigned char y) {
  if ((x < 1) || (y < 1) || (x > g->field_width) || (y > g->field_height)) return;
  if ((g->field[GOFFS(g, x, y)] & (KROPKI_DOTX|KROPKI_CAPX)) != 0) return;
  g->playablefields++;
  if (g->playablefields > 1) 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, unsigned char flags) {
  int xx, yy;
  unsigned char 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;
  for (yy = 0; yy <= game->field_height + 1; yy++) {
    for (xx = 0; xx <= game->field_width + 1; xx++) {
      /* first reset the three MSB bits */
      game->field[GOFFS(game, xx, yy)] &= (0xff ^ KROPKI_FLGX);
      /* if it's a player's non-captive dot, set the MSB bit */
      if ((game->field[GOFFS(game, xx, yy)] & (KROPKI_DOTX|KROPKI_CAPX)) == game->lastplayer) {
        game->field[GOFFS(game, xx, yy)] |= KROPKI_FLGB;
      }
      update_playablefields(game, xx, yy);
    }
  }
  /* apply the flood */
  floodfill(game, 0, 0, game->field_width + 1, game->field_height + 1, KROPKI_FLGAB);
  /* 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)] & (KROPKI_FLGAB|KROPKI_CAPX|KROPKI_DOTX)) == (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, KROPKI_FLGA);
        newcapture = 1; /* remember that something got captured 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;
  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 & KROPKI_FLGAB) == KROPKI_FLGA) {
        /* was this a dot (or wall) captured by opponent? subtract a point then */
        if ((fieldval & (KROPKI_DOTX|KROPKI_CAPX)) == (KROPKI_DOT2|KROPKI_CAP1)) game->score[0]--; /* player 2 dot captured by player 1 */
        if ((fieldval & (KROPKI_DOTX|KROPKI_CAPX)) == (KROPKI_DOT1|KROPKI_CAP2)) game->score[1]--; /* player 1 dot captured by player 2 */
        /* mark the field as 'captured' by player) */
        fieldval &= (0xff ^ KROPKI_CAPX); /* 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 & (KROPKI_DOTX|KROPKI_CAPX)) == (KROPKI_DOT2|KROPKI_CAP1)) game->score[0]++; /* player 2 dot captured by player 1 */
        if ((fieldval & (KROPKI_DOTX|KROPKI_CAPX)) == (KROPKI_DOT1|KROPKI_CAP2)) game->score[1]++; /* player 1 dot captured by player 2 */
        /* 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 size_t computegamestructsize(unsigned char width, unsigned char height) {
  size_t res;
  /* the internal datatable have a 1-position margin around the playable field
   * to make it easier to detect closed areas */
  res = width + 2;
  res *= height + 2;
  res += sizeof(struct kropki_game);
  return(res);
}


struct kropki_game *kropki_newgame(unsigned char width, unsigned char height, unsigned char whoplaysfirst) {
  struct kropki_game *res;
  /* validate arguments */
  if ((width < 1) || (height < 1) || (whoplaysfirst > 2)) return(NULL);
  /* alloc and fill */
  res = calloc(computegamestructsize(width, height), 1);
  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;
  res->playablefields *= 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, unsigned char x, unsigned char y) {
  if (game == NULL) return(-1);
  if ((x >= game->field_width) || (y >= game->field_height)) return(-1);
  return(KROPKI_GETPOS(game, x, y)); /* return only 5 lowest bits - other contain potential garbage */
}


int kropki_setpos(struct kropki_game *game, unsigned char x, unsigned char y, unsigned char player, unsigned char flags) {
  if (game == NULL) return(-1); /* no allocated playfield */
  /* out of bounds checks */
  if ((x >= game->field_width) || (y >= game->field_height)) return(-1);
  /* player validation */
  if ((player < 1) || (player > 2)) return(-3);
  if ((game->nextplayer != 0) && (player != game->nextplayer)) return(-4);
  /* 'normalize' the coordinates (because of the 1-dot margin around the playfield) */
  x += 1;
  y += 1;
  /* is the field playable? */
  if ((game->field[GOFFS(game, x, y)] & (KROPKI_DOTX|KROPKI_CAPX)) != 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;
  game->chgrectx1 = x - 1;
  game->chgrectx2 = x - 1;
  game->chgrecty1 = y - 1;
  game->chgrecty2 = y - 1;
  /* printf("New dot at [%d:%d]\n", x, y); */
  lookfornewcaptures(game, flags);
  /* is the game over? if so, reset nextplayer */
  if (game->playablefields == 0) game->nextplayer = 0;
  return(0);
}


/* skip player's turn (0 = skip whoever's turn it is) */
int kropki_pass(struct kropki_game *g, unsigned char player) {
  if (player > 2) return(-1);
  if (player == 0) {
    if (g->nextplayer != 0) g->nextplayer = 3 - g->nextplayer; /* 1->2  2->1*/
    return(0);
  }
  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, unsigned char flags) {
  size_t 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(gamebytesize);
  if (res == NULL) return(NULL);

  /* copy flat data over and clear out the walls ptr */
  memcpy(res, game, 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));
    if (newwall == NULL) {
      kropki_freegame(res);
      return(NULL);
    }
    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,  unsigned char *resx,  unsigned char *resy) {
  if (game->playablefields == 0) return(-1);
  if (resx != NULL) *resx = game->playablefieldx;
  if (resy != NULL) *resy = game->playablefieldy;
  return(0);
}
