/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: RegionBoundingBox.java
 * Written by: Christian Harnisch, Ingo Besenfelder, Michael Neumann (Team 3)
 *
 * Copyright (c) 2010, Static Free Software. All rights reserved.
 *
 * Electric(tm) 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 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) 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, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.routing.experimentalAStar2.map;

import com.sun.electric.tool.routing.experimentalAStar2.algorithm.AStarMapBase;
import com.sun.electric.tool.routing.experimentalAStar2.algorithm.AStarMapVisitorBase;
import com.sun.electric.tool.routing.experimentalAStar2.algorithm.AStarRegionNode;

/**
 * Acts as a proxy to the global region grid. This is used for storing the
 * bounds of a region bounding box and restrict the accesses of an
 * AStarRegionMachine.
 */
public class RegionBoundingBox extends AStarMapBase<AStarRegionNode>
{
  /* Tuning parameters */
  /**
   * Depicts by how many regions the region grid is enlarged upon calling
   * <code>enlarge()</code>
   */
  private static final int GROWTH_RATE = 2;

  /*
   * From here on below: Private variables for internal use. Don't try to tune
   * these.
   */
  /**
   * The region grid this bounding box is located in.
   */
  private AStarMapBase<AStarRegionNode> regionGrid;

  /**
   * These members depict the bounds of the bounding box. The values are meant
   * inclusively, in other words, an upper bound of 0 and a lower bound of 1
   * means that this bounding box includes layers 0 and 1.
   */
  private int westernBound, easternBound, northernBound, southernBound, upperBound, lowerBound;

  /**
   * Creates a new bounding box around the given pair of coordinates. The
   * created box is minimal, and can later be increased in size by calling
   * <code>enlarge</code>
   * 
   * @param regionGrid The region grid this bounding box is located in.
   * @param startX X position of the start point.
   * @param startY Y position of the start point.
   * @param startZ Z position of the start point.
   * @param goalX X position of the goal point.
   * @param goalY Y position of the goal point.
   * @param goalZ Z position of the goal point.
   */
  public RegionBoundingBox(AStarMapBase<AStarRegionNode> regionGrid, int startX, int startY, int startZ, int goalX, int goalY,
      int goalZ)
  {
    // Calculate region bounding box
    westernBound = Math.min(startX, goalX);
    easternBound = Math.max(startX, goalX);
    northernBound = Math.min(startY, goalY);
    southernBound = Math.max(startY, goalY);
    upperBound = Math.min(startZ, goalZ) - 1;
    lowerBound = Math.max(startZ, goalZ) + 1;
    this.regionGrid = regionGrid;
  }

//  /**
//   * Sets the size of the bounding box
//   * 
//   * @deprecated The bounds are currently calculated internally.
//   * 
//   * @param westernBound
//   * @param easternBound
//   * @param northernBound
//   * @param southernBound
//   * @param upperBound
//   * @param lowerBound
//   */
//  @Deprecated
//  public void setBounds(int westernBound, int easternBound, int northernBound, int southernBound, int upperBound, int lowerBound)
//  {
//    this.westernBound = westernBound;
//    this.easternBound = easternBound;
//    this.northernBound = northernBound;
//    this.southernBound = southernBound;
//    this.upperBound = upperBound;
//    this.lowerBound = lowerBound;
//  }

  /**
   * Increases the size of the bounding box by one in all directions. Can be
   * used for retrying to find a global path in this bounding box if path search
   * failed before.
   */
  public void enlarge()
  {
    this.westernBound -= GROWTH_RATE;
    this.easternBound += GROWTH_RATE;
    this.northernBound -= GROWTH_RATE;
    this.southernBound += GROWTH_RATE;
    this.upperBound -= GROWTH_RATE;
    this.lowerBound += GROWTH_RATE;
  }

  /**
   * Returns if all regions are currently not occupied by another search.
   * 
   * @return true if all regions are currently not occupied by another search.
   */
  public boolean isBoundingBoxFree()
  {
    for (int x = westernBound; x <= easternBound; x++)
      for (int y = northernBound; y <= southernBound; y++)
        for (int z = upperBound; z <= lowerBound; z++)
          if (!isOutsideMap(x, y, z))
            if (regionGrid.isTileBlocked(x, y, z))
              return false;
    return true;
  }

  /**
   * Marks all AStarRegionNodes inside this bounding box as occupied.
   */
  public void occupyBoundingBox()
  {
    for (int x = westernBound; x <= easternBound; x++)
      for (int y = northernBound; y <= southernBound; y++)
        for (int z = upperBound; z <= lowerBound; z++)
          if (!isOutsideMap(x, y, z))
            regionGrid.setTileBlocked(x, y, z, true);
  }

  /**
   * Marks all AStarRegionNodes inside this bounding box as not occupied.
   */
  public void releaseBoundingBox()
  {
    for (int x = westernBound; x <= easternBound; x++)
      for (int y = northernBound; y <= southernBound; y++)
        for (int z = upperBound; z <= lowerBound; z++)
          if (!isOutsideMap(x, y, z))
            regionGrid.setTileBlocked(x, y, z, false);
  }

  /*
   * (non-Javadoc)
   * 
   * @seecom.sun.electric.tool.routing.astar.t3.algorithm.AStarMapBase#
   * clearMapKeepBlocks()
   */
  @Override
  public void clearMapKeepBlocks()
  {
    throw new UnsupportedOperationException(
        "The region boundinx box cannot be cleared, you must call this method in the region grid.");
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.sun.electric.tool.routing.astar.t3.algorithm.AStarMapBase#clone()
   */
  @Override
  public AStarMapBase<AStarRegionNode> clone()
  {
    throw new UnsupportedOperationException("The region bounding box shouldn't be cloned to ensure mutual exclusive access.");
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * com.sun.electric.tool.routing.astar.t3.algorithm.AStarMapBase#getMaxXNodes
   * ()
   */
  @Override
  public int getMaxXNodes()
  {
    return (easternBound - westernBound);
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * com.sun.electric.tool.routing.astar.t3.algorithm.AStarMapBase#getMaxYNodes
   * ()
   */
  @Override
  public int getMaxYNodes()
  {
    return (southernBound - northernBound);
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * com.sun.electric.tool.routing.astar.t3.algorithm.AStarMapBase#getMaxZNodes
   * ()
   */
  @Override
  public int getMaxZNodes()
  {
    return (lowerBound - upperBound);
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * com.sun.electric.tool.routing.astar.t3.algorithm.AStarMapBase#isTileBlocked
   * (int, int, int)
   */
  @Override
  public boolean isTileBlocked(int x, int y, int z)
  {
    return (isOutsideBounds(x, y, z) || this.regionGrid.isTileBlocked(x, y, z));
  }

  private boolean isOutsideBounds(int x, int y, int z)
  {
    return (x < westernBound || x > easternBound || y < northernBound || y > southernBound || z < upperBound || z > lowerBound);
  }

  private boolean isOutsideMap(int x, int y, int z)
  {
    boolean outsideX = false;
    boolean outsideY = false;
    boolean outsideZ = false;
    if (regionGrid.getMaxXNodes() != 0)
      outsideX = (x < 0) || (x >= regionGrid.getMaxXNodes());
    if (regionGrid.getMaxYNodes() != 0)
      outsideY = (y < 0) || (y >= regionGrid.getMaxYNodes());
    if (regionGrid.getMaxZNodes() != 0)
      outsideZ = (z < 0) || (z >= regionGrid.getMaxZNodes());
    return (outsideX || outsideY || outsideZ);
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * com.sun.electric.tool.routing.astar.t3.algorithm.AStarMapBase#nodeAt(int,
   * int, int)
   */
  @Override
  public AStarRegionNode nodeAt(int x, int y, int z)
  {
    if (isOutsideBounds(x, y, z))
      return null;
    else
      return this.regionGrid.nodeAt(x, y, z);
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * com.sun.electric.tool.routing.astar.t3.algorithm.AStarMapBase#setTileBlocked
   * (int, int, int, boolean)
   */
  @Override
  public void setTileBlocked(int x, int y, int z, boolean blockedStatus)
  {
    this.regionGrid.setTileBlocked(x, y, z, blockedStatus);
  }

  /**
   * Lets the visitor visit the neighbours of <code>origin</code> in six
   * directions, that is four in the same layer and its neighbours above and
   * below.
   * 
   * @param origin
   * @param visitor
   */
  @Override
  public void visitNeighboursOf(AStarRegionNode origin, AStarMapVisitorBase<AStarRegionNode> visitor)
  {
    int x = origin.getX();
    int y = origin.getY();
    int z = origin.getZ();
    if (!isOutsideBounds(x, y - 1, z))
      if (!isOutsideMap(x, y - 1, z))
        visitor.visitNeighbour(origin, x, y - 1, z);
    if (!isOutsideBounds(x + 1, y, z))
      if (!isOutsideMap(x + 1, y, z))
        visitor.visitNeighbour(origin, x + 1, y, z);
    if (!isOutsideBounds(x, y + 1, z))
      if (!isOutsideMap(x, y + 1, z))
        visitor.visitNeighbour(origin, x, y + 1, z);
    if (!isOutsideBounds(x - 1, y, z))
      if (!isOutsideMap(x - 1, y, z))
        visitor.visitNeighbour(origin, x - 1, y, z);
    if (!isOutsideBounds(x, y, z + 1))
      if (!isOutsideMap(x, y, z + 1))
        visitor.visitNeighbour(origin, x, y, z + 1);
    if (!isOutsideBounds(x, y, z - 1))
      if (!isOutsideMap(x, y, z - 1))
        visitor.visitNeighbour(origin, x, y, z - 1);
  }
}
