/*
  Copyright (C) 2005 Dan Alcantara (dfalcantara@ucdavis.edu, http://wwwcsif.cs.ucdavis.edu/~alcantar)
  Based on the board game "Blokus" by Educational Insights, Inc.
  
  This program is free software; you can redistribute 
  it and/or modify it under the terms of the GNU General 
  Public License as published by the Free Software Foundation; 
  either version 2 of the License, or (at your option) any later version.
  
  This program is distributed in the hope that it will be useful, but 
  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 
  more details.
  
  You should have received a copy of the GNU General Public License along 
  with this program; if not, write to the Free Software Foundation, Inc., 
  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  
  Full license visible at http://www.opensource.org/licenses/gpl-license.php
*/

/*	Contains all board info and controls all of the board logic: whether a piece can be placed, 
 *	where, etc.
 */

#include "board.h"

BlokishBoard::BlokishBoard()
{
  mnBoardWidth = 20;
  mnBoardHeight = 20;
  Reset();
}

void BlokishBoard::Write(std::ofstream &out)
{
  out.write((char*) &mnBoardWidth , sizeof(mnBoardWidth ));
  out.write((char*) &mnBoardHeight, sizeof(mnBoardHeight));
  out.write((char*) &mnRound      , sizeof(mnRound));
  for(int i = 0; i < 4; i++)
  {
    out.write((char*) &(mbvFirstPlaced[i]) , sizeof(mbvFirstPlaced[i]));
  }
  
  for(int nRow = 0; nRow < mnBoardHeight; nRow++)
  {
    for(int nCol = 0; nCol < mnBoardWidth; nCol++)
    {
      bool possible = maaPossible[nRow][nCol];
    
      out.write((char*) &(maaBoard[nRow][nCol]), sizeof(maaBoard[nRow][nCol]));
      out.write((char*) &possible,               sizeof(possible));
    }
  }
}

void BlokishBoard::Read(std::ifstream &in)
{
  in.read((char*) &mnBoardWidth , sizeof(mnBoardWidth ));
  in.read((char*) &mnBoardHeight, sizeof(mnBoardHeight));
  in.read((char*) &mnRound      , sizeof(mnRound));
  for(int i = 0; i < 4; i++)
  {
    in.read((char*) &(mbvFirstPlaced[i]) , sizeof(mbvFirstPlaced[i]));
  }
  
  for(int nRow = 0; nRow < mnBoardHeight; nRow++)
  {
    for(int nCol = 0; nCol < mnBoardWidth; nCol++)
    {
      bool possible;
      in.read((char*) &(maaBoard[nRow][nCol]),    sizeof(maaBoard[nRow][nCol]));
      in.read((char*) &possible,                  sizeof(possible));

      maaPossible[nRow][nCol] = possible;
    }
  }
}

void BlokishBoard::Reset()
{
  // Clear out the board.
  for(int nRow = 0; nRow < mnBoardHeight; nRow++)
  {
    for(int nCol = 0; nCol < mnBoardHeight; nCol++)
    {
      maaPossible[nRow][nCol] = false;
      maaBoard[nRow][nCol] = none;
      maaAge[nRow][nCol] = 0;
    }
  }

  // Signal that the board corners are usable.
  for(unsigned int i = 0; i < 4; i++)
    mbvFirstPlaced[i] = false;

  mnRound = 0;

  mMoveHistory.clear();
}

// Tries to place a piece on the board.
bool BlokishBoard::PlacePiece(BlokishPiece *piece, int row, int col)
{
  if(PieceCanBePlaced(piece, row, col))
  {
    Move lastMove; 
    lastMove.round = mnRound;

    mnRound++;

    const SquareVector &squares = piece->GetSquares();
    for(SquareVector::const_iterator itr = squares.begin(); itr != squares.end(); ++itr)
    {
      // Since it can be placed, shouldn't need bounds checking here.
      int nRow = row + itr->mY;
      int nCol = col + itr->mX;

      maaBoard[nRow][nCol] = piece->GetOwner();
      maaAge[nRow][nCol] = mnRound;
    }

    lastMove.firstPlaced = mbvFirstPlaced[piece->GetOwner()];
    if(mbvFirstPlaced[int(piece->GetOwner())] == false)
    {
      mbvFirstPlaced[int(piece->GetOwner())] = true;
    }

    lastMove.piece = *piece;
    lastMove.row = row;
    lastMove.col = col;

    mMoveHistory.push_back(lastMove);

    return true;
  }

  return false;
}

// Remove the last piece placed.
void BlokishBoard::UndoPlacePiece()
{
  Move &lastMove = mMoveHistory.back();
  mnRound = lastMove.round;
  
  const SquareVector &squares = lastMove.piece.GetSquares();
  for(SquareVector::const_iterator itr = squares.begin(); itr != squares.end(); itr++)
  {
    int nRow = lastMove.row + itr->mY;
    int nCol = lastMove.col + itr->mX;
    maaBoard[nRow][nCol] = none;
    maaAge[nRow][nCol] = 0;
  }

  mbvFirstPlaced[lastMove.piece.GetOwner()] = lastMove.firstPlaced;
  mMoveHistory.pop_back();
}

// Checks whether or not a piece can be placed at a particular position.
bool BlokishBoard::PieceCanBePlaced(BlokishPiece *piece, int row, int col) const
{
  if(false == mbvFirstPlaced[int(piece->GetOwner())])
  {
    if(PieceTouchesBoardCorner(piece, row, col) && PieceFits(piece, row, col))
    {
      return true;
    }
  }
  else
  {
    if(PieceFits(piece, row, col) && PieceTouchesPieceCorner(piece, row, col) && PieceDoesNotConflict(piece, row, col))
    {
      return true;
    }
  }

  return false;
}

void BlokishBoard::Print()
{
  for(int nRow = mnBoardHeight-1; nRow >= 0; nRow--)
  {
    for(int nCol = 0; nCol < mnBoardWidth; nCol++)
      std::cout << maaBoard[nRow][nCol];
    std::cout << "\n";
  }
  std::cout << "\n";
}

// Checks if the player is dead by trying out all possible combinations until a valid move is found.
bool BlokishBoard::IsDead(BlokishID player, PieceVector &piecesRemaining) const
{
  for(int nRow = 0; nRow < mnBoardHeight; nRow++)
  {
    for(int nCol = 0; nCol < mnBoardWidth; nCol++)
    {
      if(GetSpaceID(nRow, nCol) == none)
      {
        for(std::vector<BlokishPiece*>::iterator itr = piecesRemaining.begin(); itr != piecesRemaining.end(); itr++)
        {
          for(int nFlip = 0; nFlip < (*itr)->mnSides; nFlip++)
          {
            for(int nRotation = 0; nRotation < (*itr)->mnUniqueRotations; nRotation++)
            {
              (*itr)->RotateClockwise();
              if(PieceCanBePlaced(*itr, nRow, nCol))
                return false;
            }

            (*itr)->ReflectHorizontal();
          }

          /*
          // Rotate the piece
          (*itr)->RotateClockwise();
          if(PieceCanBePlaced(*itr, nRow, nCol))
            return false;

          (*itr)->RotateClockwise();
          if(PieceCanBePlaced(*itr, nRow, nCol))
            return false;

          (*itr)->RotateClockwise();
          if(PieceCanBePlaced(*itr, nRow, nCol))
            return false;

          (*itr)->RotateClockwise();
          if(PieceCanBePlaced(*itr, nRow, nCol))
            return false;

          // Flip the piece and try again.
          (*itr)->ReflectVertical();

          (*itr)->RotateClockwise();
          if(PieceCanBePlaced(*itr, nRow, nCol))
            return false;

          (*itr)->RotateClockwise();
          if(PieceCanBePlaced(*itr, nRow, nCol))
            return false;

          (*itr)->RotateClockwise();
          if(PieceCanBePlaced(*itr, nRow, nCol))
            return false;

          (*itr)->RotateClockwise();
          if(PieceCanBePlaced(*itr, nRow, nCol))
            return false;

          (*itr)->ReflectVertical();
          */
        }
      }
    }
  }

  return true;
}

int BlokishBoard::FindUsableSpaces(BlokishID player, const PieceVector &pieces, int numPieces)
{
  if(numPieces == 0)
    return 0;

  int numOpen = 0;

  for(int nRow = 0; nRow < mnBoardHeight; nRow++)
  {
    for(int nCol = 0; nCol < mnBoardWidth; nCol++)
    {
      maaPossible[nRow][nCol] = false;
    }
  }

  numPieces = std::min(numPieces, int(pieces.size()));

  for(int nRow = 0; nRow < mnBoardHeight; nRow++)
  {
    for(int nCol = 0; nCol < mnBoardWidth; nCol++)
    {
      for(int nPiece = 0; nPiece < numPieces; nPiece++)
      {
        BlokishPiece &piece = *(pieces[nPiece]);

        for(int nFlip = 0; nFlip < piece.mnSides; nFlip++)
        {
          for(int nRotation = 0; nRotation < piece.mnUniqueRotations; nRotation++)
          {
            if(PieceCanBePlaced(&piece, nRow, nCol))
            {
              const SquareVector &squares = piece.GetSquares();

              for(SquareVector::const_iterator squareItr = squares.begin(); squareItr != squares.end(); squareItr++)
              {
                int actualCol = nCol + squareItr->mX;
                int actualRow = nRow + squareItr->mY;

                if(maaPossible[actualRow][actualCol] == false)
                {
                  maaPossible[actualRow][actualCol] = true;
                  numOpen++;
                }
              }
            }

            piece.RotateClockwise();
          }

          piece.ReflectHorizontal();
        }
      }
    }
  }

  return numOpen;
}

int BlokishBoard::GetNumEnemyCorners(BlokishID player, const std::vector<PieceVector*> &pieces, int numPieces)
{
  int num = 0;
  if(first != player)
    num += FindUsableSpaces(first,  *pieces[0], numPieces);
  if(second != player)
    num += FindUsableSpaces(second, *pieces[1], numPieces);
  if(third != player)
    num += FindUsableSpaces(third,  *pieces[2], numPieces);
  if(fourth != player)
    num += FindUsableSpaces(fourth, *pieces[3], numPieces);

  return num;
}

bool BlokishBoard::PieceTouchesBoardCorner(BlokishPiece *piece, int row, int col) const
{
  const SquareVector &squares = piece->GetSquares();

  for(SquareVector::const_iterator itr = squares.begin(); itr != squares.end(); ++itr)
  {
    int nRow = row + itr->mY;
    int nCol = col + itr->mX;

    if      (nCol == 0                && nRow == 0)                 { return true; }
    else if (nCol == 0                && nRow == mnBoardHeight - 1) { return true; }
    else if (nCol == mnBoardWidth - 1 && nRow == mnBoardHeight - 1) { return true; }
    else if (nCol == mnBoardWidth - 1 && nRow == 0)                 { return true; }
  }

  return false;
}

bool BlokishBoard::PieceTouchesPieceCorner(BlokishPiece *piece, int row, int col) const
{ // Check if it touches the corner of another piece
  const SquareVector &squares = piece->GetSquares();

  for(SquareVector::const_iterator itr = squares.begin(); itr != squares.end(); ++itr)
  {
    int nRow = row + itr->mY;
    int nCol = col + itr->mX;
    BlokishID playerID = piece->GetOwner();
    
    // Check upper-left
    if(GetSpaceID(nRow - 1, nCol - 1) == playerID)
      return true;
      
    // Check upper-right
    if(GetSpaceID(nRow - 1, nCol + 1) == playerID)
      return true;

    // Check bottom-left
    if(GetSpaceID(nRow + 1, nCol - 1) == playerID)
      return true;

    // Check bottom-right
    if(GetSpaceID(nRow + 1, nCol + 1) == playerID)
      return true;
  }
 
  return false;
}

bool BlokishBoard::PieceDoesNotConflict(BlokishPiece *piece, int row, int col) const
{ // Check it it won't touch any of this player's other pieces
  const SquareVector &squares = piece->GetSquares();

  for(SquareVector::const_iterator itr = squares.begin(); itr != squares.end(); ++itr)
  {
    int nRow = row + itr->mY;
    int nCol = col + itr->mX;
    BlokishID playerID = piece->GetOwner();

    // Check left
    if(GetSpaceID(nRow, nCol - 1) == playerID)
      return false;
      
    // Check right
    if(GetSpaceID(nRow, nCol + 1) == playerID)
      return false;

    // Check above
    if(GetSpaceID(nRow - 1, nCol) == playerID)
      return false;

    // Check below
    if(GetSpaceID(nRow + 1, nCol) == playerID)
      return false;
  }
 
  return true;
}

bool BlokishBoard::PieceFits(BlokishPiece *piece, int row, int col) const
{ // Make sure it fits
  const SquareVector &squares = piece->GetSquares();
  
  for(SquareVector::const_iterator itr = squares.begin(); itr != squares.end(); ++itr)
  {
    int nRow = row + itr->mY;
    int nCol = col + itr->mX;
  
    if(GetSpaceID(nRow, nCol) != none)
    {
      return false;
    }
  }

  return true;
}
