/**
 * @file Player.java
 *
 * @author
 *    Anastasia Foti AEM:8959
 *    <anastaskf@ece.auth.gr>
 *
 * @author
 *    Christos Choutouridis AEM:8997
 *    <cchoutou@ece.auth.gr>
 */

package host.labyrinth;

import java.util.ArrayList;

/**
 * @brief
 *    This class represents the game's player
 */
class Player {
   /** Helper variables to keep track of the move() return values @see move() */
   static final int MOVE_DATA_SIZE        = 4;  /**< The move return data array size */
   static final int MOVE_TILE_ID          = 0;  /**< Index of the tileId information of the move */
   static final int MOVE_ROW              = 1;  /**< The index of row information */
   static final int MOVE_COLUMN           = 2;  /**< The index of column information */
   static final int MOVE_DICE             = 3;  /**< The index of dice information */

   /** @name Constructors */
   /** @{ */

   /**
    * Create a new player and put him at the row-column coordinates 
    * @param name       The name of the player
    * @param champion   Flag to indicate if a player is a `champion`
    * @param board      Reference to the board of the game
    * @param row        The row coordinate of initial player position
    * @param column     The column coordinate of initial player's position
    */
   Player(String name, boolean champion, Board board, int row, int column) throws Exception {
      this.playerId  = board.generatePlayerId();
      this.name      = name;
      this.board     = board;
      this.score     = 0;
      this.x         = column;
      this.y         = row;
      this.champion  = champion;
      this.dirCounter= new int[DirRange.End];      // yes we spoil some memory. Java is the worst.
      this.path      = new ArrayList<Integer[]>();
      int[] m        = {
            Position.toID(row, column), row, column, Const.noSupply
      };
      board.updateMove(m, playerId);
   }

   /**
    * Create a new player and put him at the row-column coordinates 
    * @param name       The name of the player
    * @param champion   Flag to indicate if a player is a `champion`
    * @param board      Reference to the board of the game
    * @param tileId     The tileId coordinate of player's initial position
    */
   Player(String name, boolean champion, Board board, int tileId) throws Exception {
      this.playerId  = board.generatePlayerId();
      this.name      = name;
      this.board     = board;
      this.score     = 0;
      this.x         = Position.toCol(tileId);
      this.y         = Position.toRow(tileId);
      this.champion  = champion;
      this.dirCounter= new int[DirRange.End];      // yes we spoil some memory. Java is the worst.
      this.path      = new ArrayList<Integer[]>();
      int[] m        = {
            tileId, Position.toRow(tileId), Position.toCol(tileId), Const.noSupply
      };
      board.updateMove(m, playerId);
   }
   /** @} */

   /** @name Player main application interface */
   /** @{ */

   /**
    * Player's move.
    *
    * A player first throws a dice to get a random direction. Then checks if the direction
    * is walkable. If it is, then goes to that tile and update player's data.
    * If the player is a champion then he also picks up a possible supply from the tile.
    *
    * @param id   The id of the starting tile.
    * @return     An array containing player's final position and possible supply of that position.
    *             The array format is:
    * <ul>
    * <li> int[0]: The tileId of the final player's position.
    * <li> int[1]: The row of the final player's position.
    * <li> int[2]: The column of the final player's position.
    * <li> int[3]: The dice/direction of the move.
    * </ul>
    */
   int[] move(int id) {
      // Initialize return array with the current data
      int[] ret = new int[MOVE_DATA_SIZE];
      ret[MOVE_TILE_ID]    = id;
      ret[MOVE_ROW]        = Position.toRow(id);
      ret[MOVE_COLUMN]     = Position.toCol(id);
      ret[MOVE_DICE]       = Direction.NONE;
      int supplyFlag =0, moveFlag =0;

      int diceDirection;
      do
         diceDirection = board.dice();          // throw the dice
      while (!board.isWalkable(id, diceDirection));
      moveFlag =1;                              // mark the successful move
      // Get next tile
      Position next = new Position(Position.toRow(id), Position.toCol(id), diceDirection);
      ret[MOVE_TILE_ID] = next.getId();         // Update move's return data
      ret[MOVE_ROW]     = y = next.getRow();
      ret[MOVE_COLUMN]  = x = next.getCol();
      ret[MOVE_DICE]    = diceDirection;
      // In case of a champion player, try also to pick a supply
      if (champion && (board.tryPickSupply(next.getId()) != Const.noSupply)) {
         supplyFlag =1;                         // mark the successful supply pickup
         ++score;                               // keep score
      }
      ++dirCounter[diceDirection];              // update direction counters
      board.updateMove(ret, playerId);
      // update path
      Integer[] p = {
            ret[MOVE_TILE_ID], diceDirection, moveFlag, supplyFlag,
            dirCounter[Direction.UP], dirCounter[Direction.RIGHT], dirCounter[Direction.DOWN], dirCounter[Direction.LEFT],
            Const.noSupply, Const.noOpponent
      };
      path.add(p);
      return ret;
   }

   /**
    * Prints round information for the player
    */
   void statistics() {
      if (!path.isEmpty()) {
         Integer[] last = path.get(path.size()-1);
         String who = String.format("%12s", name);
         System.out.print(who + ": score[" + score + "]" + ", dice =" + last[1] + ", tileId =" + last[0] + " (" + Position.toRow(last[0]) + ", " + Position.toCol(last[0]) + ")");
         if (last[2] == 0)
            System.out.println(" *Can not move.");
         else if (last[3] != 0)
            System.out.println(" *Found a supply.");
         else
            System.out.println("");
      }
   }

   /**
    * Prints final statistics for the player
    * @note
    *    We add this final_statistics() wrapper in order to provide polymorphism to
    *    Player class hierarchy and to be sure that we are not braking the
    *    Liskov substitution principle
    *    @see https://en.wikipedia.org/wiki/Liskov_substitution_principle
    */
   void final_statistics () { }

   /** Utility to access player's tileID */
   int playerTileId()   { return Position.toID(y, x); }
   /** Utility to access player's row position (row coordinate) */
   int playerRow()      { return y; }
   /** Utility to access player's column position (column coordinate) */
   int playerCol()      { return x; }
   /** @} */

   /**
    * @name Accessor/Mutator interface
    * @note
    *    Please consider not to use mutator interface. Its the abstraction killer :(
    *    We have added a bit of logic however, in order to make it a bit more safe.
    */
   /** @{ */
   int getPlayerId ()   { return playerId; }
   String getName()     { return name; }
   Board getBoard ()    { return board; }
   int getScore ()      { return score; }
   int getX()           { return x; }
   int getY()           { return y; }
   boolean getChampion(){ return champion; }
   int[] getDirCounter(){ return dirCounter; }
   ArrayList<Integer[]> getPath() {
      return path;
   }
   

   void setPlayerId(int id)   { playerId = id; }
   void setName(String name)  { this.name = name; }
   void setBoard (Board board){ this.board = board; }
   void setScore(int score)   { this.score = score; }
   void setX(int x) {
      assert (x >= 0 && x< Session.boardSize) : "X(column) coordinate must be in the range [0, Session.boardSize)";
      this.x = x;
   }
   void setY(int y) {
      assert (y >= 0 && y< Session.boardSize) : "Y(row) coordinate must be in the range [0, Session.boardSize)";
      this.y = y;
   }
   void setChampion (boolean champion) {
      this.champion = champion;
   }
   void setDirCounter(int[] dirCounter) {
      this.dirCounter = dirCounter;
   }

   void setPath(ArrayList<Integer[]> path) {
      this.path = path;
   }
   /** @} */

   /** @name Class data */
   /** @{ */
   protected int     playerId;      /**< The unique identifier of the player */
   protected String  name;          /**< The name of the player */ 
   protected Board   board;         /**< Reference to the session's boards */
   protected int     score;         /**< The current score of the player */
   protected int     x;             /**< The column coordinate of the player on the board */
   protected int     y;             /**< The row coordinate of the player on the board */
   protected boolean champion;      /**< Champion indicate a player who plays against the Minotaur */
   protected int     dirCounter[];
   protected ArrayList<Integer[]> path;
   /**<
    * our history. The integer[] format is:
    * <ul> 
    * <li> Integer[0]: tileId       - The tile id we choose for the move
    * <li> Integer[1]: dice         - The dice (a.k.a direction) of move
    * <li> Integer[2]: moveStatus   - True if it was successful (we can move in that direction)
    * <li> Integer[3]: tookSupply   - True if we took supply
    * <li> Integer[4]: upCounter    - Accumulator to count all the up moves
    * <li> Integer[5]: righrCounter - Accumulator to count all the right moves
    * <li> Integer[6]: downCounter  - Accumulator to count all the down moves
    * <li> Integer[7]: leftCounter  - Accumulator to count all the left moves
    * <li> Integer[8]: SupDistance  - The distance of the nearest supply (only for heuristic players)
    * <li> Integer[9]: OppDistance  - The distance of the nearest opponent (only for heuristic players)
    * </ul>
    * }
    */
   /** @} */
}
