/*!
*******************************************************************************
  \file    random-dom.c
  \author  Matthew Mead
  \par     email: mmead\@digipen.edu
  \date    2/3/2011
  \brief   Test driver for the Domineering plugin.

  This test driver interfaces with the Domineering plugin directly. It does not
  require networking or a server. The plugin is the same code that the server
  uses so this should test the client exactly like the server would. The
  benefit of this is that this client can play about 50,000 games (10x10) per
  second (depending on the speed of the computer). There is no "intelligence"
  in this test client as all of the moves are generated randomly.

  To run this program from the command line:

    ./random-dom {count} {rows columns} {player 1 orientation} [removed_cells]

    where:
      count is the number of games to play.
      rows/cols are the Y-X dimensions (rows columns).
      player 1 orientation is 1 for horiz. 2 for vert.
      (Optional) removed_cells is a comma-separated list of cells (row.column)
        that are not in play. Example: 0.0,0.1,1.2,3.3
        There are no spaces in the list.

    ./random-dom 10000 6 8 1 0.0,1.1,2.2,3.3,4.4

  will play 10,000 games of Domineering on a board with 6 rows and 8 columns,
  player 1 will be horizontal and the cells 0.0,1.1 etc. will be blocked out.
  After the games are over, statistics will be displayed.
  Use the Options in the code below to enable/disable more/less output.

  Currently, this program always generates the same set of random numbers each
  time it is run. To enable randomness between runs, uncomment the call to
  srand() in the main() function at the bottom of this file.

  A few runs (1,000,000 10x10 games with player 1 playing horizontal):

  Wins - Player 1(H):50587, Player 2(V):49413, Games Played:100000, Games Left:0, Wins: 1(50.59%) 2(49.41%)
  Wins - Player 1(H):50467, Player 2(V):49533, Games Played:100000, Games Left:0, Wins: 1(50.47%) 2(49.53%)
  Wins - Player 1(H):50562, Player 2(V):49438, Games Played:100000, Games Left:0, Wins: 1(50.56%) 2(49.44%)
  Wins - Player 1(H):50729, Player 2(V):49271, Games Played:100000, Games Left:0, Wins: 1(50.73%) 2(49.27%)
  Wins - Player 1(H):50789, Player 2(V):49211, Games Played:100000, Games Left:0, Wins: 1(50.79%) 2(49.21%)

  Running 1,000,000 games, the most number of moves required was 49, the least number was 32 (only 1).

  Another example: A 3x4 board with 3 cells marked out and Player 1 (Left) playing vertical:

  Initial board position:

      X X . .
      . . . .
      . . X .

  This is the command line:

    ./random-dom 1000000 3 4 1 0.0,0.1,2.2 

  Playing 5 rounds of 1,000,000 games yields these results (Player 1 winning is 7/40, or .175)

  Wins - Player 1(V):175462, Player 2(H):824538, Games Played:1000000, Games Left:0, Wins: 1(17.55%) 2(82.45%)
  Wins - Player 1(V):175085, Player 2(H):824915, Games Played:1000000, Games Left:0, Wins: 1(17.51%) 2(82.49%)
  Wins - Player 1(V):174765, Player 2(H):825235, Games Played:1000000, Games Left:0, Wins: 1(17.48%) 2(82.52%)
  Wins - Player 1(V):175228, Player 2(H):824772, Games Played:1000000, Games Left:0, Wins: 1(17.52%) 2(82.48%)
  Wins - Player 1(V):175616, Player 2(H):824384, Games Played:1000000, Games Left:0, Wins: 1(17.56%) 2(82.44%)  

*******************************************************************************/
#include <stdio.h>  /* printf, sprintf, fflush                 */
#include <stdlib.h> /* rand, srand, atoi, malloc, calloc, free */
#include <time.h>   /* time                                    */
#include <string.h> /* strlen, strcpy, strchr                  */

#include "PluginInterface.h"
#include "Domineering.h" /* enum DOM_DIRECTION {ddHORIZONTAL, doVERTICAL} */

/*
#ifdef __GNUC__
#define FUNCTION_IS_NOT_USED __attribute__ ((unused))
#else
#define FUNCTION_IS_NOT_USED
#endif
*/

/*
  The server creates its own Player structs internally. I'm just using this
  as a place-holder for the server's player. It can be anything actually.
*/
struct Player
{
  int dummy;
};

/* The game that is created by the server (i.e. plugin) */
struct Game* gGame = 0;

/* Options */
static int gSilent = 1;      /* Produce very little output (just displays totals)           */
static int gVerbose = 0;     /* Produce copious amounts of output during play               */

static int gPlayer = 1;          /* Which player is currently making a move                     */
static int gOther = 2;           /* Which player is not making a move                           */
static int gCount = 1;           /* The number of games to play                                 */
static int gWins[3] = {0};       /* gWins[1] counts wins for player 1, gWins[2] counts player 2 */
static int gLosses[3] = {0};     /* same as gWins except keeps track of losses                  */
static int gGamesPlayed = 0;     /* The number of games played thus far                         */
static unsigned gRows;          /* Number of rows                                              */
static unsigned gCols;          /* Number of columns                                           */
static char *gBlockedCells = 0; /* A comma separated list of row.column pairs to block out     */

/* One player is horizontal, one is vertical */
static enum DOM_DIRECTION gPlayer1Direction = ddHORIZONTAL;

/* The representation of the Domineering board */
char *Board;

/* For drawing the board on the screen */
const char EMPTY_SQUARE = '.';
const char SYMBOLS[] = {'0', '-', '|', ' '};
static char Symbols[4] = {'0', '-', '|', ' '};

/*
  Track how many moves it took for a win i.e. gWinsMoves1[7]++ means that Player 1 won
  on move #7. This is currently a hack because the array is hard-coded. Large boards
  (larger than 10x10) may overflow the array. This should be dynamically allocated,
  the size based on the size of the board. It's disabled for now.
*/
#define gMostMoves 100
static int gWinsMoves1[gMostMoves] = {0};
static int gWinsMoves2[gMostMoves] = {0};

static void *gPlayers[2] = {0};

typedef struct Point
{
  int row;
  int col;
}Point;

struct Point IndexToPoint(int index)
{
  struct Point pt;
  pt.row = index / gCols;
  pt.col = index % gCols;
  return pt;
}

/* generate random numbers between low and high */
static int RandomInt(int low, int high)
{
  int number;
  number = rand() % (high - low + 1) + low;
  return number;
}

static void DumpBoard(void)
{
  unsigned row, col;
  for (row = 0; row < gRows; row++)
  {
    for (col = 0; col < gCols; col++)
    {

      char c = Symbols[(int)Board[row * gCols + col]];
      /*printf("|%i| ", Board[row * gCols + col]);*/
      if (c)
        printf(" %c", c);
      else
        printf(" 0");
    }
    printf("\n");
  }
  printf("\n");
}

static int is_legal_move(int player, unsigned row, unsigned column)
{
    /* Out of the range */
  if (row >= gRows || column >= gCols)
    return 0;

    /* Position is in use */
  if (Board[row * gCols + column])
    return 0;

    /* horizontal */
  if (player == 1)
  {
      /* horizontal */
    if (gPlayer1Direction == ddHORIZONTAL)
    {
      if (column + 1 >= gCols)
        return 0;
      if (Board[row * gCols + column + 1])
        return 0;
    }
    else /* vertical */
    {
      if (row + 1 >= gRows)
        return 0;
      if (Board[(row + 1) * gCols + column])
        return 0;
    }
  }
  else /* Player 2 */
  {
      /* horizontal */
    if (gPlayer1Direction == doVERTICAL)
    {
      if (column + 1 >= gCols)
        return 0;
      if (Board[row * gCols + column + 1])
        return 0;
    }
    else /* vertical */
    {
      if (row + 1 >= gRows)
        return 0;
      if (Board[(row + 1) * gCols + column])
        return 0;
    }
  }
  return 1;
}




/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************/
/*
  If you wanted to make this test client intelligent, this is likely the only
  function you'd have to change. You'd also have to know  which player is
  moving so you could pick the best next move based on which player is making
  the move.

  Currently, this function simply picks a random move
*/
static struct Point GetNextMove(int player)
{
  int row, col;
  struct Point pt;

  do
  {
    row = RandomInt(0, gRows - 1);
    col = RandomInt(0, gCols - 1);
    /*
    printf("Player %i trying %i,%i\n", player, row, col);
    DumpBoard();
    */
  }
  while (!is_legal_move(player, row, col));

    /* Player 1 */
  if (player == 1)
  {
      /* horizontal */
    if (gPlayer1Direction == ddHORIZONTAL)
    {
      Board[row * gCols + col] = 1;
      Board[row * gCols + col + 1] = 1;
      /*printf("Player 1 is moving horizontal\n");*/
    }
    else /* vertical */
    {
      Board[row * gCols + col] = 1;
      Board[(row + 1) * gCols + col] = 1;
      /*printf("Player 1 is moving vertical\n");*/
    }
  }
  else /* Player 2 */
  {
      /* horizontal */
    if (gPlayer1Direction == doVERTICAL)
    {
      Board[row * gCols + col] = 2;
      Board[row * gCols + col + 1] = 2;
      /*printf("Player 2 is moving horizontal\n");*/
    }
    else /* vertical */
    {
      Board[row * gCols + col] = 2;
      Board[(row + 1) * gCols + col] = 2;
      /*printf("Player 2 is moving vertical\n");*/
    }
  }

  pt.row = row;
  pt.col = col;
  /*printf("Player %i moving to %i,%i\n", player, row, col);*/

  return pt;
}
/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************/

static int mark_off_cells(char *board, const char *string)
{
  char *buffer = (char *)malloc(strlen(string) + 1);
  char *delim = ", ";
  char *token;
  char buf[100];

  strcpy(buffer, string);
  token = strtok(buffer, delim);
  while (token)
  {
    int row, col;
    char *pt;
    strcpy(buf, token);
    pt = strchr(buf, '.');
    if (!pt)
      continue;

    *pt = 0;
    row = atoi(buf);
    col = atoi(pt + 1);
    board[row * gCols + col] = 3;

    token = strtok(NULL, delim);
  }
  free(buffer);
  return 0;
}

#if 0
static void init_board(void)
{
  unsigned i, j;
  for (i = 0; i < gRows; i++)
    for (j = 0; j < gCols; j++)
      Board[i * gCols + j] = 0;
}

static void ClearBoard(void)
{
  init_board();
}
#endif

static int PointToIndex(const struct Point* pt)
{
  return pt->row * gCols + pt->col;
}

static void AddOutput(const char* str, int newline)
{
  printf("%s", str);
  if (newline)
    printf("\n");

  fflush(stdout);
}

/*
  Creates the game and adds a player as Player 1

*/
static int driver_CreateGame(void)
{
  struct Player *player;

  if (gVerbose)
    AddOutput("Creating game...", 1);

  gGame = (struct Game *)CreateGame();
  if (!gGame)
  {
    AddOutput("Error: Can't create game.", 1);
    return 0;
  }

  player = (struct Player *)malloc(sizeof (struct Player));
  if (AddPlayer(gGame, player))
  {
    AddOutput("Error: Can't add player 1.", 1);
    DestroyGame(gGame);
    free(player);
    return 0;
  }

  gPlayers[0] = player;
  return 1;
}

/*
  Joining the game simply adds a second player to the game.
*/
static int driver_JoinGame(void)
{
  struct Player *player;
  if (gVerbose)
  {
    AddOutput("Joining game with player 2", 1);
  }

  if (!gGame)
  {
    AddOutput("Error: Can't join a game: No game created.", 1);
    return 0;
  }

  player = (struct Player *)malloc(sizeof(struct Player));
  if (AddPlayer(gGame, player))
  {
    AddOutput("Error: Can't add player 2.", 1);
    free(player);
    return 0;
  }

  gPlayers[1] = player;
  return 1;
}

static int driver_SetGameOption(void)
{
  char *command;
  char buffer[100]; /* TODO: Make this dynamic */

  if (!gGame)
  {
    AddOutput("Error: Can't set game options: No game created.", 1);
    return 0;
  }

  if (gPlayer1Direction == ddHORIZONTAL)
  {
    command = "ORH:1";
    Symbols[1] = '-';
    Symbols[2] = '|';
  }
  else
  {
    command = "ORH:2";
    Symbols[1] = '|';
    Symbols[2] = '-';
  }

  if (gVerbose)
    AddOutput("Setting game option ORH", 1);

  if (!SetGameOption(gGame, command))
  {
    AddOutput("Error: Can't set game option ORH", 1);
    return 0;
  }

  sprintf(buffer, "DIM:%ix%i", gRows, gCols);
  if (gVerbose)
  {
    AddOutput("Setting game option: ", 0);
    AddOutput(buffer, 1);
  }

  if (!SetGameOption(gGame, buffer))
  {
    AddOutput("Error: Can't set game option: ", 0);
    AddOutput(buffer, 1);
    return 0;
  }

    /* Allocate the board */
  Board = (char *)calloc(gRows * gCols, sizeof(char));

    /* Block out any cells the user provided */
  if (gBlockedCells)
  {
    char *buff = (char *)malloc(strlen(gBlockedCells) + 5);
    char *p = buff;
    sprintf(p, "MRK:%s", gBlockedCells);
    mark_off_cells(Board, p + 4);
    if (!SetGameOption(gGame, p))
    {
      AddOutput("Error: Can't set game option MRK", 1);
      free(buff);
      return 0;
    }
    free(buff);
  }

  /* Testing stuff */
  #if 0
  {
    int size = 20;
    int i;
    char *buff = (char *)malloc(size * 20);
    char *p = buff;
    sprintf(p, "MRK:");
    for (i = 0; i < size; i++)
    {
      int row = RandomInt(0, gRows - 1);
      int col = RandomInt(0, gCols - 1);
      char buf[30];

      /*
      if (row % 2)
        row = 0;
      */

      if (i)
        sprintf(buf, ",%i.%i", row, col);
      else
        sprintf(buf, "%i.%i", row, col);

      strcat(p, buf);
    }

    /*printf("%s\n", p);*/
    mark_off_cells(Board, p + 4);
    if (!SetGameOption(gGame, p))
    {
      AddOutput("Error: Can't set game option MRK", 1);
      free(buff);
      return 0;
    }
    free(buff);
  }
  #endif

  return 1;
}

/*
  After the game is created and both players are added,
  the game can be started. The server will do this automatically after
  both players are in the game and both players have sent the "Ready"
  message.
*/
static int driver_StartGame(void)
{
  int count;

  if (gVerbose)
    AddOutput("Starting game...", 1);

  if (!gGame)
  {
    AddOutput("Error: Can't start a game: No game created.", 1);
    return 0;
  }

    /* Sanity check */
  count = PlayerCount(gGame);
  if (count != 2)
  {
    printf("count = %i\n", count);
    AddOutput("Can't start game: requires 2 players.", 1);
    return 0;
  }
    /*
      This should always succeed because the only check the plugin makes
      is to see that there are 2 players in the game. We just checked that
      case above.
     */
  if (StartGame(gGame))
  {
    AddOutput("Error: Can't start game.", 1);
    return 0;
  }

  return 1;
}

/*
  Clean up the game.
*/
static void driver_DestroyGame(void)
{
  if (gVerbose)
    AddOutput("Destroying game...", 1);

  DestroyGame(gGame);
  free(gPlayers[0]);
  free(gPlayers[1]);
}

/*
  Wrapper function for getting a game going.
*/
static int NewGame(void)
{
  if (!driver_CreateGame())
      return 0;

  if (!driver_JoinGame())
      return 0;

  if (!driver_SetGameOption())
    return 0;

  if (!driver_StartGame())
      return 0;

  if (gVerbose)
  {
    int count = PlayerCount(gGame);
    printf("Player count: %i\n", count);
  }

  return 1;
}

/*
  Deals with any command line parameters our driver wants to accept.
  Your driver can do anything here. TODO: Change to getopt.
*/
static int init(int argc, char **argv)
{
  int value;

  if (argc < 5)
  {
    printf("Usage: %s {count} {rows columns} {player 1 orientation} [removed_cells]\n", argv[0]);
    printf("\n");
    printf("where: count is the number of games to play.\n");
    printf("       rows/cols are the X-Y dimensions (rows columns).\n");
    printf("       player 1 orientation is 1 for horiz. 2 for vert.\n");
    printf("       (Optional) removed_cells is a comma-separated list of cells (row.column) that are not in play.\n");
    printf("         Example: 0.0,0.1,1.2,3.3\n");
    printf("         There are no spaces in the list.\n");
    return 0;
  }

  gCount = atoi(argv[1]);
  gRows = atoi(argv[2]);
  gCols = atoi(argv[3]);
  value = atoi(argv[4]);

    /* This is optional */
  if (argc == 6)
    gBlockedCells = argv[5];

  if (value == 1)
    gPlayer1Direction = ddHORIZONTAL;
  else
    gPlayer1Direction = doVERTICAL;

  if (gVerbose)
  {
    printf("Plugin information\n");
    printf("----------------------------\n");
    printf("       Name: %s\n", Name());
    printf("     Author: %s\n", Author());
    printf("       Type: %s\n", Type());
    printf("    Players: %i\n", NumberOfPlayers());
    printf("    Version: %s\n", Version());
    printf("API Version: %s\n", APIVersion());
    printf("----------------------------\n");
  }

  return 1;
}

/*
  One of the goals of this test client is to accumulate lots of statistics
  about the game and display them after running many games.
*/
static void CalcGameOverStats(int movenum)
{
  int winner = GetPlayerNumber(gGame, GetNextPlayer(gGame));
  char buffer[100];

  if (winner == 0)
  {
      /* Can't happen in Domineering */
    sprintf(buffer, "The game is a draw!");
  }
  else
    sprintf(buffer, "Game over: Player %i is the winner after %i moves!", winner, movenum);

  if (gVerbose)
    AddOutput(buffer, 1);

  if (winner == 0)
    /* Domineering can't have draws */;
  else if (winner == gPlayer)
  {
    gWins[gPlayer]++;
    gLosses[gOther]++;
  }
  else
  {
    gWins[gOther]++;
    gLosses[gPlayer]++;
  }

  if (winner == 1)
    gWinsMoves1[movenum]++;
  else if (winner == 2)
    gWinsMoves2[movenum]++;

  /*printf("Number of moves: %i\n", movenum);*/
  if (!gSilent)
  {
    printf("Moves: %i, ", movenum);
    printf("Wins - Player 1(%c):%i, ", (gPlayer1Direction == ddHORIZONTAL ? 'H' : 'V'), gWins[1]);
    printf("Player 2[%c]:%i, ", (gPlayer1Direction == ddHORIZONTAL ? 'V' : 'H'), gWins[2]);
    printf("Games Played:%i, Games Left:%i, ", gGamesPlayed, gCount - gGamesPlayed);
    printf("Number of moves: %i\n", movenum);
  }
}

static void DisplayGrandTotals(void)
{
  /*int i;*/
  printf("Grand Total:\n");
  printf("Wins - Player 1(%c):%i, Player 2(%c):%i, Games Played:%i, Games Left:%i, Wins: 1(%5.2f%%) 2(%5.2f%%)\n",
          (gPlayer1Direction == ddHORIZONTAL ? 'H' : 'V'), gWins[1],
          (gPlayer1Direction == ddHORIZONTAL ? 'V' : 'H'), gWins[2], gGamesPlayed, gCount - gGamesPlayed,
          (float)gWins[1] / gGamesPlayed * 100,  (float)gWins[2] / gGamesPlayed * 100);

  /*
  for (i = 0; i < gMostMoves; i++)
    printf("Move #%02i : 1(%i), 2(%i)\n", i + 1, gWinsMoves1[i], gWinsMoves2[i]);
  */
}

/*
void test(void)
{
  char p[] = "1.2,2.2,3.3,1.4,5.6,23.45,8.9,  17.8  , 4.3";
  char *delim = ", ";
  char *buffer = (char *)malloc(strlen(p) + 1);

  char *token = strtok(p, delim);
  while (token)
  {
    int row, col;
    char *pt;
    strcpy(buffer, token);
    pt = strchr(buffer, '.');
    if (!pt)
      continue;

    *pt = 0;
    row = atoi(buffer);
    col = atoi(pt + 1);
    printf("|%s| (%i,%i)\n", token, row, col);
    token = strtok(NULL, delim);
  }
  free(buffer);
  exit(0);
}
*/

/*
  This is the loop where the games are "played".
*/
int main(int argc, char **argv)
{
  int i;

  if (!init(argc, argv))
    return 0;

    /* Uncomment the call to srand to have randomness every time the program is run. */
    /* It's easier to debug a program that is repeatable each time. Once the         */
    /* program is correct, enable the randomness.                                    */
  /*srand(time(NULL));*/

    /* Loop once for each game */
  for (i = 0; i < gCount; i++)
  {
    int movenum = 0, done = 0, invalid;

      /* Set up a new game each time */
    NewGame();

      /* Player 1 always goes first (horizontal) */
    gPlayer = 1;

      /* Infinite loop, breaks out when a win is detected */
    while (!done)
    {
      int player, move;
      char buf[10];

        /* GetNextMove() call will return the current player's (i.e. gPlayer) next move.  */
        /* The "intelligence" of this function determines the intelligence of the client. */
      struct Point pt = GetNextMove(gPlayer);
      movenum++;

      move = PointToIndex(&pt);
      sprintf(buf, "%i", move);

        /* Call the plugin to verify that the move is valid. This should always succeed since */
        /* this client verifies the validity before making the move.                          */
      invalid = ValidateMove(gGame, GetPlayer(gGame, gPlayer), buf);
      if (invalid)
      {
        printf("Player %i tried INVALID move to %i,%i\n", gPlayer, pt.row, pt.col);
        DumpBoard();
      }

        /* Display board on the screen if we're debugging */
      if (gVerbose)
      {
        printf("Player %i moved to %i,%i on move %i\n", gPlayer, pt.row, pt.col, movenum);
        DumpBoard();
      }

        /* Combinatorial games don't have draws */
      if (GetGameStatus(gGame) == PLUGIN_GAME_STATUS_OVER_WINNER)
      {
        CalcGameOverStats(movenum);

          /* Next game */
        break;
      }

        /* Toggle next player */
      player = GetPlayerNumber(gGame, GetPlayer(gGame, gPlayer));
      gPlayer = (player == 1) ? 2 : 1;
      gOther = (gPlayer == 1) ? 2 : 1;
    }
    gGamesPlayed++;
    driver_DestroyGame();
  }

  DisplayGrandTotals();
  return 0;
}