/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: ProxyNode.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, 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.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 com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.GenMath;
import com.sun.electric.util.math.Orientation;

import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * This class is a proxy for the actual placement node.
 * It can be moved around, rotated and cloned without touching the placement node
 */
final class ProxyNode implements Comparable<ProxyNode>
{
	private double x;
	private double y;
	private Orientation orientation = null;
	public double width = 0;					// rotated width of the placement node
	public double height = 0;					// rotated height of the placement node
	public boolean finalized = false;

	private PlacementNode node = null;

	private ArrayList<PlacementNetwork> nets = new ArrayList<PlacementNetwork>();
	private HashMap<PlacementNetwork, PlacementNetwork> netMap = null;

	/**
	 * This class is a proxy for <PlacementPort>.
	 * It calculates its position relative to a <ProxyNode> not to a <PlacementNode>
	 *
	 * @author Basti
	 *
	 */
	private final class ProxyPort extends PlacementPort
	{
		private double rotatedOffX, rotatedOffY;
		private ProxyNode proxyNode;
		private double offX;
		private double offY;


		/**
		 * Constructor to create a PlacementPort.
		 * @param x the X offset of this PlacementPort from the center of its PlacementNode.
		 * @param y the Y offset of this PlacementPort from the center of its PlacementNode.
		 * @param pp the Electric PortProto of this PlacementPort.
		 */
		public ProxyPort(double x, double y, ProxyNode p)
		{
			super(x, y);
			offX = x;
			offY = y;
			this.proxyNode = p;
			setPlacementNode(p.getNode());
		}

		/**
		 * Returns rotated coordinates
		 */
		public double getRotatedOffX() { return rotatedOffX; }
		public double getRotatedOffY() { return rotatedOffY; }

		/**
		 * Internal method to compute the rotated offset of this PlacementPort
		 * assuming that the Orientation of its ProxyNode has changed.
		 */
		public void computeRotatedOffset()
		{
			Orientation orient = proxyNode.getPlacementOrientation();
			if (orient == Orientation.IDENT)
			{
				rotatedOffX = offX;
				rotatedOffY = offY;
				return;
			}
			FixpTransform trans = orient.pureRotate();
			Point2D offset = new Point2D.Double(offX, offY);
			trans.transform(offset, offset);
			rotatedOffX = offset.getX();
			rotatedOffY = offset.getY();
		}
	}


	/**
	 * Constructor to create a ProxyNode
	 * @param node the PlacementNode that should be proxied
	 * @param ignoredNets a list of nets that should be ignored
	 */
	public ProxyNode(PlacementNode node, ArrayList<PlacementNetwork> ignoredNets)
	{
		this.node = node;
		this.x = node.getPlacementX();
		this.y = node.getPlacementY();
		this.width = node.getWidth();
		this.height = node.getHeight();

		// create a list of all nets that node belongs to
		for(PlacementPort p : node.getPorts())
			if(!nets.contains(p.getPlacementNetwork()) && p.getPlacementNetwork() != null)
				if(!ignoredNets.contains(p.getPlacementNetwork()))
					nets.add(p.getPlacementNetwork());

		orientation = node.getPlacementOrientation();
	}

	/**
	 * Constructor used by cloning that doesn't create a new net list
	 * @param node
	 * @param x
	 * @param y
	 * @param width
	 * @param height
	 * @param o orientation of the cloned node
	 * @param nets the netlist of the cloned node
	 */
	private ProxyNode(PlacementNode node, double x, double y, double width, double height, Orientation o, ArrayList<PlacementNetwork> nets)
	{
		this.node = node;
		this.x = x;
		this.y = y;
		this.nets = nets;
		this.width = width;
		this.height = height;
		this.orientation = o;
	}

	/**
	 * Method that applies the changes made to the proxy to the placement node
	 */
	public void apply()
	{
		node.setPlacement(x, y);
		node.setOrientation(orientation);
	}

	/**
	 * Method that sets the node to a new position
	 * @param x
	 * @param y
	 */
	public void setPlacement(double x, double y) {
		this.x = x;
		this.y = y;
	}

	/**
	 * Method that returns the proxied PlacementNode
	 * @return the PlacementNode
	 */
	public PlacementNode getNode() {
		return this.node;
	}

	/**
	 * Method that clones this proxy
	 */
	public ProxyNode clone() {
		return new ProxyNode(this.node, x, y, width, height, orientation, nets);
	}

	/**
	 * Method to get a list of nets this node belongs to.
	 * This is more convenient than iterating over the ports
	 * Also it only contains nets that are not ignored (e.g. because
	 * they are too huge)
	 * @return the list of nets this node belongs to
	 */
	public ArrayList<PlacementNetwork> getNets() {
		return nets;
	}

	/**
	 * Method to get the X-dimension of the location of this ProxyNode
	 * @return
	 */
	public double getPlacementX() {
		return this.x;
	}

	/**
	 * Method to get the Y-dimension of the location of this ProxyNode
	 * @return
	 */
	public double getPlacementY() {
		return this.y;
	}

	/**
	 * Method to get the orientation of this ProxyNode
	 * @return
	 */
	public Orientation getPlacementOrientation() {
		return orientation;
	}

	/**
	 * Method that returns the net, that was "cloned" when rotating this port.
	 * When evaluating a rotation perturbation, the cloned proxy creates flat
	 * copys of all nets it belongs to an replaces its ports in all the nets
	 * with proxy ports that calculate their offset relative to the now rotated
	 * proxy node (instead of the not rotated placement node)
	 *
	 * It is intended to be called only on a clone of a ProxyNode when getting
	 * the correct hashValue to save the calculated net metric.
	 * @param alteredNet
	 * @return
	 */
	public PlacementNetwork getOriginalNet(PlacementNetwork alteredNet)
	{
		if(netMap == null) return alteredNet;
		return netMap.get(alteredNet);
	}

	/**
	 * Method that sets the orientation of the proxy node
	 * @param o the new orientation
	 * @param forDummy sets whether the proxy node replaces ports in its nets
	 */
	public void setPlacementOrientation(Orientation o, boolean forDummy) {

		Orientation oldOrientation = orientation;
		orientation = o;

		if(forDummy)
		{
			netMap = new HashMap<PlacementNetwork, PlacementNetwork>();
			ArrayList<PlacementNetwork> newNets = new ArrayList<PlacementNetwork>(nets.size());

			/*
			 * Replace the list of nets with a new list. Replace every
			 * port on this node with a proxy port an calculate their offsets
			 * relative to this node
			 */
			for(PlacementNetwork net : nets)
			{
				ArrayList<PlacementPort> newPortsOnNet = new ArrayList<PlacementPort>();

				// Replace ports that belong to this node with proxies
				for(PlacementPort port : net.getPortsOnNet())
				{
					if(port.getPlacementNode() == this.node)
					{
						// get the unrotated offsets (they are private in the framework...)
						// by applying the inverse rotation to the rotated values
						FixpTransform trans = oldOrientation.inverse().pureRotate();
						Point2D offset = new Point2D.Double(port.getRotatedOffX(), port.getRotatedOffY());
						trans.transform(offset, offset);
						ProxyPort pp = new ProxyPort(offset.getX(),offset.getY(), this);

						// calculate the port rotated offsets relative to this proxy node !
						pp.computeRotatedOffset();
						newPortsOnNet.add(pp);
					}
					else
						newPortsOnNet.add(port);
				}

				PlacementNetwork newNet = new PlacementNetwork(newPortsOnNet, net.isOnRail());
				netMap.put(net, newNet);
				newNets.add(newNet);
			}

			this.nets = newNets;
		}
		else
		{
			// this actually makes the rotation visible to other threads!
			node.setOrientation(o);
		}

		// swap height and width for 90°, 270°, ...
		if (o == Orientation.R || o == Orientation.RRR || o == Orientation.XR || o == Orientation.XRRR || o == Orientation.XYR || o == Orientation.XYRRR || o == Orientation.YR || o == Orientation.YRRR)
		{
			width = node.getHeight();
			height = node.getWidth();
		}
		else
		{
			width = node.getWidth();
			height = node.getHeight();
		}
	}

	public int compareTo(ProxyNode o) {
		double dist1 = x * x + y * y;
		double dist2 = o.x * o.x + o.y * o.y;

		if(dist1 > dist2)
			return 1;
		else
			return -1;
	}
}
