/**
 * @file Tile.java
 *
 * @author
 *    Christos Choutouridis
 *    <cchoutou@ece.auth.gr>
 *    AEM:8997
 */

package host.labyrinth;

/**
 * @brief
 *    This class is the representation of the board's tile
 *
 * Tiles are arranged on the board in square shape and they're identified by
 * an ID. This ID is the linear combination of x and y coordinate of the tile.
 */
class Tile {

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

   /**
    * The main constructor of the Tile constructed from (row,column)
    *
    * @param row     The row coordinate to place the Tile
    * @param col     The column coordinate to place the Tile
    * @param up      The existence of wall north of the tile
    * @param down    The existence of wall south of the tile
    * @param left    The existence of wall left of the tile
    * @param right   The existence of wall right of the tile
    */
   Tile(int row, int col, boolean up, boolean down, boolean left, boolean right) {
      // Boundary checks
      assert (row >= 0 && row< Session.boardSize) : "Row coordinate must be in the range [0, Session.boardSize)";
      assert (col >= 0 && col< Session.boardSize) : "Column coordinate must be in the range [0, Session.boardSize)";

      // Initialization
      this.tileId = Position.toID(row, col);
      this.x =col;
      this.y =row;
      this.up = up;
      this.down = down;
      this.left = left;
      this.right = right;
   }

   /**
    * The main constructor of the Tile constructed from tileId
    *
    * @param id      The tileId to place the Tile
    * @param up      The existence of wall north of the tile
    * @param down    The existence of wall south of the tile
    * @param left    The existence of wall left of the tile
    * @param right   The existence of wall right of the tile
    */
   Tile(int id, boolean up, boolean down, boolean left, boolean right) {
      // Boundary check
      assert (id >= 0 && id <= Position.toID(Session.boardSize-1, Session.boardSize-1))
         : "TileId must be in the range of [0, Session.boardSize^2)";
      // Initialization
      this.tileId = id;
      this.x      =Position.toCol(id);
      this.y      =Position.toRow(id);
      this.up     = up;
      this.down   = down;
      this.left   = left;
      this.right  = right;
   }

   /**
    * A deep copy constructor.
    */
   Tile (Tile t) {
      this.tileId    = t.tileId;
      this.x         = t.x;
      this.y         = t.y;
      this.up        = t.up;
      this.down      = t.down;
      this.left      = t.left;
      this.right     = t.right;
      // We achieve deep copy as the members are all primitives.
   }
   /** @} */

   /** @name Supply's main application interface */
   /** @{ */

   /**
    * @return the position of the tile as a Position object
    * @see Position
    */
   Position position ()   { return new Position (tileId); }

   /**
    * Set the position of the tile from a (row, column) pair
    * @param row  The row coordinate of the tile
    * @param col  The column coordinate of the tile
    * @return the position of the supply as a Position object
    * @note       This function also returns the supplyId to help in chained expressions.
    * @see Position
    */
   Position position (int row, int col) {
      // Boundary checks
      assert (row >= 0 && row< Session.boardSize) : "Row coordinate must be in the range [0, Session.boardSize)";
      assert (col >= 0 && col< Session.boardSize) : "Column coordinate must be in the range [0, Session.boardSize)";

      Position p  = new Position (row, col);
      this.x      = p.getCol();  // =col;
      this.y      = p.getRow();  // =row;
      this.tileId = p.getId();
      return p;
   }

   /**
    * Set the position of the tile from a tileId
    * @param tileId  The tileId position
    * @return        The position of the supply as Position object
    * @note          This function also returns the supplyId to help in chained expressions.
    * @see Position
    */
   Position position (int tileId) {
      // Boundary check
      assert (tileId >= 0 && tileId <= Position.toID(Session.boardSize-1, Session.boardSize-1))
         : "TileId must be in the range of [0, Session.boardSize^2)";

      Position p  = new Position (tileId);
      this.x      = p.getCol();
      this.y      = p.getRow();
      this.tileId = p.getId();   // =tileId;
      return p;
   }

   /**
    * Sets the tile's wall in the requested direction.
    * @param direction  The direction for the wall.
    */
   void setWall (int direction) {
      switch (direction) {
         case Direction.UP:   this.up     = true;  break;
         case Direction.DOWN: this.down   = true;  break;
         case Direction.LEFT: this.left   = true;  break;
         case Direction.RIGHT:this.right  = true;  break;
      }
   }

   /**
    * Clears the tile's wall in the requested direction.
    * @param direction  The direction for the wall 
    */
   void clearWall (int direction) {
      switch (direction) {
         case Direction.UP:   this.up     = false; break;
         case Direction.DOWN: this.down   = false; break;
         case Direction.LEFT: this.left   = false; break;
         case Direction.RIGHT:this.right  = false; break;
      }
   }

   /**
    * Checks if the tile has wall in the requested direction
    * @param direction  The direction to check
    * @return     True if there is a wall
    */
   boolean hasWall (int direction) {
      switch (direction) {
         case Direction.UP:    return up;
         case Direction.RIGHT: return right;
         case Direction.DOWN:  return down;
         case Direction.LEFT:  return left;
      }
      return false;
   }

   /**
    * Checks if the tile has walls and return the number of them
    * @return     The number of walls
    */
   int hasWalls () {
      return ((up?1:0) + (down?1:0) + (left?1:0) + (right?1:0));
   }

   /**
    * Utility to check if the tile has a supply.
    * @param supplies   Reference to supply array to check.
    * @return Const.noSupply if there is no supply or the supplyId if succeed. 
    */
   int hasSupply (Supply[] supplies) {
      for (Supply s: supplies)
         if (s.position().getId() == position().getId())
            return s.getSupplyId();
      return Const.noSupply;
   }

   /**
    * Utility to find a supply in the supplies array and removes it.
    * @param supplies   Reference to supply array to check
    * @param supplyId   The supplyId to search.
    */
   void pickSupply (Supply[] supplies, int supplyId) {
      for (Supply s: supplies)
         if (s.getSupplyId() == supplyId)
            s.removeSupply();
   }
   /** @} */

   /**
    * @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 getTileId () { return tileId; }
   int getX ()      { return x; }
   int getY ()      { return y; }
   boolean getUp ()    { return up; }
   boolean getDown ()  { return down; }
   boolean getLeft ()  { return left; }
   boolean getRight () { return right; }

   void setTileId(int tileId) {
      assert (tileId >= 0 && tileId <= Position.toID(Session.boardSize-1, Session.boardSize-1))
      : "TileId must be in the range of [0, Session.boardSize^2)";
      this.tileId = tileId;
      this.x = Position.toCol(tileId);
      this.y = Position.toRow(tileId);
   }
   void setX(int x) {
      assert (x >= 0 && x< Session.boardSize) : "X(column) coordinate must be in the range [0, Session.boardSize)";
      this.x = x;
      this.tileId = Position.toID(this.x, this.y);
   }
   void setY(int y) {
      assert (y >= 0 && y< Session.boardSize) : "Y(row) coordinate must be in the range [0, Session.boardSize)";
      this.y = y;
      this.tileId = Position.toID(this.x, this.y);
   }
   void setUp(boolean up)       { this.up = up; }
   void setDown(boolean down)   { this.down = down; }
   void setRight(boolean right) { this.right = right; }
   void setLeft(boolean left)   { this.left = left; }
   /** @} */

   /** @name Class data */
   /** @{ */
   private int tileId;        /**<
                               * The unique identifier of the tile. This is the linear combination of
                               * x and y coordinates of the tile
                               */
   private int x;             /**< The x coordinate(column) of the tile as if the board lies in the 1st quadrant */
   private int y;             /**< The y coordinate(row) of the tile as if the board lies in the 1st quadrant */
   private boolean up;        /**< Indicator of a wall in the north side of the tile */
   private boolean down;      /**< Indicator of a wall in the south side of the tile */
   private boolean left;      /**< Indicator of a wall in the left side of the tile */
   private boolean right;     /**< Indicator of a wall in the right side of the tile */
   /**
    * @warning
    *    We can calculate tileId from (x,y) so having both (x,y) and tile ID is error
    *    prone and not a good practice. Its easy to get them out of sync(code smell).
    *    We implement it just because its in the requirements of the assignment.
    *    @see Supply.supplyTileId
    */
   /** @} */
}
