/*
 *
 *
 */

#include <stdio.h>
#include <time.h>

#include "genann.h"

#include "kropki_core.h"
#include "ai.h"

#include "ai_proto.c"


#define HOWMANYGAMES 16
#define TRAININGGAMESIZE 24

/* set to non-zero for unassisted training against proto.ann */
#define VSMODE 1
#define MINWINS 11  /* how many times should the mutant win */


static int findlastgenfile(int hiddenlayers) {
  int gen = 0;
  for (;;) {
    FILE *fd;
    char fname[64];
    snprintf(fname, sizeof(fname), "proto_%d_gen_%d.ann", hiddenlayers, gen);
    fd = fopen(fname, "rb");
    /* does file exist? */
    if (fd == NULL) break;
    fclose(fd);
    gen++;
  }
  return(gen - 1); /* return last found gen (-1 if none found) */
}


int main(int argc, char **argv) {
  FILE *fd;
  char *okps;
  struct kropki_game *g;
  int x, y, xx, yy;
  genann *ann = NULL, *opp = NULL;
  double sarr[SARR_SIDE * SARR_SIDE];
  double outputs[2];
  int sarroffsets[2];
  int flags;
  int gamenum;
  int wins[2];
  int stupidmoves[2];
  int hiddenlayers;
  int gen = 0;
  int totalmoves[2];

  if ((argc != 2) || (atoi(argv[1]) < 1)) {
    printf("usage: genann_train <num_of_hidden_layers>\n");
    return(1);
  }
  hiddenlayers = atoi(argv[1]);

  srand(time(NULL));

  RETRY:
  wins[0] = 0;
  wins[1] = 0;
  stupidmoves[0] = 0;
  stupidmoves[1] = 0;
  totalmoves[0] = 0;
  totalmoves[1] = 0;
  printf("ANN initialization...\n");

  /* unassisted mode? load proto.ann */
  if (VSMODE != 0) {
    FILE *fd;
    int i, changes = 0;
    if (opp == NULL) {
      char fname[64];
      gen = findlastgenfile(hiddenlayers);
      printf("last gen available for %d layers: %d\n", hiddenlayers, gen);
      if (gen < 0) {
        gen = 0;
        snprintf(fname, sizeof(fname), "proto_%d_gen_%d.ann", hiddenlayers, gen);
        opp = genann_init(SARR_SIDE * SARR_SIDE, hiddenlayers, SARR_SIDE * SARR_SIDE * 2, 2); /* inputs, hidden layers, neurons per hidden layer, outputs */
        if (opp == NULL) {
          printf("ERROR: genann_init() failed!\n");
          return(1);
        }
        fd = fopen(fname, "wb");
        genann_write(opp, fd);
        genann_free(opp);
        opp = NULL;
        fclose(fd);
        printf("no previous gen found for %d hidden layers: created a new random ANN: '%s'\n", hiddenlayers, fname);
      }
      snprintf(fname, sizeof(fname), "proto_%d_gen_%d.ann", hiddenlayers, gen);
      fd = fopen(fname, "rb");
      opp = genann_read(fd);
      fclose(fd);
      if (opp == NULL) {
        printf("ERROR: genann_read(%s) failed!\n", fname);
        return(1);
      }
      printf("loaded '%s'\n", fname);
    }
    if (ann != NULL) genann_free(ann);
    ann = genann_copy(opp);
    for (i = 0; i < ann->total_weights; i++) {
      if ((rand() % 1000) != 1) continue;
      changes++;
      ann->weight[i] += ((double)rand()) / RAND_MAX-0.5;
    }
    printf("mutations from gen %d: %d (%0.3f%% from a total of %d neurons)\n", gen, changes, (float)(100 * changes) / (ann->total_weights), ann->total_weights);
    if (changes == 0) return(0);
  } else {
    ann = genann_init(SARR_SIDE * SARR_SIDE, hiddenlayers, SARR_SIDE * SARR_SIDE * 2, 2); /* inputs, hidden layers, neurons per hidden layer, outputs */
    if (ann == NULL) {
      printf("ERROR: genann_init() failed!\n");
      return(1);
    }
  }

  printf("ANN ready (%d hidden layers)\n", hiddenlayers);

  for (gamenum = 1; gamenum <= HOWMANYGAMES; gamenum++) {

    g = kropki_newgame(TRAININGGAMESIZE, TRAININGGAMESIZE, 2);

    for (;;) {

      /* end of game? quit */
      if (g->nextplayer == 0) break;

      flags = game2simplifiedarray(sarr, g, sarroffsets);
//      printf("sarr offsets: %d, %d\n", sarroffsets[0], sarroffsets[1]);
//      debug_outsarr(sarr, flags);

      if (VSMODE == 0) {
#if VSMODE == 0
        /* collect Dumbo move */
        okps = kropki_struct2okp(g);
        printf("Dumbo thinks... ");
        ai_dumbo(okps, &x, &y);
        free(okps);

        printf("says: %d,%d\n", x, y);

        /* train AI */
        xx = x - sarroffsets[0];
        yy = y - sarroffsets[1];
        if ((xx < 0) || (yy < 0) || (xx >= SARR_SIDE) || (yy >= SARR_SIDE)) {
          printf("MOVE TOO FAR AWAY (%d,%d): IGNORED!\n", xx, yy);
        } else {
          outputs[0] = xx;
          outputs[1] = yy;
          outputs[0] /= (SARR_SIDE - 1);
          outputs[1] /= (SARR_SIDE - 1);
          printf("training ANN...\n");
          genann_train(ann, sarr, outputs, 0.1);
          printf("OK\n");
        }
#endif
      } else {
        int i;
        double const *aimove;
        double aires[2];
        /* should opponent play? */
        if (g->nextplayer == 1) {
          aimove = genann_run(ann, sarr);
        } else {
          aimove = genann_run(opp, sarr);
        }
        for (i = 0; i < 2; i++) aires[i] = round(aimove[i] * (SARR_SIDE - 1)) + sarroffsets[i];
        x = aires[0];
        y = aires[1];
      }

      /* perform the move - if fail then fall back to stupid */
      totalmoves[g->nextplayer - 1]++;
      if (kropki_setpos(g, x, y, g->nextplayer) != 0) {
        //printf("WARN: kropki_setpos() failed -> fall back to stupid mode\n");
        stupid_choice(g, &x, &y);
        stupidmoves[g->nextplayer - 1]++;
        if (kropki_setpos(g, x, y, g->nextplayer) != 0) {
          printf("ERROR: kropki_setpos() failed\n");
          break;
        }
      }
      //printf("progress: game %d/%d is %d%% full (%d playable fields left from a total of %d) [wins so far: %d vs %d]\n", gamenum, HOWMANYGAMES, 100 - (g->playablefields * 100 / (g->field_width * g->field_height)), g->playablefields, g->field_width * g->field_height, wins[0], wins[1]);
    }

    //printf("END OF GAME %d/%d!\n", gamenum, HOWMANYGAMES);
    if (VSMODE) {
      int scores[2];
      if (g->score[0] > g->score[1]) wins[0]++;
      if (g->score[1] > g->score[0]) wins[1]++;
      printf("last game points: %d vs %d, global wins: %d vs %d (stupid moves: %d vs %d, ie %0.1f%% vs %0.1f%%)\n", g->score[0], g->score[1], wins[0], wins[1], stupidmoves[0], stupidmoves[1], (float)stupidmoves[0] * 100 / totalmoves[0], (float)stupidmoves[1] * 100 / totalmoves[1]);
      if (HOWMANYGAMES - wins[1] < MINWINS) goto RETRY;
    }

    kropki_freegame(g);
  } /* next game? */

  /* save the ANN */
  if (VSMODE == 0) {
    fd = fopen("proto.ann", "wb");
    genann_write(ann, fd);
    fclose(fd);
    genann_free(ann);
    printf("ANN saved to proto.ann\n");
  } else {
    char fname[128];
    if (stupidmoves[0] > stupidmoves[1]) goto RETRY;
    if (wins[0] < MINWINS) goto RETRY;
    snprintf(fname, sizeof(fname), "proto_%d_gen_%d.ann", hiddenlayers, ++gen);
    fd = fopen(fname, "wb");
    genann_write(ann, fd);
    fclose(fd);
    printf("ANN saved to %s\n", fname);
    /* ann becomes the opponent and let's continue */
    genann_free(opp);
    opp = ann;
    ann = NULL;
    goto RETRY;
  }

  return(0);
}
