/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Example.java
 * Technology Editor, helper class during conversion of libraries to technologies
 * Written by Steven M. Rubin.
 *
 * Copyright (c) 2005, 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.user.tecEdit;

import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.topology.Geometric;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.util.math.DBMath;

import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * This class defines graphical node and arc examples during conversion of libraries to technologies.
 */
public class Example implements Serializable
{
	/** head of list of samples in example */	List<Sample> samples;
	/** sample under analysis */				Sample       studySample;
	/** bounding box of example */				double       lx, hx, ly, hy;

	public Example()
	{
		samples = new ArrayList<Sample>();
	}

	/**
	 * Method to parse the node examples in cell "np" and return a list of
	 * EXAMPLEs (one per example).  "isNode" is true if this is a node
	 * being examined.  Returns NOEXAMPLE on error.
	 */
	public static List<Example> getExamples(Cell np, boolean isNode, TechConversionResult tcr, List<Example> variations)
	{
		Map<NodeInst,Object> nodeExamples = new HashMap<NodeInst,Object>();
		for(Iterator<NodeInst> it = np.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();

			// ignore special nodes with function information
			int funct = Manipulate.getOptionOnNode(ni);
			if (funct != Info.LAYERPATCH && funct != Info.PORTOBJ && funct != Info.HIGHLIGHTOBJ)
			{
				nodeExamples.put(ni, Integer.valueOf(0));
			}
		}

		List<Example> neList = new ArrayList<Example>();
		for(Iterator<NodeInst> it = np.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();
			if (nodeExamples.get(ni) != null) continue;

			// get a new cluster of nodes
			Example ne = new Example();
			neList.add(ne);

			Poly poly = new Poly(ni.getAnchorCenterX(), ni.getAnchorCenterY(),
				ni.getLambdaBaseXSize(), ni.getLambdaBaseYSize());
			poly.transform(ni.rotateOut());
			Rectangle2D soFar = poly.getBounds2D();

			// now find all others that touch this area
			boolean gotBBox = false;
			boolean foundOne = true;
			int hCount = 0;
			while (foundOne)
			{
				foundOne = false;

				// begin to search the area so far
                List<NodeInst> sortedNodes = new ArrayList<NodeInst>();
				for(Iterator<Geometric> oIt = np.searchIterator(soFar); oIt.hasNext(); )
				{
					Geometric geom = oIt.next();
					if (geom == null) break;
					if (geom instanceof NodeInst)
                    	sortedNodes.add((NodeInst)geom);
                }
				for(NodeInst otherNi: sortedNodes)
				{
					Poly oPoly = new Poly(otherNi.getAnchorCenterX(), otherNi.getAnchorCenterY(),
						otherNi.getLambdaBaseXSize(), otherNi.getLambdaBaseYSize());
					oPoly.transform(otherNi.rotateOut());
					Rectangle2D otherRect = oPoly.getBounds2D();
					if (!DBMath.rectsIntersect(otherRect, soFar)) continue;

					// make sure the node is valid
					Object otherAssn = nodeExamples.get(otherNi);
					if (otherAssn != null)
					{
						if (otherAssn instanceof Integer) continue;
						if ((Example)otherAssn == ne) continue;
						String error = "Examples are too close.  Found " + neList.size() + " examples at:";
						for(Example nee : neList)
						{
							error += " [" + TextUtils.formatDistance(nee.lx) + "<=X<=" + TextUtils.formatDistance(nee.hx) +
								" and " + TextUtils.formatDistance(nee.ly) + "<=Y<=" + TextUtils.formatDistance(nee.hy) + "]";
						}
						tcr.markError(otherNi, np, error);
						return null;
					}
					nodeExamples.put(otherNi, ne);
					// add it to the cluster
					Sample ns = new Sample();
					ns.node = otherNi;
					ns.values = null;
					ns.msg = null;
					ns.parent = ne;
					ne.samples.add(ns);
					ns.assoc = null;
					ns.xPos = otherRect.getCenterX();
					ns.yPos = otherRect.getCenterY();
					int funct = Manipulate.getOptionOnNode(otherNi);
					switch (funct)
					{
						case Info.PORTOBJ:
							if (!isNode)
							{
								tcr.markError(otherNi, np, "Ports can only exist in nodes");
								return null;
							}
							ns.layer = Generic.tech().portNode;
							break;
						case Info.CENTEROBJ:
							if (!isNode)
							{
								tcr.markError(otherNi, np, "Grab points can only exist in nodes");
								return null;
							}
							ns.layer = Generic.tech().cellCenterNode;
							break;
						case Info.HIGHLIGHTOBJ:
							hCount++;
							break;
						default:
							ns.layer = Manipulate.getLayerCell(otherNi);
							if (ns.layer == null)
							{
                                Manipulate.getLayerCell(otherNi);
                                tcr.markError(otherNi, np, "Node has no layer information");
								return null;
							}
							break;
					}

					// accumulate state if this is not a "grab point" mark
					if (!Generic.isCellCenter(otherNi))
					{
						if (!gotBBox)
						{
							ne.lx = otherRect.getMinX();   ne.hx = otherRect.getMaxX();
							ne.ly = otherRect.getMinY();   ne.hy = otherRect.getMaxY();
							gotBBox = true;
						} else
						{
							if (otherRect.getMinX() < ne.lx) ne.lx = otherRect.getMinX();
							if (otherRect.getMaxX() > ne.hx) ne.hx = otherRect.getMaxX();
							if (otherRect.getMinY() < ne.ly) ne.ly = otherRect.getMinY();
							if (otherRect.getMaxY() > ne.hy) ne.hy = otherRect.getMaxY();
						}
						soFar.setRect(ne.lx, ne.ly, ne.hx-ne.lx, ne.hy-ne.ly);
					}
					foundOne = true;
				}
			}
			if (hCount == 0)
			{
				tcr.markError(null, np, "No highlight layer found");
				return null;
			}
			if (hCount != 1)
			{
				tcr.markError(null, np, "Too many highlight layers found: " + hCount);
				return null;
			}
		}
		if (neList == null)
		{
			tcr.markError(null, np, "No examples found");
			return neList;
		}

		// put variations in a separate list
		if (variations != null)
		{
			for(int i=0; i<neList.size(); i++)
			{
				Example e = neList.get(i);
				for(Sample s : e.samples)
				{
					if (!s.node.getNameKey().isTempname())
					{
						// named a layer: this is a variation
						variations.add(e);
						neList.remove(i);
						i--;
						break;
					}
				}
			}
			if (neList.size() == 0 && variations.size() > 0)
			{
				tcr.markError(null, np, "All examples have text on them...text should be used only in variations");
				return neList;
			}
		}

		// now search the list for the smallest, most upper-left example (the "main" example)
		double sizeX = 0;
		double sizeY = 0;
		double locX = 0;
		double locY = 0;
		Example bestNe = null;
		for(Example ne : neList)
		{
			double newSize = ne.hx-ne.lx;
			newSize *= ne.hy-ne.ly;
			if (bestNe != null)
			{
				if (newSize > sizeX*sizeY) continue;
				if (newSize == sizeX*sizeY && (ne.lx+ne.hx)/2 >= locX && (ne.ly+ne.hy)/2 <= locY)
					continue;
			}
			sizeX = ne.hx - ne.lx;
			sizeY = ne.hy - ne.ly;
			locX = (ne.lx + ne.hx) / 2;
			locY = (ne.ly + ne.hy) / 2;
			bestNe = ne;
		}

		// place the main example at the top of the list
		if (bestNe != null && bestNe != neList)
		{
			neList.remove(bestNe);
			neList.add(0, bestNe);
		}

		// done
		return neList;
	}
}
