/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: AreaOverlapMetric.java
 * Written by Team 2: Jan Barth, Iskandar Abudiab
 * 
 * This code has been developed at the Karlsruhe Institute of Technology (KIT), Germany, 
 * as part of the course "Multicore Programming in Practice: Tools, Models, and Languages".
 * Contact instructor: Dr. Victor Pankratius (pankratius@ipd.uka.de)
 *
 * 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.placement.simulatedAnnealing1.metrics;

import com.sun.electric.tool.placement.PlacementFrame.PlacementNode;
import com.sun.electric.tool.placement.simulatedAnnealing1.SimulatedAnnealing.PlacementNodePosition;

import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/** Parallel Placement
 * 
 * Metric to compute the ovelapping areas of the placement nodes.
 * This metric is incremental, i.e. given a new state it updates the score by only computing
 * the changes in the ovelapping areas caused by the nodes that were moved or swapped.
 **/
public class AreaOverlapMetric {

	private List<PlacementNode> allNodes;
	private com.sun.electric.tool.placement.simulatedAnnealing1.SimulatedAnnealing.IncrementalState incState;
	private double currentScore;
	private Map<PlacementNode, Double> oevrlapScores;

	/**
	 * Method to create a AreaOverlapMetric object.
	 * @param allNodes a list containing all <code>PlacementNode</code> objects.
	 * @param incrementalState an <code>IncremetntalState</code> object describing the current state.
	 */
	public AreaOverlapMetric(List<PlacementNode> allNodes, com.sun.electric.tool.placement.simulatedAnnealing1.SimulatedAnnealing.IncrementalState incrementalState){
		this.oevrlapScores = new HashMap<PlacementNode , Double>(allNodes.size());
		this.allNodes = allNodes;
		this.incState = incrementalState;
	}

	/**
	 * Initialises this metric.
	 * @param allNodes a list containing all <code>PlacementNode</code> objects.
	 * @return current score, which is the sum of all overlapping areas.
	 */
	public double init(List<PlacementNode> allNodes) {
		currentScore = 0;		
		for (PlacementNode node: allNodes) {
			oevrlapScores.put(node, Double.valueOf(0));
			computeOverlapForNode(node);
		}		
		return currentScore;		
	}
	
	/**
	 * Get current score.
	 * @return current score, which is the sum of all overlapping areas.
	 */
	public double getCurrentScore() {
		return currentScore;
	}

	/**
	 * Method that computes all overlapping areas caused by the given <code>PlacementNode</code>.
	 * @param theOne <code>PlacementNode</code> object for which the overlapping is to be computed.
	 * @return sum of all overlapping areas caused by this node.
	 */
	public double computeOverlapForNode(PlacementNode theOne) {
		double areaOverlap= 0.0;

		for (PlacementNode notTheOne: allNodes) {
			if (theOne == notTheOne) continue;
			double x1 = theOne.getPlacementX();
			double y1 = theOne.getPlacementY();
			double w1 = theOne.getWidth();
			double h1 = theOne.getHeight();
			int angle1 = theOne.getPlacementOrientation().getAngle();
			
			double x2 = notTheOne.getPlacementX();
			double y2 = notTheOne.getPlacementY();
			double w2 = theOne.getWidth();
			double h2 = theOne.getHeight();
			int angle2 = notTheOne.getPlacementOrientation().getAngle();
			
			//Check if node was moved or swapped.
			int originalPlacementNodeIndex = allNodes.indexOf(theOne);
			if (incState.isNodeChanged(originalPlacementNodeIndex)) {
				PlacementNodePosition changedNode = incState.getNodeFromState(originalPlacementNodeIndex);
				x1 = changedNode.getPlacementX();
				y1 = changedNode.getPlacementY();
				angle1 = changedNode.getPlacementOrientation().getAngle();
			}			
			
			//Get bounding rectangles and compute overlap.
			Rectangle2D.Double r1 = getRectangleForNode(x1, y1, w1, h1, angle1);
			Rectangle2D.Double r2 = getRectangleForNode(x2, y2, w2, h2, angle2);
			areaOverlap += getIntersectionArea(r1, r2);
		}
		
		double oldScore = oevrlapScores.get(theOne).doubleValue();
		if (oldScore > 0 && Math.abs(oldScore - areaOverlap) > 0.001) {
//			System.out.println("Score changed from " + oldScore + " to " + areaOverlap);
		}
		oevrlapScores.put(theOne, Double.valueOf(areaOverlap));
		currentScore = currentScore - oldScore + areaOverlap;
		return areaOverlap;
	}

	/**
	 * Method that creates a bounding <code>Rectangle2D.Double</code> given the nodes placement.
	 * @param x centre's X coordinate.
	 * @param y centre's Y coordinate.
	 * @param w width of the node
	 * @param h height of the node.
	 * @param angle the rotation angle of the node.
	 * @return <code>Rectangle2D.Double</code> object bounding the node.
	 */
	private Rectangle2D.Double getRectangleForNode(double x, double y, double w, double h, int angle){
		//TODO: Check the correctness of the generated rectangles.
		switch(angle){
			case 0:	 	return new Rectangle2D.Double(x - (w/2), y - (h/2), w, h); 
			case 90: 	return new Rectangle2D.Double(x - (h/2), y - (w/2), h, w); 
			case 180:	return new Rectangle2D.Double(x - (w/2), y - (h/2), w, h); 
			case 270:	return new Rectangle2D.Double(x - (h/2), y - (w/2), h, w); 
			default: 	return new Rectangle2D.Double(x - (w/2), y - (h/2), w, h);
		}
	}
	
	/**
	 * Method to compute the area overlapped by the two given <code>Rectangle2D.Double</code> objects.
	 * @param r1 the first <code>Rectangle2D.Double</code> object.
	 * @param r2 the second <code>Rectangle2D.Double</code> object
	 * @return returns the area being overlapped by the given rectangles.
	 */
	private double getIntersectionArea(Rectangle2D.Double r1, Rectangle2D.Double r2){
		double area = 0.0;
		Rectangle2D overlap = r1.createIntersection(r2);
		if(!overlap.isEmpty()) area = overlap.getWidth()*overlap.getHeight();
		return area;
	}

	/**
	 * Updates the metric score.
	 * @param index the index of the node that was moved or swapped.
	 * @return the new metric score.
	 */
	public double update(int index) {
		double BeforeIncUpdate = currentScore;
		computeOverlapForNode(allNodes.get(index));
		double incScore = currentScore;
		double newScore = currentScore;
		if (Math.abs(incScore - newScore) > 0.0001) {
			System.out.println("BeforeIncUpdate=" + BeforeIncUpdate + ", after update: incScore="+ incScore + ", correctScore=" + newScore);	
		}		
		return currentScore;
	}
}
