/***** All content © 2010 DigiPen (USA) Corporation, all rights reserved. *****/
/*! \file    Amazons.c
**  \author  Nathan Williams
**  \par     email: nwilliam\@digipen.edu
**  \date    04/11/2010
**  \brief   Implementation of Amazons.
**
**   References:
**     - http://ticc.uvt.nl/icga/games/amazons/
**     - http://www.csun.edu/~lorentz/amazon.htm
**     - http://en.wikipedia.org/wiki/Game_of_the_Amazons
**     - http://swiss2.whosting.ch/jenslieb/amazong/amazong.html
**
**   Amazons can be played on boards of arbitrary size, however, it is usually
**   played on a 10x10 board. The two players, white and black, are each given
**   given four amazons in predefined locations.
**     - White starting amazon locations: a4,  d1,  g1, j4
**     - Black starting amazon locations: a7, d10, g10, j7
**     - White moves first. Each move contains two mandatory parts: The player's
**       amazon moves like a chess queen, any number of squares in a horizontal,
**       vertical, or diagonal line. Once it gets to its final position, the 
**       amazon throws an arrow that also moves like a chess queen. The arrow 
**       permanently blocks the board square it lands in.
**     - Both the amazon and the arrow it throws must move along a line that is
**       not obstructed by another amazon or an arrow.
**     - The last player able to complete a move wins the game (ie, no draws).
**
**     Standard starting board (W = white amazon, B = black amazon)
**       10 - - - B - - B - - -
**        9 - - - - - - - - - -
**        8 - - - - - - - - - -
**        7 B - - - - - - - - B
**        6 - - - - - - - - - -
**        5 - - - - - - - - - -
**        4 W - - - - - - - - W
**        3 - - - - - - - - - -
**        2 - - - - - - - - - -
**        1 - - - W - - W - - -
**          a b c d e f g h i j
**
**
**  Plugin Specifics:
**    Move Notation: <start_loc>-<dest_loc>,<arrow_loc>
**      - This plugin uses the PGN like format, which is the same as used on 
**        Wikipedia, Gamazons, and Amazong (to name a few).
**      - For ease of interaction, the positions can be in any case and
**        there can be varying amounts of whitespace.
**      - A few example moves:
**        a4-d4,g7   (White: move queen from A4 to D4, throw arrow to G7)
**        j7-g4,j7   (Black: move queen from J7 to G4, throw arrow to J7)
**        d4-d9,g9   (White: move queen from D4 to D9, throw arrow to G9
**        d10-f8,f2  (Black: move queen from D10 to F8, throw arrow to F2)
**    Board Representation:
**      - A simple array of n*n characters, indexed so top left of board is 
**        zero. That is, a10 = 0 and j1 = 99.
**      - Empty cell is integer 0, white location is 'W', black location is 'B',
**        white arrow is 'w', and black arrow is 'b'.
**
**  Possible To Do's:
**    - Game option for setting board size
**    - Game option for setting first player
**    - Game option for loading a board (ex: with blocked spaces)
**     
*******************************************************************************/

#include <string.h> /* strlen */
#include <stdlib.h> /* calloc, free */
#include <math.h>   /* abs */

#include "PluginCommon.h"
#include "PluginInterface.h"

#define BOARD_COLS 10
#define BOARD_ROWS 10
#define WHITE 1
#define BLACK 2

struct Game
{
  int mNextPlayer;
  void* mPlayers[2];
  enum PLUGIN_GAME_STATUS mStatus;
  char mBoard[BOARD_COLS*BOARD_ROWS];
};


/* Does no validation. Assumes 10x10 board. Returns -1 on invalid. */
static int GetIndex(char col, int row)
{
  /* make them zero based */
  col = col - 'A';
  --row;

  if(col < 0 || col >= BOARD_COLS || row < 0 || row >= BOARD_ROWS)
    return -1;

  return (row*BOARD_COLS + col);
}

static int D4Distance(char c1, int r1, char c2, int r2)
{
  c1 -= 'A'; --r1;
  c2 -= 'A'; --r2;
  return abs(c1 - c2) + abs(r2 - r1);
}

static int MoveIsValid(const char* board, char c1, int r1, char c2, int r2)
{
  int colInc, rowInc, index;

  /* Basic rule checks */
  if( (c1 != c2) && (r1 != r2) && (D4Distance(c1, r1, c2, r2) % 2 != 0) )
    return 1;

  colInc = (c2 != c1) ? (c2 > c1 ? 1 : -1) : 0;
  rowInc = (r2 != r1) ? (r2 > r1 ? 1 : -1) : 0;

  do
  {
    c1 += colInc;
    r1 += rowInc;
    index = GetIndex(c1, r1);

    if(board[index] != 0)
      return 1;
  }
  while(c1 != c2 || r1 != r2);

  return 0;
}

static char GetPlayerSymbol(int player)
{
  if(player == WHITE) return 'W';
  if(player == BLACK) return 'B';
  return -1;
}

static char GetArrowSymbol(int player)
{
  if(player == WHITE) return 'w';
  if(player == BLACK) return 'b';
  return -1;
}

static int CanMoveFromPos(const char* board, char col, int row)
{
  int i;
  int canMove = 0;
  int mC[8], mR[8];

  /* left,       right,         up,            down,          left/up,       left/down,     right/up,      right/down */ 
  mC[0]=col - 1; mC[1]=col + 1; mC[2]=col;     mC[3]=col;     mC[4]=col - 1; mC[5]=col - 1; mC[6]=col + 1; mC[7]=col + 1;
  mR[0]=row;     mR[1]=row;     mR[2]=row + 1; mR[3]=row - 1; mR[4]=row + 1; mR[5]=row - 1; mR[6]=row + 1; mR[7]=row - 1;

  for(i = 0; (i < 8) && (canMove == 0); ++i)
  {
    if(mC[i] >= 'A' && mC[i] < ('A'+BOARD_COLS) && mR[i] >= 1 && mR[i] < BOARD_ROWS)
      canMove = (board[ GetIndex(mC[i], mR[i]) ] == 0);
  }
  
  return canMove;
}




int Init(void)
{
  /* Nothing to do */
  return 0;
}

void Shutdown(void)
{
}

const char* Name(void)
{
  return "Amazons";
}

const char* Author(void)
{
  return "nlw";
}

const char* Type(void)
{
  return "Amazons";
}

const char* Version(void)
{
  return "0.1";
}

const char* APIVersion(void)
{
  return "0.1";
}

int NumberOfPlayers(void)
{
  return 2;
}

void* CreateGame(void)
{
  struct Game* newGame = calloc(1, sizeof(struct Game));
  if(newGame)
    newGame->mStatus = PLUGIN_GAME_STATUS_CREATED;
  return newGame;
}

int SetGameOption(void* igame, const char* option)
{
  /* No options for this game */
  (void)igame;
  (void)option;
  
  return 1;
}

int AddPlayer(void* igame, void* player)
{
  struct Game* game = igame;
  
  if(game->mPlayers[0] == NULL)
    game->mPlayers[0] = player;
  else if(game->mPlayers[1] == NULL)
    game->mPlayers[1] = player;
  else
    return 1;
  
  return 0;
}

int RemovePlayer(void* igame, void* player)
{
  struct Game* game = igame;
  
  if(game->mPlayers[0] == player)
    game->mPlayers[0] = NULL;
  else if(game->mPlayers[1] == player)
    game->mPlayers[1] = NULL;
  else
    return 1;
  
  if(game->mStatus == PLUGIN_GAME_STATUS_PLAYING)
    game->mStatus = PLUGIN_GAME_STATUS_PAUSED;
  
  return 0;
}

int PlayerCount(void* igame)
{
  struct Game* game = igame;
  return (game->mPlayers[0] != 0) + (game->mPlayers[1] != 0);
}

void* GetPlayer(void* igame, int playerNumber)
{
  struct Game* game = igame;
  
  if(playerNumber == WHITE || playerNumber == BLACK)
    return game->mPlayers[playerNumber-1];
  
  return NULL;
}

int GetPlayerNumber(void* igame, void* player)
{
  struct Game* game = igame;
  
  if(game->mPlayers[0] == player)
    return 1;
  
  if(game->mPlayers[1] == player)
    return 2;
  
  return 0;
}

int StartGame(void* igame)
{
  struct Game* game = igame;
  
  if(game->mPlayers[0] == NULL || game->mPlayers[1] == NULL)
    return 1;
  
  /* Init board */
  memset(game->mBoard, 0, BOARD_COLS*BOARD_ROWS);

  /* Starting positions */
  game->mBoard[ GetIndex('A', 4) ] = 'W';
  game->mBoard[ GetIndex('D', 1) ] = 'W';
  game->mBoard[ GetIndex('G', 1) ] = 'W';
  game->mBoard[ GetIndex('J', 4) ] = 'W';
  game->mBoard[ GetIndex('A', 7) ] = 'B';
  game->mBoard[ GetIndex('D',10) ] = 'B';
  game->mBoard[ GetIndex('G',10) ] = 'B';
  game->mBoard[ GetIndex('J', 7) ] = 'B';
  
  game->mNextPlayer = WHITE;
  game->mStatus = PLUGIN_GAME_STATUS_PLAYING;
  
  return 0;
}

void* GetNextPlayer(void* igame)
{
  struct Game* game = igame;
  return GetPlayer(igame, game->mNextPlayer);
}

int ValidateMove(void* igame, void* player, const char* move)
{
  struct Game* game = igame;
  char* board = game->mBoard;

  int index = 0;
  char m[6] = {0};
  const char *it = move;    
  int arrowSymbol = GetArrowSymbol(game->mNextPlayer);
  int playerSymbol = GetPlayerSymbol(game->mNextPlayer);
  int startIndex, destIndex, arrowIndex, x, y;


  if(move == NULL || player != GetNextPlayer(igame) || game->mStatus != PLUGIN_GAME_STATUS_PLAYING)
    return -1;

  while(*it && index < 6)
  {
    if(*it == ' ' || *it == '-' || *it == ',')
    {
      ++it; /* skip */
    }
    else if(*it >= '0' && *it <= '9')
    {
      int mul = 1;
      
      while(*it && *it >= '0' && *it <= '9')
      {
        m[index] *= mul;
        m[index] += (*it - '0');
        mul *= 10;
        ++it;
      }
      
      ++index;
    }
    else if(*it >= 'A' && *it <= 'Z')
    {
      m[index++] = *it++;
    }
    else if(*it >= 'a' && *it <= 'z')
    {
      m[index++] = *it++ - 32; /* toupper */
    }
    else
    {
      /* Invalid character */
      return -1;
    }
  }
  
  if(index != 6)
    return -1; /* partial move */

  startIndex = GetIndex(m[0], m[1]);
  destIndex = GetIndex(m[2], m[3]);
  arrowIndex = GetIndex(m[4], m[5]);
  
  if(startIndex < 0 || destIndex < 0 || arrowIndex < 0)
    return -1; /* invalid board positions in move */


  /* Move syntactically valid, now check against board */

  /* Moving from owned pos */
  if(board[startIndex] != GetPlayerSymbol(game->mNextPlayer))
    return -1;

  /* Basic bad movies that don't fit in checks below) */
  if((startIndex == destIndex) || (destIndex == arrowIndex))
    return -1;

  /* Check amazon movement */    
  if(MoveIsValid(board, m[0], m[1], m[2], m[3]))
    return 1;
  
  /* Check arrow movement */
  board[startIndex] = 0; /* arrow shouldn't be blocked by where we started */  
  if((arrowIndex != startIndex) && MoveIsValid(board, m[2], m[3], m[4], m[5]))
  {
    board[startIndex] = playerSymbol;
    return 1;
  }


  /* Valid move. Update board info */
  board[startIndex] = 0;
  board[destIndex] = playerSymbol;
  board[arrowIndex] = arrowSymbol;
  
  
  /* Check game over (new player cannot move) */
  game->mNextPlayer = (game->mNextPlayer == WHITE) ? BLACK : WHITE;
  playerSymbol = GetPlayerSymbol(game->mNextPlayer);
  
  index = 0; /* bool, move left */
  for(x = 0; (x < BOARD_COLS) && (index == 0); ++x)
  {
    char col = 'A' + x;
    
    for(y = 0; (y < BOARD_ROWS) && (index == 0); ++y)
    {
      if(board[ GetIndex(col, 1 + y) ] == playerSymbol)
        index |= CanMoveFromPos(board, col, y + 1);
    }
  }

  /* current player has no moves, last player won */
  if(index == 0)
  {
    game->mNextPlayer = (game->mNextPlayer == WHITE) ? BLACK : WHITE;
    game->mStatus = PLUGIN_GAME_STATUS_OVER_WINNER;
  }
  
  return 0;
}

int GetGameStatus(void* igame)
{
  struct Game* game = igame;
  return game->mStatus;
}

void DestroyGame(void* igame)
{
  free(igame);
}