/**
 * @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;
//import java.util.Arrays;

/**
 * @brief
 *    This class represents the game's player who cheats.
 */
class HeuristicPlayer extends Player {

   /** @name Constructors */
   /** @{ */
   public HeuristicPlayer(String name, boolean champion, Board board, int row, int column) {
      super(name, champion, board, row, column);
      path = new ArrayList<Integer[]>();
   }

   public HeuristicPlayer(String name, boolean champion, Board board, int tileId) {
      super(name, champion, board, tileId);
      path = new ArrayList<Integer[]>();
   }
   /** @} */

   /** @name Board's main application interface */
   /** @{ */
   
   /**
    * Utility to get the distance of a possible supply in some direction
    * @param currentPos    The current position of the player
    * @param direction     The direction to check
    * @return           The distance or Const.noSupply
    */
   int supplyInDirection(int currentPos, int direction) {
      Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos));

      for (int i=0 ; board.isWalkable(pos.getId(), direction) && i<Const.viewDistance ; ++i) {
         pos = new Position(Position.toRow(pos.getId()), Position.toCol(pos.getId()), direction);
         if (board.hasSupply(pos.getId()))
            return i+1;
      }
      return Const.noSupply;
   }

   /**
    * Utility to get the distance of a possible opponent in some direction
    * @param currentPos    The current position of the player
    * @param direction     The direction to check
    * @return           The distance or Const.noOpponent
    */
   int opponetInDirection(int currentPos, int direction) {
      Position pos  = new Position(Position.toRow(currentPos), Position.toCol(currentPos));
      int [][] opps = board.getOpponentMoves(playerId);

      for (int i=0 ; board.isWalkable(pos.getId(), direction) && i<Const.viewDistance ; ++i) {
         pos = new Position(Position.toRow(pos.getId()), Position.toCol(pos.getId()), direction);
         for (int o =0 ; o<opps.length; ++o) {
            if (opps[o][0] == pos.getId())
               return i+1;
         }
      }
      return Const.noOpponent;
   }

   double evaluate (int currentPos, int dice) {
      int opDist  = opponetInDirection (currentPos, dice);
      int supDist = supplyInDirection(currentPos, dice);

      // saturate
      opDist  = (opDist == Const.noOpponent) ? Integer.MAX_VALUE : opDist;
      supDist = (supDist == Const.noSupply)  ? Integer.MAX_VALUE : supDist;
      return 1.0/supDist * Const.supplyFactor
           - 1.0/opDist  * Const.opponentFactor; 
   }

   int directionOfMax (double[] eval, int[] eval_dir, int N) {
      double M = Double.NEGATIVE_INFINITY;
      int M_idx = -1;
      for (int i =0; i < N ; ++i) {
         if (eval[i] > M) {
            M = eval[i];
            M_idx = i;
         }
      }
      return eval_dir[M_idx];
   }

   boolean isUnevaluated (double[] eval, int N) {
      for (int i =0 ; i<N ; ++i)
         if (eval[i] != 0 && eval[i] != Double.NEGATIVE_INFINITY)
            return false;
      return true;
   }

   // Must return a new move always
   int getNextMove(int currentPos) {
      Range dirs        = new Range(DirRange.Begin, DirRange.End, DirRange.Step);
      int N             = dirs.size();
      double[] eval     = new double[N];
      int [] eval_dir   = new int[N];

      for (int i =0, dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get(), ++i) {
         if (board.isWalkable(currentPos, dir))
            eval[i]  = evaluate(currentPos, dir);
         else
            eval[i]  = Double.NEGATIVE_INFINITY;
         eval_dir[i] = dir;
      }
      int dir;
      if (isUnevaluated(eval, N)) {
         ShuffledRange r = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
         do
            dir = r.get();
         while (!board.isWalkable(currentPos, dir));
      }
      else {
         dir = directionOfMax (eval, eval_dir, N);
      }
      Position new_pos = new Position( Position.toRow(currentPos), Position.toCol(currentPos), dir );
      return new_pos.getId();
   }

   /**
    * HeuristicPlayer's move.
    *
    * A player of this kind cheats. He does not throw a dice to get a direction. In contrary he
    * calculates his next move very carefully.
    * 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 supplyId in case player picked one (Const.noSupply otherwise).   
    * </ul>
    */
   int[] move(int id) {
      // Initialize return array with the current data
      int[] ret = new int[Const.moveItems];
      ret[0] = getNextMove(id);
      ret[1] = y = Position.toRow(ret[0]);
      ret[2] = x = Position.toCol(ret[0]);
      int supplyFlag = 0;
      // In case of a champion player, try also to pick a supply
      if (champion && (ret[3] = board.tryPickSupply(ret[0])) != Const.noSupply) {
         ++score;                               // keep score
         ++supplyFlag;
         System.out.println(name + ":\t*Found a supply. [score: " + score + "]");
      }
      board.updateMove(ret, playerId);

      // Update supply and opponent distance
      int dir = Direction.get(id, ret[0]);
      int smin =DirRange.End, omin =DirRange.End;
      for (int d = DirRange.Begin ; d<DirRange.End ; d += DirRange.Step) {
         int s = supplyInDirection (ret[0], d);
         int o = opponetInDirection(ret[0], d);
         if (s < smin)  smin = s;
         if (o < omin)  omin = o;
      }
      Integer[] p = {dir, supplyFlag, smin, omin };

      path.add(p);
      return ret;
   }
   
   /** @} */
   /** @name Class data */
   /** @{ */
   private ArrayList<Integer[]>     path;   /**< our history
                                             * The integer[] format is:
                                             * { dice, tookSupply, SupplyDistance, OpponentDistance}
                                             */

   /** @} */
}
