/*
 * uses a neural net for move prediction, based on a 9x9 area around previous move.
 *
 * the area being analized is an array of the playfield that is simplified as follows:
 *  - opponent's dots are represented as -1
 *  - AI dots are represented as 1
 *  - empty dots are represented as 0
 *  - captured areas are entirely filled with opponent's dots (-1)
 *  - walls are stripped, ie they are represented as normal opponent's dots (-1)
 */

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "genann.h"

#include "kropki_core.h"

/* how big is the simplified array fed to UI (one side) */
#define SARR_SIDE 15

static void stupid_choice(const struct kropki_game *g, int *movex, int *movey) {
  int x, y, i;
  /* try a random move 10x times in a row */
  for (i = 0; i < 10; i++) {
    x = rand() % g->field_width;
    y = rand() % g->field_height;
    if (kropki_getpos(g, x, y) == 0) {
      *movex = x;
      *movey = y;
      return;
    }
  }
  /* if not found a valid move, take the first available */
  for (y = 0; y < g->field_height; y++) {
    for (x = 0; x < g->field_width; x++) {
      int f;
      f = kropki_getpos(g, x, y);
      if (f == 0) {
        *movex = x;
        *movey = y;
        return;
      }
    }
  }
}


/* looks for an available (playable) field in a playfield window. returns -1 if not found, 1 otherwise */
static int findavailinwindow(const struct kropki_game *g, int x, int y, int sidelen) {
  int xx, yy;
  for (yy = y; yy < y + sidelen; yy++) {
    for (xx = x; xx < x + sidelen; xx++) {
      if (kropki_getpos(g, xx, yy) == 0) return(1);
    }
  }
  return(-1);
}


#define SARR_HMIRROR 1
#define SARR_VMIRROR 2

/* converts a game struct into a "simplified field description" suitable for neural net processing, may return flags: SARR_HMIRROR or SARR_VMIRROR */
static int game2simplifiedarray(double *sarr, const struct kropki_game *g, int *sarroffsets) {
  int i;
  int x, y;
  int xx, yy;
  int flags = 0;
  int weights_hhalf[2];
  int weights_vhalf[2];
  memset(weights_hhalf, 0, sizeof(weights_hhalf));
  memset(weights_vhalf, 0, sizeof(weights_vhalf));
  /* no last player == empty field */
  if (g->lastplayer == 0) {
    memset(sarr, 0, sizeof(double) * SARR_SIDE * SARR_SIDE);
    sarroffsets[0] = 0;
    sarroffsets[1] = 0;
    return(flags);
  }
  /* compute the position of the box around last move (ie. its top left corner) */
  x = g->lastmovex;
  y = g->lastmovey;
  x -= SARR_SIDE / 2;
  y -= SARR_SIDE / 2;
  if (x < 0) x = 0;
  if (y < 0) y = 0;
  if (x + SARR_SIDE > g->field_width) x = g->field_width - SARR_SIDE;
  if (y + SARR_SIDE > g->field_height) y = g->field_height - SARR_SIDE;
  /* is there at least 1 playable field in this window? */
  if (findavailinwindow(g, x, y, SARR_SIDE) == -1) {
    /* no playable field -> find the first available field */
    //printf("NO PLAYABLE FIELD -> LOOKING FOR ALTERNATIVE WINDOW\n");
    kropki_findfirstfreefield(g, &x, &y);
    if (x + SARR_SIDE > g->field_width) x = g->field_width - SARR_SIDE;
    if (y + SARR_SIDE > g->field_height) y = g->field_height - SARR_SIDE;
  }
  /* set elected offsets */
  sarroffsets[0] = x;
  sarroffsets[1] = y;
  /* iterate over fields and fill the simplified array (also compute the "weight" of each line) */
  i = 0;
  for (yy = 0; yy < SARR_SIDE; yy++) {
    for (xx = 0; xx < SARR_SIDE; xx++) {
      int f;
      f = kropki_getpos(g, x + xx, y + yy);
      /* normalize field: strip walls */
      f &= 15;
      /* normalize field: captured areas become simple owner dots */
      if ((f >> 2) != 0) f >>= 2;
      /* normalize field: my dots are 1, opponent dots are -1 */
      if (f == g->lastplayer) {
        f = -1; /* opponent */
      } else if (f != 0) {
        f = 1; /* me */
      }
      /* set sarr and inc i */
      sarr[i++] = f;
      /* weight update */
      if (yy <= SARR_SIDE / 2) weights_hhalf[0] += f;
      if (yy > SARR_SIDE / 2) weights_hhalf[1] += f;
      if (xx <= SARR_SIDE / 2) weights_vhalf[0] += f;
      if (xx > SARR_SIDE / 2) weights_vhalf[1] += f;
    }
  }
  /* h-mirror the field if the top half is "heavier" than the lower half.
   * this is done to limit the amount of variations presented to the neural
   * net during training */
/*  if (weights_hhalf[0] > weights_hhalf[1]) {
    double sarrbuf[SARR_SIDE * SARR_SIDE / 2];
    size_t sarrlen = sizeof(double) * SARR_SIDE * SARR_SIDE;
    memcpy(sarrbuf, sarr, sarrlen / 2);
    memcpy(sarr, &(sarr[SARR_SIDE * SARR_SIDE / 2]), sarrlen / 2);
    memcpy(&(sarr[SARR_SIDE * SARR_SIDE / 2]), sarrbuf, sarrlen / 2);
    flags |= SARR_HMIRROR;
  }*/
  /* TODO v-mirror the field if the left half is "heavier" than the right halt. */
  return(flags);
}


static void debug_outsarr(double *sarr, int flags) {
  int i, ii = 0;
  printf("sarr: ");
  for (i = 0; i < SARR_SIDE * SARR_SIDE; i++) {
    printf(" %2d", (int)(sarr[i]));
    ii++;
    if (ii >= SARR_SIDE) {
      ii = 0;
      printf("\n      ");
    }
  }
  if (flags) {
    printf("flags:");
    if (flags & SARR_HMIRROR) printf(" H-MIRROR");
    if (flags & SARR_VMIRROR) printf(" V-MIRROR");
  }
  printf("\n");
}


void ai_proto(const char *okp, int *movex, int *movey) {
  struct kropki_game *g;
  double sarr[SARR_SIDE * SARR_SIDE];
  double const *aimove;
  int sarroffsets[2], aires[2];
  int i;
  int sarrflags;
  genann *ann;

  /* init move coordinates to invalid values */
  *movex = -1;
  *movey = -1;

  /* init genann */
  {
    FILE *fd;
    fd = fopen("proto.ann", "rb");
    if (fd == NULL) {
      printf("ERROR: failed to open ann file\n");
      return;
    }
    ann = genann_read(fd);
    fclose(fd);
    if (ann == NULL) {
      printf("ERROR: genann_read() failed\n");
      return;
    }
  }

  g = kropki_okp2struct(okp);
  if (g == NULL) {
    printf("ERROR: kropki_okp2struct() failed\n");
    goto FREE_AND_QUIT;
  }

  /* convert game into a simplified array */
  sarrflags = game2simplifiedarray(sarr, g, sarroffsets);

  /* debug: output simplified array */
  debug_outsarr(sarr, sarrflags);

  /* ask AI for a prediction */
  aimove = genann_run(ann, sarr);
  /* convert AI answer (0..1) to SARR_SIDE-1 range */
  for (i = 0; i < 2; i++) aires[i] = round(aimove[i] * (SARR_SIDE - 1));
  /* TODO: V-MIRROR? */
  printf("genann returned [%f,%f], that is relative [%d,%d], ie. absolute [%d,%d]\n", aimove[0], aimove[1], aires[0], aires[1], aires[0] + sarroffsets[0], aires[1] + sarroffsets[1]);

  /* apply mirror flags, if set */
  if (sarrflags & SARR_HMIRROR) aires[1] = (SARR_SIDE - 1) - aires[1]; /* H-MIRROR */

  aires[0] += sarroffsets[0];
  aires[1] += sarroffsets[1];

  /* is it a legal move? */
  if (kropki_getpos(g, aires[0], aires[1]) != 0) {
    printf("WARN: AI tried an invalid move! Fall back to stupid.\n");
    stupid_choice(g, movex, movey);
  } else {
    *movex = aires[0];
    *movey = aires[1];
  }

  FREE_AND_QUIT:

  kropki_freegame(g);
  genann_free(ann);
}
