/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: MSTMetric.java
 * Written by Team 6: Sebastian Roether, Jochen Lutz
 * 
 * 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.simulatedAnnealing2;

import com.sun.electric.tool.placement.PlacementFrame.PlacementNetwork;
import com.sun.electric.tool.placement.PlacementFrame.PlacementNode;
import com.sun.electric.tool.placement.PlacementFrame.PlacementPort;

import java.util.Arrays;
import java.util.Map;

public final class MSTMetric extends Metric
{
	Edge[] edges = null;
	UnionFind connected = null;
	
	/**
	 * A connection of two ports and its (squared) euclidean distance
	 */
	private class Edge implements Comparable<Edge>
	{	
		int from;
		int to;
		int length;
		
		public Edge(PlacementPort from, PlacementPort to, int fi, int ti, Map<PlacementNode, ProxyNode> hm, ProxyNode[] originals, ProxyNode[] replacements )
		{
			this.from = fi;
			this.to = ti;

			double x_1, y_1, x_2, y_2;

			if ( hm == null ) {
				x_1 = from.getRotatedOffX() + from.getPlacementNode().getPlacementX();
				y_1 = from.getRotatedOffY() + from.getPlacementNode().getPlacementY();
				x_2 = to.getRotatedOffX() + to.getPlacementNode().getPlacementX();
				y_2 = to.getRotatedOffY() + to.getPlacementNode().getPlacementY();
			}
			else {
				ProxyNode fromNode = hm.get(from.getPlacementNode());
				ProxyNode toNode   = hm.get(to.getPlacementNode());

				for(int i = 0; i < originals.length; i++) {
					if(fromNode == originals[i]) fromNode = replacements[i];
					if(toNode   == originals[i]) toNode   = replacements[i];
				}

				x_1 = from.getRotatedOffX() + fromNode.getPlacementX();
				y_1 = from.getRotatedOffY() + fromNode.getPlacementY();
				x_2 = to.getRotatedOffX() + toNode.getPlacementX();
				y_2 = to.getRotatedOffY() + toNode.getPlacementY();
			}
			
			
			length = (int)(((x_1 - x_2) * (x_1 - x_2) + (y_1 - y_2) * (y_1 - y_2)) * 1000);
		}
		
		public int compareTo(Edge other)
		{
			return this.length - other.length;
		}
	}
	
	// TODO:
	// a=(a,b), b=(a,c) Kanten von a nach b,c
	// Wenn |b| > |a| und winkel < 90° dann ist b Hypothenuse von abc und bc ist kürzer als ac
	// weswegen ac nicht im MST sein kann
	// Kann eigentlich nur 4 Kanten pro Port geben ... sonst wäre vielleicht auch der Algorithmus von Prim besser...
	public MSTMetric( PlacementNetwork net, Map<PlacementNode, ProxyNode> hm, ProxyNode[] originals, ProxyNode[] replacements )
	{
		PlacementPort[] ports = new PlacementPort[net.getPortsOnNet().size()];
		ports = net.getPortsOnNet().toArray(ports);
		connected = new UnionFind(ports.length);

		// create one edge for every possible connection of two ports
		edges = new Edge[(ports.length - 1) * ports.length / 2];
		for(int i = 0, c = 0; i < ports.length; i++)
		{
			for(int j = i + 1; j < ports.length; j++, c++)
			{
				edges[c] = new Edge(ports[i], ports[j], i, j, hm, originals, replacements );
			}
		}
	}
	
	public MSTMetric( PlacementNetwork net ) {
		this( net, null, new ProxyNode[]{}, new ProxyNode[]{} );
	}
	
	/**
	 * Implementation of Kruskal MST algorithm. 
	 * @return total length of the minimum spanning tree
	 */
	public double compute() {
		double length = 0;
		int unions = connected.nodes.length - 1;
		
		Arrays.sort(edges);
		
		// n sets are merged after n - 1 merge operations
		for(int i = 0; i < edges.length && unions > 0; i++)
		{
			int subgraph1 = edges[i].from;
			int subgraph2 = edges[i].to;
			
			// if both ends of the connection are in different sets, the
			// edge is part of the mst
			if(connected.find(subgraph1) != connected.find(subgraph2))
			{
				connected.union(subgraph1, subgraph2);
				unions--;
				length += Math.sqrt((double)edges[i].length / 1000);
			}
		}
		return length;
	}
	
	
	/** 
	 * Union-Find implementation. Used to detect cycles within a graph
	 */
	static class UnionFind {
	    Node[] nodes;
	    Node[] stack;

	    public UnionFind(int size) {
	        nodes = new Node[size];
	        stack = new Node[size];
	    }

	    /**
	     * Searches the disjoint sets for a given integer.  Returns the set
	     * containing the integer a.
	     */
	    private Node findNode(int a) {
	          Node na = nodes[a];

	          if (na == null) {
	              // Start a new set with a
	              Node root = new Node(a);

	              root.child = new Node(a);
	              root.child.parent = root;

	              nodes[a] = root.child;

	              return root;
	          }

	          return findNode(na);
	    }

	    /**
	     * Returns the integer value associated with the first <tt>Node</tt>
	     * in a set.
	     */
	    public int find(int a) {
	        return findNode(a).value;
	    }

	    /**
	     * Finds the set containing a given Node.
	     */
	    private Node findNode(Node node) {
	        int top = 0;

	        // Find the child of the root element.
	        while (node.parent.child == null) {
	            stack[top++] = node;
	            node = node.parent;
	        }

	        // Do path compression on the way back down.
	        Node rootChild = node;

	        while (top > 0) {
	            node = stack[--top];
	            node.parent = rootChild;
	        }

	        return rootChild.parent;
	    }

	    /**
	     * Returns true if a and b are in the same set.
	     */
	    public boolean isEquiv(int a, int b) {
	        return findNode(a) == findNode(b);
	    }

	    /**
	     * Combines the set that contains a with the set that contains b.
	     */
	    public void union(int a, int b) {
	        Node na = findNode(a);
	        Node nb = findNode(b);

	        if (na == nb) {
	            return;
	        }

	        // Link the smaller tree under the larger.
	        if (na.rank > nb.rank) {
	            // Delete nb.
	            nb.child.parent = na.child;
	            na.value = b;
	        }
	        else {
	            // Delete na.
	            na.child.parent = nb.child;
	            nb.value = b;

	            if (na.rank == nb.rank) {
	                nb.rank++;
	            }
	        }
	    }

	    static class Node {
	        Node parent;  // The root of the tree in which this Node resides
	        Node child;
	        int value;
	        int rank;     // This Node's height in the tree

	        public Node(int v) {
	            value = v;
	            rank = 0;
	        }
	    }
	}
	
	

	@Override
	public double netLength(PlacementNetwork network) {
		MSTMetric foo = new MSTMetric(network);
		return foo.compute();
	}

	@Override
	public double netLength(PlacementNetwork network, Map<PlacementNode, ProxyNode> proxyMap, ProxyNode[] originals, ProxyNode[] replacements)
	{
		MSTMetric foo = new MSTMetric( network, proxyMap, originals, replacements );
		return foo.compute();
	}
}
