/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: AStarRegionMachine.java
 * Written by: Christian Harnisch, Ingo Besenfelder, Michael Neumann (Team 3)
 *
 * Copyright (c) 2010, Oracle and/or its affiliates. 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.experimentalAStar3.machine;

import java.util.List;

import com.sun.electric.tool.routing.experimentalAStar3.algorithm.AStar;
import com.sun.electric.tool.routing.experimentalAStar3.algorithm.AStarGoalBase;
import com.sun.electric.tool.routing.experimentalAStar3.algorithm.AStarMapBase;
import com.sun.electric.tool.routing.experimentalAStar3.algorithm.AStarMapVisitorBase;
import com.sun.electric.tool.routing.experimentalAStar3.algorithm.AStarNode;
import com.sun.electric.tool.routing.experimentalAStar3.algorithm.AStarRegionNode;
import com.sun.electric.tool.routing.experimentalAStar3.memorymanager.ObjectPool;
import com.sun.electric.tool.routing.experimentalAStar3.storage.AStarClosedListReferencing;
import com.sun.electric.tool.routing.experimentalAStar3.storage.AStarOpenListCheapList;

/**
 * @author Christian Harnisch
 * 
 */
public class AStarRegionMachine implements AStarMachine<AStarRegionNode>, AStarMapVisitorBase<AStarRegionNode>
{
  private ObjectPool<AStarRegionNode> nodePool;

  private AStarOpenListCheapList<AStarRegionNode> openList;

  private AStarClosedListReferencing<AStarRegionNode> closedList;

  private AStarMapBase<AStarRegionNode> map = null;

  private AStarGoalBase<AStarRegionNode> goal = null;

  public AStarRegionMachine(ObjectPool<AStarRegionNode> nodePool)
  {
    this.nodePool = nodePool;
    this.openList = new AStarOpenListCheapList<AStarRegionNode>();
    this.closedList = new AStarClosedListReferencing<AStarRegionNode>();
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * com.sun.electric.tool.routing.astar.t3.machine.AStarMachine#findPath(int,
   * int, int, int, int, int)
   */
  public List<AStarRegionNode> findPath(int startX, int startY, int startZ, int goalX, int goalY, int goalZ)
  {
    if (map == null)
      throw new IllegalStateException("The searched map must be specified before path search can be done.");
    if (this.goal == null)
      throw new IllegalStateException("The goal object must be specified before path search can be done.");

    AStarRegionNode startRegion = map.nodeAt(startX, startY, startZ);
    AStarRegionNode goalRegion = map.nodeAt(goalX, goalY, goalZ);

    assert (startRegion != null);
    assert (goalRegion != null);

    this.goal.setGoalNode(goalRegion);
    int goalDistance = this.goal.distanceToGoal(startX, startY, startZ);
    goalRegion.initialize(null, goalDistance, 0, goalDistance, goalX, goalY, goalZ);

    startRegion.initialize(null, 0, goalDistance, goalDistance, startX, startY, startZ);
    // openList.clearOpenList();
    // closedList.clearClosedList();
    openList.addNodeToOpenList(startRegion);
    return AStar.findPath(openList, closedList, map, this.goal, nodePool, this);
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * com.sun.electric.tool.routing.astar.t3.machine.AStarMachine#setUpSearchSpace
   * (com.sun.electric.tool.routing.astar.t3.algorithm.AStarMapBase,
   * com.sun.electric.tool.routing.astar.t3.algorithm.AStarGoalBase)
   */
  public void setUpSearchSpace(AStarMapBase<AStarRegionNode> newMap, AStarGoalBase<AStarRegionNode> newGoal)
  {
    if (newMap == null)
      throw new IllegalArgumentException("The map to search may not be null.");
    if (newGoal == null)
      throw new IllegalArgumentException("The goal object may not be null.");
    this.map = newMap;
    openList.setMap(map);
    closedList.setMap(map);
    this.goal = newGoal;
    this.goal.setNodeStorage(this.openList);
  }

//  /**
//   * Searches are done in a bounding rectangle around start and goal region, and
//   * on a limited number of layers.<br>
//   * By default, this rectangle and the number of layers is minimal. With
//   * setting the margin width to non-zero, an additional margin of regions and
//   * layers is used for search.<br>
//   * It is also possible to define a minimum number of additional layers. This
//   * is usefol to ensure that routing may switch to other layers, even when
//   * start and end node are located on the same.
//   * 
//   * @param marginWidth The new margin width.
//   * @param minimumLayerMargin The new number of additional layers to use.
//   */
  /*
   * public void setMarginWidth(int marginWidth, int minimumLayerMargin) { if
   * (marginWidth < 0) throw new
   * IllegalArgumentException("Margin width must be greater or equal 0, but was "
   * + marginWidth); if (minimumLayerMargin < 0) throw new
   * IllegalArgumentException
   * ("Minimum layer margin must be greater or equal 0, but was " +
   * minimumLayerMargin); }
   */

  /*
   * (non-Javadoc)
   * 
   * @seecom.sun.electric.tool.routing.astar.t3.algorithm.AStarMapVisitorBase#
   * visitNeighbour
   * (com.sun.electric.tool.routing.astar.t3.algorithm.AStarNodeBase, int, int,
   * int)
   */
  public void visitNeighbour(AStarRegionNode origin, int x, int y, int z)
  {
    // If there's no capacity into this direction, don't visit.
    if (origin.getX() == x)
      if ((!origin.isTerminalRegion() && origin.getVerticalCapacity() == 0)
          || (!map.nodeAt(x, y, z).isTerminalRegion() && map.nodeAt(x, y, z).getVerticalCapacity() == 0))
        return;
    if (origin.getY() == y)
      if ((!origin.isTerminalRegion() && origin.getHorizontalCapacity() == 0)
          || (!map.nodeAt(x, y, z).isTerminalRegion() && map.nodeAt(x, y, z).getHorizontalCapacity() == 0))
        return;

    int costFromStart = origin.getCostFromStart() + goal.getNodeCost(origin, x, y, z);
    int costToGoal = goal.distanceToGoal(x, y, z);
    int totalCost = costFromStart + costToGoal;

    // Check if on open/closed list
    // If yes, check if new path is more efficient (lower f value)
    // and update path if so.
    AStarRegionNode foundNode = openList.findOpenNode(x, y, z);
    AStarNode portal = null;
    boolean foundCheaperPath = false;
    if (foundNode != null)
    {
      if (totalCost < foundNode.getTotalCost())
      {
        // If no portal can be found, don't visit.
        portal = this.findPortal(origin, foundNode);// TODO: Do
        // it!
        if (portal != null)
        {
          foundCheaperPath = true;
          openList.removeNodeFromOpenList(foundNode);
        }
      }
    }
    else
    {
      foundNode = closedList.findClosedNode(x, y, z);
      if (foundNode != null)
      {
        if (totalCost < foundNode.getTotalCost())
        {
          // If no portal can be found, don't visit.
          portal = this.findPortal(origin, foundNode); // TODO: Do
          // it!
          if (portal != null)
          {
            foundCheaperPath = true;
            closedList.removeNodeFromClosedList(foundNode);
          }
        }
      }
      else
      {
        foundNode = map.nodeAt(x, y, z);
        // If no portal can be found, don't visit.
        portal = this.findPortal(origin, foundNode);
        if (portal != null)
        {
          // Node not found in open and closed list, thus put a
          // new one on the open list.
          foundCheaperPath = true;

        }
      }
    }

    if (foundCheaperPath)
    {
      foundNode.initialize(origin, costFromStart, costToGoal, totalCost, x, y, z);
      foundNode.setEntryPoint(portal);
      openList.addNodeToOpenList(foundNode);
    }
  }

  public AStarNode findPortal(AStarRegionNode originRegion, AStarRegionNode targetRegion)
  {
    /*
     * TODO: hier werden die entry points gesetzt. Die exit points kann man erst
     * nachdem die komplette regionen-route feststeht, setzen.
     */
    AStarNode result = null;
    int viaX = originRegion.getEntryPoint().getX();
    int viaY = originRegion.getEntryPoint().getY();
    if (originRegion.getZ() != targetRegion.getZ())
    {
      // if (viaX == 0 || viaX == originRegion.width - 1)
      // {
      // int count = 0;
      // while (viaY + count < originRegion.height || viaY - count >= 0)
      // {
      // if (viaY + count < originRegion.height &&
      // !originRegion.isTileBlocked(viaX, viaY + count)
      // && !targetRegion.isTileBlocked(viaX, viaY + count)
      // && originRegion.routingMap.nodeAt(viaX, viaY + count,
      // 0).getTerminalCount() == 0
      // && targetRegion.routingMap.nodeAt(viaX, viaY + count,
      // 0).getTerminalCount() == 0)
      // return targetRegion.routingMap.nodeAt(viaX, viaY + count, 0);
      // if (viaY - count >= 0 && !originRegion.isTileBlocked(viaX, viaY -
      // count)
      // && !targetRegion.isTileBlocked(viaX, viaY - count)
      // && originRegion.routingMap.nodeAt(viaX, viaY - count,
      // 0).getTerminalCount() == 0
      // && targetRegion.routingMap.nodeAt(viaX, viaY - count,
      // 0).getTerminalCount() == 0)
      // return targetRegion.routingMap.nodeAt(viaX, viaY - count, 0);
      // count++;
      // }
      // }
      // else if (viaY == 0 || viaY == originRegion.height - 1)
      // {
      // int count = 0;
      // while (viaX + count < originRegion.width || viaX - count >= 0)
      // {
      // if (viaX + count < originRegion.width &&
      // !originRegion.isTileBlocked(viaX + count, viaY)
      // && !targetRegion.isTileBlocked(viaX + count, viaY)
      // && originRegion.routingMap.nodeAt(viaX + count, viaY,
      // 0).getTerminalCount() == 0
      // && targetRegion.routingMap.nodeAt(viaX + count, viaY,
      // 0).getTerminalCount() == 0)
      // return targetRegion.routingMap.nodeAt(viaX + count, viaY, 0);
      // if (viaX - count >= 0 && !originRegion.isTileBlocked(viaX - count,
      // viaY)
      // && !targetRegion.isTileBlocked(viaX - count, viaY)
      // && originRegion.routingMap.nodeAt(viaX - count, viaY,
      // 0).getTerminalCount() == 0
      // && targetRegion.routingMap.nodeAt(viaX - count, viaY,
      // 0).getTerminalCount() == 0)
      // return targetRegion.routingMap.nodeAt(viaX - count, viaY, 0);
      // count++;
      // }
      // }
      // else
      // somwhere in the middle
      // {
      int count = 0;
      while (viaY + count < originRegion.height || viaY - count >= 0 || viaX + count < originRegion.width || viaX - count >= 0)
      {
        if (viaX + count < originRegion.width && !originRegion.isTileBlocked(viaX + count, viaY)
            && !targetRegion.isTileBlocked(viaX + count, viaY)
            && originRegion.routingMap.nodeAt(viaX + count, viaY, 0).getTerminalCount() == 0
            && targetRegion.routingMap.nodeAt(viaX + count, viaY, 0).getTerminalCount() == 0)
          return targetRegion.routingMap.nodeAt(viaX + count, viaY, 0);
        if (viaX - count >= 0 && !originRegion.isTileBlocked(viaX - count, viaY)
            && !targetRegion.isTileBlocked(viaX - count, viaY)
            && originRegion.routingMap.nodeAt(viaX - count, viaY, 0).getTerminalCount() == 0
            && targetRegion.routingMap.nodeAt(viaX - count, viaY, 0).getTerminalCount() == 0)
          return targetRegion.routingMap.nodeAt(viaX - count, viaY, 0);
        if (viaY + count < originRegion.height && !originRegion.isTileBlocked(viaX, viaY + count)
            && !targetRegion.isTileBlocked(viaX, viaY + count)
            && originRegion.routingMap.nodeAt(viaX, viaY + count, 0).getTerminalCount() == 0
            && targetRegion.routingMap.nodeAt(viaX, viaY + count, 0).getTerminalCount() == 0)
          return targetRegion.routingMap.nodeAt(viaX, viaY + count, 0);
        if (viaY - count >= 0 && !originRegion.isTileBlocked(viaX, viaY - count)
            && !targetRegion.isTileBlocked(viaX, viaY - count)
            && originRegion.routingMap.nodeAt(viaX, viaY - count, 0).getTerminalCount() == 0
            && targetRegion.routingMap.nodeAt(viaX, viaY - count, 0).getTerminalCount() == 0)
          return targetRegion.routingMap.nodeAt(viaX, viaY - count, 0);
        count++;
      }
      // }
      return null;
    }

    int ox = originRegion.getX();
    int fx = targetRegion.getX();
    int oy = originRegion.getY();
    int fy = targetRegion.getY();

    // if foundNode is to the east
    if (ox == fx - 1)
    {
      int targetX = 0;
      int originX = originRegion.width - 1;
      int y = originRegion.getEntryPoint().getY();
      int count = 0;
      while (y + count < originRegion.height || y - count >= 0)
      {
        if (y + count < originRegion.height && originRegion.isPortalWithPath(originX, y + count, false)
            && targetRegion.isPortalWithPath(targetX, y + count, false))
          return targetRegion.getMap(false).nodeAt(targetX, y + count, 0);
        if (y - count >= 0 && originRegion.isPortalWithPath(originX, y - count, false)
            && targetRegion.isPortalWithPath(targetX, y - count, false))
          return targetRegion.getMap(false).nodeAt(targetX, y - count, 0);
        count++;
      }
    }
    // if foundNode is to the west
    else if (ox == fx + 1)
    {
      int targetX = originRegion.width - 1;
      int originX = 0;
      int y = originRegion.getEntryPoint().getY();
      int count = 0;
      while (y + count < originRegion.height || y - count >= 0)
      {
        if (y + count < originRegion.height && originRegion.isPortalWithPath(originX, y + count, false)
            && targetRegion.isPortalWithPath(targetX, y + count, false))
          return targetRegion.getMap(false).nodeAt(targetX, y + count, 0);
        if (y - count >= 0 && originRegion.isPortalWithPath(originX, y - count, false)
            && targetRegion.isPortalWithPath(targetX, y - count, false))
          return targetRegion.getMap(false).nodeAt(targetX, y - count, 0);
        count++;
      }
    }
    // if foundNode is to the north
    else if (oy == fy + 1)
    {
      int targetY = originRegion.height - 1;
      int originY = 0;
      int x = originRegion.getEntryPoint().getX();
      ;
      int count = 0;

      while (x + count < originRegion.width || x - count >= 0)
      {
        if (x + count < originRegion.width && originRegion.isPortalWithPath(x + count, originY, true)
            && targetRegion.isPortalWithPath(x + count, targetY, true))
          return targetRegion.getMap(false).nodeAt(x + count, targetY, 0);
        if (x - count >= 0 && originRegion.isPortalWithPath(x - count, originY, true)
            && targetRegion.isPortalWithPath(x - count, targetY, true))
          return targetRegion.getMap(false).nodeAt(x - count, targetY, 0);
        count++;
      }
    }
    // if foundNode is to the south
    else if (oy == fy - 1)
    {
      int targetY = 0;
      int originY = originRegion.height - 1;
      int x = originRegion.getEntryPoint().getX();
      ;
      int count = 0;
      while (x + count < originRegion.width || x - count >= 0)
      {
        if (x + count < originRegion.width && originRegion.isPortalWithPath(x + count, originY, true)
            && targetRegion.isPortalWithPath(x + count, targetY, true))
          return targetRegion.getMap(false).nodeAt(x + count, targetY, 0);
        if (x - count >= 0 && originRegion.isPortalWithPath(x - count, originY, true)
            && targetRegion.isPortalWithPath(x - count, targetY, true))
          return targetRegion.getMap(false).nodeAt(x - count, targetY, 0);
        count++;
      }
    }
    return result;
  }
}
