/*
 * VANETsim open source project - http://www.vanet-simulator.org
 * Copyright (C) 2008 - 2013  Andreas Tomandl, Florian Scheuer, Bernhard Gruber
 * 
 * This program 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.
 * 
 * This program 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 vanetsim.routing.A_Star;

import java.util.ArrayDeque;

import vanetsim.map.Node;
import vanetsim.map.Street;
import vanetsim.routing.RoutingAlgorithm;

/**
 * An implementation of the A*-algorithm. A* uses an heuristic to limit the distance calculations
 * and thus results in a much faster routing compared with Dijkstra. It also always finds the optimal
 * results like Dijkstra if the heuristic is correctly chosen (which it is in this implementation).
 * The implementation is based on the pseudocode from
 * <a href=http://de.wikipedia.org/wiki/A*-Algorithmus>German Wikipedia</a>
 * (link last time checked on 15.08.2008). However, it is modified and expanded with performance
 * optimizations and necessary changes for usage in a vanet-simulator (one-way-routes, barring...).
 * An own data structure is used which is basically the official <code>PriorityQueue</code> implementation but with
 * unnecessary function calls and casts removed. This uses about 25% less cpu than the original <code>PriorityQueue</code>
 * and about 40% less than a <code>TreeSet</code>. A basic <code>ArrayList</code> would take about 4x the performance.
 * 
 * Note for developers: It makes no sense to try to process streets which only have 2 crossings (no real junctions!) as one large street.
 * It surely saves some sqrt-operations but you trade this with lots of necessary checks and lookups and (what is a larger problem) you need
 * to rebuild the successors later because the vehicle needs a full path! Furthermore, it's also not a real A* anymore as you traverse nodes
 * which a real A* would not have checked because their f-value is too high. In a (not fully optimized) test scenario, this concept resulted 
 * in about 30% lower (!) performance so it's really not worth thinking about it.
 */
public final class A_Star_Algorithm implements RoutingAlgorithm{
	
	/**
	 * Instantiates a new A_Star_Algo.
	 */
	public A_Star_Algorithm(){
	}
	
	/**
	 * Main calculation function.
	 * 
	 * @param mode				The mode in which to operate. <code>0</code> means calculating with street lengths, <code>1</code> means calculating based on speed/time 
	 * @param direction			<code>0</code>=don't care about direction, <code>-1</code>=from startNode to endNode, <code>1</code>=from endNode to startNode
	 * @param startStreet		the street on which the start point lies
	 * @param startStreetPos	the position measured in cm from the startNode of the <code>startStreet</code>
	 * @param targetX			the x coordinate of the target point
	 * @param targetY			the y coordinate of the target point
	 * @param targetStreet		the street on which the target point lies
	 * @param targetStreetPos	the position measured in cm from the startNode of the <code>targetStreet</code>
	 * @param penaltyStreets	an array with all streets which have penalties.
	 * @param penaltyDirections	an array with directions corresponding to penaltyStreets. <code>1</code> in the array means from endNode to startNode, 
	 * 							<code>0</code> means both directions and <code>-1</code> means from startNode to endNode
	 * @param penalties			an array with all penalties measured in cm.
	 * @param penaltySize		how many penalties exist.
	 * @param additionalVar 	can be used to set the maximum speed for calculations in <code>mode=1</code>
	 * 
	 * @return an A_Star_Node which allows reconstructing the optimal path by going through the predecessors!
	 */
	private A_Star_Node computeRoute(int mode, int direction, Street startStreet, double startStreetPos, int targetX, int targetY, Street targetStreet, double targetStreetPos, Street[] penaltyStreets, int[] penaltyDirections, int[] penalties, int penaltySize, int additionalVar){
		int distanceAdd;
		long dx, dy;
		double f, g, distance;
		boolean target1found = false, target2found = false, endNodeMayBeDestination;
		int speed;
		Node tmpNode;
		int i, j;
		A_Star_Node currentNode, successor, startNode;
		Street[] outgoingStreets;
		Street tmpStreet;
		A_Star_Queue openList = new A_Star_Queue();
		
		// get LookupTable from factory. The LookupTable is needed for a mapping between our normal map nodes and the nodes for routing
		int[] tmp = new int[1];
		A_Star_LookupTable<Node, A_Star_Node> lookupTable = A_Star_LookupTableFactory.getTable(tmp);
		int counter = tmp[0];
		
		if(targetStreet.isOneway()) endNodeMayBeDestination = false;
		else endNodeMayBeDestination = true;
		// penalties are not considered for the first node as it should not be possible to escape from them 
		if(direction > -1){
			startNode = lookupTable.get(startStreet.getStartNode());
			if(startNode == null){		// not yet in table => put it
				startNode = new A_Star_Node(startStreet.getStartNode(), counter);
				lookupTable.put(startStreet.getStartNode(), startNode);
			} else {
				startNode.reset(counter);
				startNode.setPredecessor(null);
			}
			if(mode == 0){
				startNode.setF(startStreetPos);
				startNode.setG(startStreetPos);
			}
			else {	//time calculation
				if(startStreet.getSpeed() > additionalVar) speed = additionalVar;
				else speed = startStreet.getSpeed();
				startNode.setF(startStreetPos/speed);
				startNode.setG(startStreetPos/speed);
			}	
			startNode.setInOpenList(true);
			openList.add(startNode);
		}
		if(direction < 1){
			startNode = lookupTable.get(startStreet.getEndNode());
			if(startNode == null){		// not yet in table => put it
				startNode = new A_Star_Node(startStreet.getEndNode(), counter);
				lookupTable.put(startStreet.getEndNode(), startNode);
			} else {
				startNode.reset(counter);
				startNode.setPredecessor(null);
			}
			if(mode == 0){
				startNode.setF(startStreet.getLength() - startStreetPos);
				startNode.setG(startStreet.getLength() - startStreetPos);
			}
			else {	//time calculation
				if(startStreet.getSpeed() > additionalVar) speed = additionalVar;
				else speed = startStreet.getSpeed();
				startNode.setF((startStreet.getLength() - startStreetPos)/speed);
				startNode.setG((startStreet.getLength() - startStreetPos)/speed);
			}
			startNode.setInOpenList(true);
			openList.add(startNode);
		}
		do{
			// take and remove node with smallest f value (=first element)
			currentNode = openList.poll();
			// found target?
			if (endNodeMayBeDestination && currentNode.getRealNode() == targetStreet.getEndNode()){
				if(target1found){
					A_Star_LookupTableFactory.putTable(counter, lookupTable);
					return currentNode;
				}
				else {	//we're near the end but didn't add the costs for the last street yet
					if(mode == 0) f = currentNode.getF() + (targetStreet.getLength() - targetStreetPos);
					else {
						if(targetStreet.getSpeed() > additionalVar) speed = additionalVar;
						else speed = targetStreet.getSpeed();
						f = currentNode.getF() + ((targetStreet.getLength() - targetStreetPos)/speed);
					}
					currentNode.setF(f);
					currentNode.setG(f);
					openList.add(currentNode);	//the poll() has removed it but we need it again!
					target1found = true;
				}
			} else if(currentNode.getRealNode() == targetStreet.getStartNode()){
				if(target2found){
					A_Star_LookupTableFactory.putTable(counter, lookupTable);
					return currentNode;
				}
				else {	//we're near the end but didn't add the costs for the last street yet
					if(mode == 0) f = currentNode.getF() + targetStreetPos;
					else {	//time calculation
						if(targetStreet.getSpeed() > additionalVar) speed = additionalVar;
						else speed = targetStreet.getSpeed();
						f = currentNode.getF() + (targetStreetPos/speed);
					}
					currentNode.setF(f);
					currentNode.setG(f);
					openList.add(currentNode);	//the poll() has removed it but we need it again!
					target2found = true;
				}
			// not yet at target. Check all streets going out from this node
			} else {
				outgoingStreets = currentNode.getRealNode().getOutgoingStreets();	// takes automatically care of one-way-routes as the list only contains correct streets!
				for(i = 0; i < outgoingStreets.length; ++i){
					tmpStreet = outgoingStreets[i];
					tmpNode = tmpStreet.getStartNode();
					if (tmpNode == currentNode.getRealNode()) tmpNode = tmpStreet.getEndNode();		//get the next node and not the same again
					
					successor = lookupTable.get(tmpNode);		// get an A_Star_Node from an ordinary node
					if(successor == null){		// not yet in table => put it
						successor = new A_Star_Node(tmpNode, counter);
						lookupTable.put(tmpNode, successor);
					} else {
						if(successor.getCounter() != counter) successor.reset(counter);
					}
					
					// only treat this node when not already on ClosedList!
					if (successor.isInClosedList() == false){
						// find penalties
						distanceAdd = 0;
						if(penaltySize > 0){
							if(tmpStreet.getStartNode() == currentNode.getRealNode()){
								for(j = 0; j < penaltySize; ++j){
									if(penaltyStreets[j] == tmpStreet && penaltyDirections[j] < 1){
										if(distanceAdd < penalties[j]) distanceAdd = penalties[j];
									}
								}
							} else {
								for(j = 0; j < penaltySize; ++j){
									if(penaltyStreets[j] == tmpStreet && penaltyDirections[j] > -1){
										if(distanceAdd < penalties[j]) distanceAdd = penalties[j];
									}
								}
							}
							
						}
						
						dx = targetX - tmpNode.getX();
						dy = targetY - tmpNode.getY();
						distance = distanceAdd + Math.sqrt(dx * dx + dy * dy); 	// Pythagorean theorem: a^2 + b^2 = c^2
						
						if(mode == 0){	//distance calculation
							g = currentNode.getG() + tmpStreet.getLength();
							f = g + distance;
						} else {	//time calculation
							if(tmpStreet.getSpeed() > additionalVar) g = currentNode.getG() + (tmpStreet.getLength()/additionalVar);
							else g = currentNode.getG() + (tmpStreet.getLength()/tmpStreet.getSpeed());
							f = g + (distance/additionalVar);	//approximation based on maxspeed (stored in additionalVar) so that real time is always underestimated!
						}
						if(!successor.isInOpenList()){		// not yet investigated...
							successor.setPredecessor(currentNode);
							successor.setF(f);
							successor.setG(g);
							successor.setInOpenList(true);
							openList.add(successor);
						} else if (successor.getF() > f){		// previously found but now has better value
							if(target1found && successor.getRealNode() == targetStreet.getEndNode()){	//if the target street has a low speed we might overwrite it here with a wrong guessed value => calculate it precise!
								if(mode == 0) f = g + (targetStreet.getLength() - targetStreetPos);
								else {
									if(targetStreet.getSpeed() > additionalVar) speed = additionalVar;
									else speed = targetStreet.getSpeed();
									f = g + ((targetStreet.getLength() - targetStreetPos)/speed);
								}
								if(successor.getF() > f){
									successor.setPredecessor(currentNode);
									successor.setF(f);
									successor.setG(g);
									openList.signalDecreasedF(successor);
								}
							} else if(target2found && successor.getRealNode() == targetStreet.getStartNode()){
								if(mode == 0) f = g + targetStreetPos;
								else {	//time calculation
									if(targetStreet.getSpeed() > additionalVar) speed = additionalVar;
									else speed = targetStreet.getSpeed();
									f = g + (targetStreetPos/speed);
								}
								if(successor.getF() > f){
									successor.setPredecessor(currentNode);
									successor.setF(f);
									successor.setG(g);
									openList.signalDecreasedF(successor);
								}
							} else {	// the "normal" case is this one!
								successor.setPredecessor(currentNode);
								successor.setF(f);
								successor.setG(g);
								openList.signalDecreasedF(successor);
							}
						}
					}
				}
		        // current node has been completely investigated
				currentNode.setInClosedList(true);
				currentNode.setInOpenList(false);
			}
	    } while (!openList.isEmpty());
	    // there's no route to the destination!
		A_Star_LookupTableFactory.putTable(counter, lookupTable);
		return null;	    
	}
	
	/**
	 * Gets a routing result.
	 * 
	 * @param mode				The mode in which to operate. <code>0</code> means calculating with street lengths, <code>1</code> means calculating based on speed/time 
	 * @param direction			<code>0</code>=don't care about direction, <code>-1</code>=from startNode to endNode, <code>1</code>=from endNode to startNode
	 * @param startX			the x coordinate of the start point
	 * @param startY			the y coordinate of the start point
	 * @param startStreet		the street on which the start point lies
	 * @param startStreetPos	the position measured in cm from the startNode of the <code>startStreet</code>
	 * @param targetX			the x coordinate of the target point
	 * @param targetY			the y coordinate of the target point
	 * @param targetStreet		the street on which the target point lies
	 * @param targetStreetPos	the position measured in cm from the startNode of the <code>targetStreet</code>
	 * @param penaltyStreets	an array with all streets which have penalties.
	 * @param penaltyDirections	an array with directions corresponding to penaltyStreets. <code>1</code> in the array means from endNode to startNode, 
	 * 							<code>0</code> means both directions and <code>-1</code> means from startNode to endNode
	 * @param penalties			an array with all penalties measured in cm.
	 * @param penaltySize		how many penalties exist.
	 * @param additionalVar		can be used to set the maximum speed for calculations in <code>mode=1</code>
	 *
	 * 
	 * @return An <code>ArrayDeque</code> for returning the result. The first element will be the start node and the last will be the end node of the routing.
	 * 
	 * @see	vanetsim.routing.RoutingAlgorithm#getRouting(int, int, int, int, Street, double, int, int, Street, double, Street[], int[], int[], int, int)
	 */
	public ArrayDeque<Node> getRouting(int mode, int direction, int startX, int startY, Street startStreet, double startStreetPos, int targetX, int targetY, Street targetStreet, double targetStreetPos, Street[] penaltyStreets, int[] penaltyDirections, int[] penalties, int penaltySize, int additionalVar){
		A_Star_Node curNode = computeRoute(mode, direction, startStreet, startStreetPos, targetX, targetY, targetStreet, targetStreetPos, penaltyStreets, penaltyDirections, penalties, penaltySize, additionalVar);
		ArrayDeque<Node> result = new ArrayDeque<Node>(255);
		while(curNode != null){
			result.addFirst(curNode.getRealNode());
			curNode = curNode.getPredecessor();
		}
		return result;
	}
}