/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Utils.java
 *
 * 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.erc.wellcheck;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.List;

import com.sun.electric.database.topology.RTNode;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.tool.erc.ERCWellCheck.WellBound;
import com.sun.electric.tool.erc.ERCWellCheck.WellBoundRecord;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.MutableBoolean;

import java.util.Iterator;

/**
 * Utilities for ERC well check
 *
 */
public class Utils {

	public enum WorkDistributionStrategy {
		/**
		 * assign the well contacts to the worker which is most closed by
		 */
		cluster,
		/**
		 * assign the well contacts randomly
		 */
		random,
		/**
		 * Use a grid over the cell to find the right worker
		 */
		bucket;
	}

	public static final boolean GATHERSTATISTICS = false;
	public static final boolean INCREMENTALGROWTH = false;

	// Change this static variable to change the work distribution
	public static final WorkDistributionStrategy WORKDISTRIBUTION = WorkDistributionStrategy.bucket;
	public static List<WellBoundRecord> wellBoundSearchOrder;
	public static int numObjSearches;

	/**
	 * Method to tell whether this function describes an element which can be
	 * used as substrate tap (ERC)
	 *
	 * @return true if the function can be used as a substrate tap.
	 */
	public static boolean canBeSubstrateTap(PrimitiveNode.Function fun) {
		return fun == PrimitiveNode.Function.SUBSTRATE || fun == PrimitiveNode.Function.RESPWELL;
	}

	/**
	 * Method to tell whether this function describes an element which can be
	 * used as well tap (ERC)
	 *
	 * @return true if the function can be used as a well tap.
	 */
	public static boolean canBeWellTap(PrimitiveNode.Function fun) {
		return fun == PrimitiveNode.Function.WELL || fun == PrimitiveNode.Function.RESNWELL;
	}

	/**
	 * Search area for touching well polygons
	 *
	 * @param cX
	 * @param cY
	 * @param wellNum
	 * @param rtree
	 * @param threadIndex
	 */
	public static void spreadWellSeed(double cX, double cY, NetValues wellNum, RTNode<WellBound> rtree,
			int threadIndex) {
		RTNode<WellBound> allFound = null;
		Point2D ctr = new Point2D.Double(cX, cY);
		Rectangle2D searchArea = new Rectangle2D.Double(cX, cY, 0, 0);
		MutableBoolean keepSearching = new MutableBoolean(true);
		Rectangle2D[] sides = new Rectangle2D[4];
		for (int i = 0; i < 4; i++)
			sides[i] = new Rectangle2D.Double(0, 0, 0, 0);
		int numSides = 1;
		sides[0].setRect(searchArea);
		while (keepSearching.booleanValue()) {
			if (INCREMENTALGROWTH) {
				// grow the search area incrementally
				double lX = sides[0].getMinX(), hX = sides[0].getMaxX();
				double lY = sides[0].getMinY(), hY = sides[0].getMaxY();
				for (int i = 1; i < numSides; i++) {
					lX = Math.min(lX, sides[i].getMinX());
					hX = Math.max(hX, sides[i].getMinX());
					lY = Math.min(lY, sides[i].getMinY());
					hY = Math.max(hY, sides[i].getMinY());
				}
				double newLX = lX, newHX = hX;
				double newLY = lY, newHY = hY;
				boolean anySearchesGood = false;
				for (int i = 0; i < numSides; i++) {
					allFound = searchInArea(sides[i], wellNum, rtree, allFound, ctr, keepSearching,
							threadIndex);
					if (keepSearching.booleanValue())
						anySearchesGood = true;
					newLX = Math.min(newLX, sides[i].getMinX());
					newHX = Math.max(newHX, sides[i].getMinX());
					newLY = Math.min(newLY, sides[i].getMinY());
					newHY = Math.max(newHY, sides[i].getMinY());
				}
				keepSearching.setValue(anySearchesGood);

				// compute new bounds
				numSides = 0;
				if (newLX < lX)
					sides[numSides++].setRect(newLX, newLY, lX - newLX, newHY - newLY);
				if (newHX > hX)
					sides[numSides++].setRect(hX, newLY, newHX - hX, newHY - newLY);
				if (newLY < lY)
					sides[numSides++].setRect(newLX, newLY, newHX - newLX, lY - newLY);
				if (newHY > hY)
					sides[numSides++].setRect(newLX, hY, newHX - newLX, newHY - hY);
			} else {
				// just keep growing the search area
				allFound = searchInArea(searchArea, wellNum, rtree, allFound, ctr, keepSearching,
						threadIndex);
			}
		}
	}

	/**
	 * Search in a given area
	 *
	 * @param searchArea
	 * @param wellNum
	 * @param rtree
	 * @param allFound
	 * @param ctr
	 * @param keepSearching
	 * @param threadIndex
	 * @return the RTNode found in the area.
	 */
	public static RTNode<WellBound> searchInArea(Rectangle2D searchArea, NetValues wellNum, RTNode<WellBound> rtree,
			RTNode<WellBound> allFound, Point2D ctr, MutableBoolean keepSearching, int threadIndex) {
		keepSearching.setValue(false);
		for (Iterator<WellBound> sea = new RTNode.Search<WellBound>(searchArea, rtree, true); sea.hasNext();) {
			WellBound wb = sea.next();
			if (GATHERSTATISTICS)
				numObjSearches++;
			// ignore if this object is already properly connected
			if (wb.getNetID() != null && wb.getNetID().getIndex() == wellNum.getIndex())
				continue;

			// see if this object actually touches something in the set
			if (allFound == null) {
				// start from center of contact
				if (!wb.getBounds().contains(ctr))
					continue;
			} else {
				boolean touches = false;
				for (Iterator<WellBound> subSea = new RTNode.Search<WellBound>(wb.getBounds(), allFound, true); subSea
						.hasNext();) {
					WellBound subWB = subSea.next();
					if (DBMath.rectsIntersect(subWB.getBounds(), wb.getBounds())) {
						touches = true;
						break;
					}
				}
				if (!touches)
					continue;
			}

			// this touches what is gathered so far: add to it
			synchronized (wb) {
				if (wb.getNetID() != null)
					wellNum.merge(wb.getNetID());
				else
					wb.setNetID(wellNum);
			}
			if (GATHERSTATISTICS)
				wellBoundSearchOrder.add(new WellBoundRecord(wb, threadIndex));
			// expand the next search area by this added bound
			Rectangle2D.union(searchArea, wb.getBounds(), searchArea);
			if (allFound == null)
				allFound = RTNode.makeTopLevel();
			allFound = RTNode.linkGeom(null, allFound, wb);
			keepSearching.setValue(true);
		}
		return allFound;
	}
}
