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

#include "kropki_core.h"  /* include self for control */
#include "binary.h"       /* binary notation support (eg. 'bx00100011') */


/* 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 MSB bit (therefore *
 * you have to set it 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); */
}


/* removes any wall that is using the dot at [x,y] */
/* static void remWall(int x, int y) {
  struct kropki_wallelement *curwall = kropki_walls, *lastwall = NULL;
  while (curwall != NULL) {
    if (((curwall->x1 == x - 1) && (curwall->y1 == y - 1)) || ((curwall->x2 == x - 1) && (curwall->y2 == y - 1))) {
        if (lastwall == NULL) { */ /* this is the first entry in the list */
/*            kropki_walls = curwall->next;
            free(curwall);
            curwall = kropki_walls;
          } else {
            lastwall->next = curwall->next;
            free(curwall);
            curwall = lastwall;
        }
      } else { */ /* no need to remove this wall */
/*        lastwall = curwall;
        curwall = curwall->next;
    }
  }
  plansza[x][y] &= bx11001111;
} */


/* This looks at existing walls, and remove these who are not part of a polygon (only one connection) */
/* static void removeSingleWalls(void) {
  int x, y, z;
  for (x = 1; x < plansza_width; x++) {
    for (y = 1; y < plansza_height; y++) {
      if ((plansza[x][y] & bx00110000) != 0) { */ /* found a dot being part of a wall - check it */
/*        z = 0;
        if (plansza[x][y] & bx00110000 & plansza[x-1][y-1]) z += 1;
        if (plansza[x][y] & bx00110000 & plansza[x][y-1]) z += 1;
        if (plansza[x][y] & bx00110000 & plansza[x+1][y-1]) z += 1;
        if (plansza[x][y] & bx00110000 & plansza[x-1][y]) z += 1;
        if (plansza[x][y] & bx00110000 & plansza[x+1][y]) z += 1;
        if (plansza[x][y] & bx00110000 & plansza[x-1][y+1]) z += 1;
        if (plansza[x][y] & bx00110000 & plansza[x][y+1]) z += 1;
        if (plansza[x][y] & bx00110000 & plansza[x+1][y+1]) z += 1;
        if (z < 2) { */ /* only one connection found - remove this wall! */
          /* printf("walls concerning [%d,%d] should be removed\n", x, y); */
/*          remWall(x,y);
        }
      }
    }
  }
} */


/* this is executed each time a move is performed, to check for newly created
 * walls, captured dots, update scores... */
static void checkgame(struct kropki_game *game) {
  int xx, yy;
  /* Now check if a base have been closed */
  for (xx = 0; xx <= game->field_width + 1; xx++) {
    for (yy = 0; yy <= game->field_height + 1; yy++) {
      game->field[GOFFS(game, xx, yy)] &= bx00111111;   /* first reset the two MSB bits */
      if (((game->field[GOFFS(game, xx, yy)] & bx00000011) == game->lastplayer) && ((game->field[GOFFS(game, xx, yy)] & bx00001100) == 0)) { /* if it's a player's non-captive dot, set the MSB bit */
        game->field[GOFFS(game, xx, yy)] |= bx10000000;
      }
    }
  }
  floodfill(game, 0, 0, game->field_width + 1, game->field_height + 1, bx11000000); /* apply the flood */
  /* check all active field with opponent's dots - is there any non-flooded (and not captured yet)? if so, flood with another value to detect captured areas */
  for (xx = 0; xx <= game->field_width; xx++) {
    for (yy = 0; yy <= game->field_height; yy++) {
      if ((game->field[GOFFS(game, xx, yy)] & bx11001111) == (3 - game->lastplayer)) {
        /* printf("Dot encircled: [%d.%d]\n", xx, yy); */
        floodfill(game, xx, yy, game->field_width + 1, game->field_height + 1, bx01000000);
      }
    }
  }
  /* Finally, build walls */
  for (xx = 2; xx < game->field_width; xx++) { /* I don't check near borders, because there is no way to capture a dot near border anyway */
    for (yy = 2; yy < game->field_height; yy++) {
      if ((game->field[GOFFS(game, xx, yy)] & bx11000000) == bx01000000) { /* detect captured areas and compute walls */
        /* don't forget to disable the dot now (mark it 'captured' by player) */
        game->field[GOFFS(game, xx, yy)] |= (game->lastplayer << 2);
        /* check what kind of wall we have around (if any) */
        /* upper left corner */
        if (((game->field[GOFFS(game, xx - 1, yy)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx, yy - 1)] & bx11000000) == bx10000000)) { /*  /  */
            if ((game->field[GOFFS(game, xx - 1, yy - 1)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
              addWall(game, xx - 1, yy, xx, yy - 1, game->lastplayer);
            }
          } else {
            if (((game->field[GOFFS(game, xx - 1, yy)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx - 1, yy - 1)] & bx11000000) == bx10000000)) { /* | */
              if ((game->field[GOFFS(game, xx - 2, yy)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
                addWall(game, xx - 1, yy, xx - 1, yy - 1, game->lastplayer);
              }
            }
            if (((game->field[GOFFS(game, xx - 1, yy - 1)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx, yy - 1)] & bx11000000) == bx10000000)) { /* - */
              if ((game->field[GOFFS(game, xx, yy - 2)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
                addWall(game, xx - 1, yy - 1, xx, yy - 1, game->lastplayer);
              }
            }
        }
        /* upper right corner */
        if (((game->field[GOFFS(game, xx + 1, yy)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx, yy - 1)] & bx11000000) == bx10000000)) { /*  /  */
            if ((game->field[GOFFS(game, xx + 1, yy - 1)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
              addWall(game, xx + 1, yy, xx, yy - 1, game->lastplayer);
            }
          } else {
            if (((game->field[GOFFS(game, xx + 1, yy)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx + 1, yy - 1)] & bx11000000) == bx10000000)) { /* | */
              if ((game->field[GOFFS(game, xx + 2, yy)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
                addWall(game, xx + 1, yy, xx + 1, yy - 1, game->lastplayer);
              }
            }
            if (((game->field[GOFFS(game, xx + 1, yy - 1)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx, yy - 1)] & bx11000000) == bx10000000)) { /* - */
              if ((game->field[GOFFS(game, xx, yy - 2)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
                addWall(game, xx + 1, yy - 1, xx, yy - 1, game->lastplayer);
              }
            }
        }
        /* lower left corner */
        if (((game->field[GOFFS(game, xx - 1, yy)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx, yy + 1)] & bx11000000) == bx10000000)) { /*  /  */
            if ((game->field[GOFFS(game, xx - 1, yy + 1)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
              addWall(game, xx - 1, yy, xx, yy + 1, game->lastplayer);
            }
          } else {
            if (((game->field[GOFFS(game, xx - 1, yy)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx - 1, yy + 1)] & bx11000000) == bx10000000)) { /* | */
              if ((game->field[GOFFS(game, xx - 2, yy)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
                addWall(game, xx - 1, yy, xx - 1, yy + 1, game->lastplayer);
              }
            }
            if (((game->field[GOFFS(game, xx - 1, yy + 1)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx, yy + 1)] & bx11000000) == bx10000000)) { /* - */
              if ((game->field[GOFFS(game, xx, yy + 2)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
                addWall(game, xx - 1, yy + 1, xx, yy + 1, game->lastplayer);
              }
            }
        }
        /* lower right corner */
        if (((game->field[GOFFS(game, xx + 1, yy)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx, yy + 1)] & bx11000000) == bx10000000)) { /*  /  */
            if ((game->field[GOFFS(game, xx + 1, yy + 1)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
              addWall(game, xx + 1, yy, xx, yy + 1, game->lastplayer);
            }
          } else {
            if (((game->field[GOFFS(game, xx + 1, yy)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx + 1, yy + 1)] & bx11000000) == bx10000000)) { /* | */
              if ((game->field[GOFFS(game, xx + 2, yy)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
                addWall(game, xx + 1, yy, xx + 1, yy + 1, game->lastplayer);
              }
            }
            if (((game->field[GOFFS(game, xx + 1, yy + 1)] & bx11000000) == bx10000000) && ((game->field[GOFFS(game, xx, yy + 1)] & bx11000000) == bx10000000)) { /* - */
              if ((game->field[GOFFS(game, xx, yy + 2)] & bx11000000) != bx01000000) { /* if the dot behind got captured as well, then do not build a wall here */
                addWall(game, xx + 1, yy + 1, xx, yy + 1, game->lastplayer);
              }
            }
        }
        /* no more wall types to check for */
      }
    }
  }
  /* removeSingleWalls(); */ /* remove any single walls */
}


static int computegamestructsize(int width, int height) {
  /* the internal datatable have a 1 pixel 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) {
  struct kropki_game *res;
  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->lastplayer = 0;   /* at first, anyone can play first */
  res->lastmovex = 0;
  res->lastmovey = 0;
  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(game->field[GOFFS(game, x + 1, y + 1)] & bx00111111); /* return only 6 lowest bits - other contain potential garbage */
}


static int kropki_setpos_internal(struct kropki_game *game, int x, int y, int player, int forceflag) {
  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);
  if ((player < 1) || (player > 2)) return(-3);
  /* '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)] & bx00000011) != kropki_EMPTY) return(-4);  /* not an empty field */
  if ((game->field[GOFFS(game, x, y)] & bx00001100) != 0) return(-5);  /* field already captured */
  /* is it the right player that plays? (check only if forceflag not set) */
  if ((player != game->lastplayer) || (forceflag != 0)) {
    game->field[GOFFS(game, x, y)] |= player;
    game->lastplayer = player;
    game->lastmovex = x - 1;
    game->lastmovey = y - 1;
  }
  /* printf("New dot at [%d:%d]\n", x, y); */
  checkgame(game);
  return(0);
}


int kropki_setpos(struct kropki_game *game, int x, int y, int player) {
  return(kropki_setpos_internal(game, x, y, player, 0));
}


int kropki_setposforce(struct kropki_game *game, int x, int y, int player) {
  return(kropki_setpos_internal(game, x, y, player, 1));
}


void kropki_getpoints(struct kropki_game *game, int *scores) {
  int x, y;
  scores[0] = 0;
  scores[1] = 0;
  for (x = 2; x < game->field_width; x++) { /* start at [2,2], because it's impossible to capture dots near the border anyway */
    for (y = 2; y < game->field_height; y++) { /* look for captured dots belonging to opposite players */
      if ((game->field[GOFFS(game, x, y)] & bx00001111) == bx00000110) scores[0] += 1;
      if ((game->field[GOFFS(game, x, y)] & bx00001111) == bx00001001) scores[1] += 1;
    }
  }
}


/* copies an entire game (including the field, walls, etc) */
struct kropki_game *kropki_copygame(struct kropki_game *game) {
  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;
  /* 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(struct kropki_game *game, int *resx, int *resy) {
  int x, y;
  for (y = 0; y < game->field_height; y++) {
    for (x = 0; x < game->field_width; x++) {
      if (kropki_getpos(game, x, y) == kropki_EMPTY) {
        if (resx != NULL) *resx = x;
        if (resy != NULL) *resy = y;
        return(0);
      }
    }
  }
  return(-1);
}
