/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: LayerCoverageTool.java
 *
 * Copyright (c) 2005, 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.extract;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.GeometryHandler;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.geometry.PolyBase;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.HierarchyEnumerator;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.id.LayerId;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.PrefPackage;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.EditWindow_;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.DRCTemplate;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.TechPool;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.TransistorSize;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.Tool;
import com.sun.electric.tool.drc.DRC;
import com.sun.electric.tool.routing.SeaOfGates;
import com.sun.electric.tool.user.ErrorLogger;
import com.sun.electric.tool.user.User;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.FixpTransform;

import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.prefs.Preferences;

/**
 * Class to describe coverage percentage for a layer.
 */
public class LayerCoverageTool extends Tool
{
    /** the LayerCoverageTool tool. */		protected static LayerCoverageTool tool = new LayerCoverageTool();

    /**
	 * The constructor sets up the DRC tool.
	 */
	private LayerCoverageTool()
	{
		super("coverage");
	}

    /**
     * Method to retrieve the singleton associated with the LayerCoverageTool tool.
     * @return the LayerCoverageTool tool.
     */
    public static LayerCoverageTool getLayerCoverageTool() { return tool; }

    /****************************** OPTIONS ******************************/

    public static class LayerCoveragePreferences extends PrefPackage {
        public static final double DEFAULT_AREA_COVERAGE = 10; // 10%
        // Default value is in um to be technology independent
        private static final double defaultSize = 50000;
        private static final String EXTRACT_NODE = "tool/extract";
        private static final String KEY_COVERAGE = "AreaCoverageJobOf";
        private final transient TechPool techPool;

        public Map<LayerId,Double> areaCoverage = new HashMap<LayerId,Double>();

        /** User preference for deltaX. The default is 50 mm. */
        @DoublePref(node = EXTRACT_NODE, key = "DeltaX", factory = defaultSize)
        public double deltaXInMicrons;

        /** User preference for deltaY. The default is 50 mm. */
        @DoublePref(node = EXTRACT_NODE, key = "DeltaY", factory = defaultSize)
        public double deltaYInMicrons;

        /** User preference for width of the bounding box. The default is 50 mm. */
        @DoublePref(node = EXTRACT_NODE, key = "Width", factory = defaultSize)
        public double widthInMicrons;

        /** User preference for height of the bounding box. The default is 50 mm. */
        @DoublePref(node = EXTRACT_NODE, key = "Height", factory = defaultSize)
        public double heightInMicrons;

        /** Preference for SeaOfGate if used */
        public SeaOfGates.SeaOfGatesOptions seaIfGatesPrefs;

        public LayerCoveragePreferences(boolean factory) {
            this(factory, TechPool.getThreadTechPool());
            seaIfGatesPrefs = new SeaOfGates.SeaOfGatesOptions();
            if (!factory) seaIfGatesPrefs.getOptionsFromPreferences(false);
        }

        private LayerCoveragePreferences(boolean factory, TechPool techPool)
        {
            super(factory);
            this.techPool = techPool;
            if (factory) return;

            Preferences techPrefs = getPrefRoot().node(TECH_NODE);
            for (Technology tech: techPool.values()) {
                for (Iterator<Layer> it = tech.getLayers(); it.hasNext(); ) {
                    Layer layer = it.next();
                    LayerId layerId = layer.getId();
                    double factoryValue = DEFAULT_AREA_COVERAGE;
                    double value = techPrefs.getDouble(getKey(KEY_COVERAGE, layerId), factoryValue);
                    if (value == factoryValue) continue;
                    areaCoverage.put(layerId, Double.valueOf(value));
                }
            }
        }

        /**
         * Store annotated option fields of the subclass into the speciefied Preferences subtree.
         * @param prefRoot the root of the Preferences subtree.
         * @param removeDefaults remove from the Preferences subtree options which have factory default value.
         */
        @Override
        public void putPrefs(Preferences prefRoot, boolean removeDefaults) {
            super.putPrefs(prefRoot, removeDefaults);
            Preferences techPrefs = prefRoot.node(TECH_NODE);
            for (Technology tech: techPool.values()) {
                for (Iterator<Layer> it = tech.getLayers(); it.hasNext(); ) {
                    Layer layer = it.next();
                    LayerId layerId = layer.getId();
                    String key = getKey(KEY_COVERAGE, layerId);
                    double factoryValue = DEFAULT_AREA_COVERAGE;
                    Double valueObj = areaCoverage.get(layerId);
                    double value = valueObj != null ? valueObj.doubleValue() : factoryValue;
                    if (removeDefaults && value == factoryValue)
                        techPrefs.remove(key);
                    else
                        techPrefs.putDouble(key, value);
                }
            }
        }

        /**
         * Method to return the minimum area coverage that the layer must reach in the technology.
         * @return the minimum area coverage (in percentage).
         */
        public double getAreaCoverage(Layer layer) {
            Double valueObj = areaCoverage.get(layer.getId());
            return valueObj != null ? valueObj.doubleValue() : DEFAULT_AREA_COVERAGE;
        }
        /**
         * Methot to set minimum area coverage that the layer must reach in the technology.
         * @param layer Layer
         * @param area the minimum area coverage (in percentage).
         */
        public void setAreaCoverageInfo(Layer layer, double area) {
            if (area == DEFAULT_AREA_COVERAGE)
                areaCoverage.remove(layer.getId());
            else
                areaCoverage.put(layer.getId(), Double.valueOf(area));
        }

        public void reset() {
            areaCoverage.clear();
            deltaXInMicrons = deltaYInMicrons = widthInMicrons = heightInMicrons = defaultSize;
        }
    }

    /**
     * Method to handle the "List Layer Coverage", "Coverage Implant Generator",  polygons merge
     * except "List Geometry on Network" commands.
     */
    public static List<Object> layerCoverageCommand(LCMode func, GeometryHandler.GHMode mode, Cell curCell, boolean startJob, LayerCoveragePreferences lcp)
    {
        // Must be change job for merge and implant;
        Job.Type jobType = (func == LayerCoverageTool.LCMode.MERGE || func == LayerCoverageTool.LCMode.IMPLANT) ?
                Job.Type.CHANGE : Job.Type.SERVER_EXAMINE;
        LayerCoverageJob job = new LayerCoverageJob(curCell, jobType, func, mode, null, null, lcp);
        if (startJob)
            job.startJob();
        else
        {
            try
            {
                job.doIt();
            }
            catch (Exception e) {}
            return job.nodesAdded;
        }
        return null;
    }

    /**
     * Method to kick area coverage per layer in a cell. It has to be public due to regressions.
     * @param cell
     * @param mode
     * @param startJob to determine if job has to run in a separate thread
     * @return true if job runs without errors. Only valid if startJob is false (regression purpose)
     */
    public static Map<Layer,Double> layerCoverageCommand(Cell cell, GeometryHandler.GHMode mode, boolean startJob, LayerCoveragePreferences lcp)
    {
        if (cell == null) return null;

        double techScale = cell.getTechnology().getScale();
        double width = lcp.widthInMicrons/techScale;
        double height = lcp.heightInMicrons/techScale;
        double deltaX = lcp.deltaXInMicrons/techScale;
        double deltaY = lcp.deltaYInMicrons/techScale;

        // Reset values to cell bounding box if area is bigger than the actual cell
        Rectangle2D bbox = cell.getBounds();
        if (width > bbox.getWidth()) width = bbox.getWidth();
        if (height > bbox.getHeight()) height = bbox.getHeight();
        Map<Layer,Double> map = null;
        AreaCoverageJob job = new AreaCoverageJob(cell, mode, width, height, deltaX, deltaY, lcp);

        // No regression
        if (startJob)
            job.startJob();
        else
        {
        	try
        	{
        		job.doIt();
                map = job.getDataInfo();
        	} catch (JobException e)
        	{
        	}
        }
        return (map);
    }

    /**
     * Method to extract bounding box for a particular Network/Layer
     * @param exportCell
     * @return Rectangle2D containing the bounding box for a particular Network/Layer
     */
    public static Rectangle2D getGeometryOnNetwork(Cell exportCell, PortInst pi, Layer layer, LayerCoverageTool.LayerCoveragePreferences lcp)
    {
        Netlist netlist = exportCell.getNetlist();
        Network net = netlist.getNetwork(pi);
        Set<Network> nets = new HashSet<Network>();
        nets.add(net);
        GeometryOnNetwork geoms = new GeometryOnNetwork(exportCell, nets, 1.0, false, layer, lcp);
        // This assumes that pi.getBounds() alywas gives you a degenerated rectangle (zero area) so
        // only a point should be searched.
        Rectangle2D bnd = pi.getBounds();
		LayerCoverageJob job = new LayerCoverageJob(exportCell, Job.Type.SERVER_EXAMINE, LCMode.NETWORK,
                GeometryHandler.GHMode.ALGO_SWEEP, geoms,
                new Point2D.Double(bnd.getX(), bnd.getY()), lcp);

        // Must run it now
        try
        {
            job.doIt();  // Former listGeometryOnNetworksNoJob
        } catch (JobException e)
        {
            e.printStackTrace();
        }
        List<Object> list = job.nodesAdded;
        // Don't know what to do if there is more than one
        Rectangle2D rect = null;
        if (list.size() != 1)
            System.out.println("Problem here");
//        assert(list.size() == 1);
        else
        {
            PolyBase poly = (PolyBase)list.toArray()[0];
            rect = poly.getBounds2D();
        }
        return rect;
    }

    /**
     * Method to calculate area, half-perimeter and ratio of each layer by merging geometries
     * @param cell cell to analyze
     * @param nets networks to analyze
     * @param startJob if job has to run on thread
     * @param mode geometric algorithm to use: GeometryHandler.ALGO_QTREE, GeometryHandler.SWEEP or GeometryHandler.ALGO_MERGE
     * @param lcp LayerCoveragePreferences
     */
    public static GeometryOnNetwork listGeometryOnNetworks(Cell cell, Set<Network> nets, boolean startJob,
                                                           GeometryHandler.GHMode mode, LayerCoveragePreferences lcp)
    {
	    if (cell == null || nets == null || nets.isEmpty()) return null;
	    double lambda = 1; // lambdaofcell(np);
        // startJob is identical to printable
	    GeometryOnNetwork geoms = new GeometryOnNetwork(cell, nets, lambda, startJob, null, lcp);
		Job job = new LayerCoverageJob(cell, Job.Type.SERVER_EXAMINE, LCMode.NETWORK, mode, geoms, null, lcp);

        if (startJob)
            job.startJob();
        else
        {
        	try
        	{
        		job.doIt();  // Former listGeometryOnNetworksNoJob
        	} catch (JobException e)
        	{
        	}
        }
	    return geoms;
	}

    /************************************************************************
     * LayerCoverageData Class
     ************************************************************************/
    private static class LayerCoverageData
    {
        private EditingPreferences ep;
        private Cell curCell;
        private Job parentJob; // to stop if parent job is killed
        private LCMode function;
        private GeometryHandler.GHMode mode;
        private GeometryOnNetwork geoms;  // Valid only for network job
        private Rectangle2D bBox; // To crop geometry under analysis by given bounding box
        private List<Object> nodesToExamine = new ArrayList<Object>(); // for implant
        private Point2D overlapPoint; // to detect if obtained geometry is connected to original request,
        // Network in fill generator

        LayerCoverageData(Job parentJob, Cell cell, LCMode func, GeometryHandler.GHMode mode,
                          GeometryOnNetwork geoms, Rectangle2D bBox, Point2D overlapPoint, EditingPreferences ep, LayerCoverageTool.LayerCoveragePreferences lcp)
        {
            this.ep = ep;
            this.parentJob = parentJob;
            this.curCell = cell;
            this.mode = mode;
            this.function = func;
            this.geoms = geoms; // Valid only for network
            this.bBox = bBox;
            this.overlapPoint = overlapPoint;

            if (func == LCMode.AREA && this.geoms == null)
                this.geoms = new GeometryOnNetwork(curCell, null, 1, true, null, lcp);
        }

        List<Object> getNodesToHighlight() { return nodesToExamine; }

        boolean doIt()
        {
            GeometryHandler tree = GeometryHandler.createGeometryHandler(mode, curCell.getTechnology().getNumLayers());
            Map<Layer,Set<PolyBase>> originalPolygons = new HashMap<Layer,Set<PolyBase>>(); // Storing initial nodes
            Set<NodeInst> nodesToDelete = new HashSet<NodeInst>(); // should only be used by IMPLANT
            Set<Network> netSet = null;
            Layer onlyThisLayer = null;

            Netlist.ShortResistors shortResistors = Netlist.ShortResistors.NO;
            if (geoms != null)
            {
                netSet = geoms.nets;
                if (geoms.nets != null && !geoms.nets.isEmpty()) {
                    Iterator<Network> nIt = geoms.nets.iterator();
                    shortResistors = nIt.next().getNetlist().getShortResistors();
                    while (nIt.hasNext()) {
                        Netlist.ShortResistors sh = nIt.next().getNetlist().getShortResistors();
                        if (sh != shortResistors)
                            throw new IllegalArgumentException("shortResistors");
                    }
                }
                onlyThisLayer = geoms.onlyThisLayer;
            }
            // enumerate the hierarchy below here
            LayerVisitor visitor = new LayerVisitor(parentJob, tree, nodesToDelete, function,
                    originalPolygons, netSet, bBox, onlyThisLayer, geoms);
            HierarchyEnumerator.enumerateCell(curCell, VarContext.globalContext, visitor, shortResistors);
            tree.postProcess(true);

            switch (function)
            {
                case MERGE:
                case IMPLANT:
                    {
                        // With polygons collected, new geometries are calculated
                        boolean noNewNodes = true;
                        boolean isMerge = (function == LCMode.MERGE);
                        Rectangle2D rect;
                        Point2D [] points;

                        // Need to detect if geometry was really modified
                        for (Layer layer : tree.getKeySet())
                        {
                            Collection<PolyBase> set = tree.getObjects(layer, !isMerge, true);
                            Object[] polyArray = null;
                            if (function == LCMode.IMPLANT)
                            {
                            	Set<PolyBase> polySet = originalPolygons.get(layer);
                            	polyArray = polySet.toArray();
                            }
                            List<Rectangle2D> newImplants = new ArrayList<Rectangle2D>();

                            // Ready to create new implants.
                            for (PolyBase polyB : set)
                            {
                                points = polyB.getPoints();
                                rect = polyB.getBounds2D();

                                if (isMerge)
                                {
                                    // ignore if one of the original elements (implant merge only)
                                    if (polyArray != null)
                                    {
                                        boolean foundOrigPoly = false;
                                        for (Object poly : polyArray)
                                        {
                                            foundOrigPoly = polyB.polySame((PolyBase)poly);
                                            if (foundOrigPoly) break;
                                        }
                                        if (foundOrigPoly) continue;
                                    }

                                    // Adding the new implant. New implant not assigned to any local variable
                                    Point2D center = new Point2D.Double(rect.getCenterX(), rect.getCenterY());
                                    PrimitiveNode priNode = layer.getPureLayerNode();
                                    NodeInst node = NodeInst.makeInstance(priNode, ep, center, rect.getWidth(), rect.getHeight(), curCell);
                                    nodesToExamine.add(node);

                                    EPoint [] ePoints = new EPoint[points.length];
                                    for(int j=0; j<points.length; j++)
                                        ePoints[j] = EPoint.fromLambda(points[j].getX(), points[j].getY());
                                    node.setTrace(ePoints);
                                } else
                                {
                                	newImplants.add(rect);
                                }
                                noNewNodes = false;
                            }
                            if (function == LCMode.IMPLANT)
                            {
                            	// merge when close to avoid notch errors
            					DRCTemplate rule = DRC.getSpacingRule(layer, null, layer, null, false, -1, 10, 100);
            					if (rule != null)
            					{
            						double dist = rule.getValue(0);
            						for(int i=0; i<newImplants.size(); i++)
            						{
            							Rectangle2D r = newImplants.get(i);
            							boolean merged = false;
            							for(int j=i+1; j<newImplants.size(); j++)
            							{
            								Rectangle2D oR = newImplants.get(j);
            								if (r.getMinX()-dist > oR.getMaxX() ||
            									oR.getMinX()-dist > r.getMaxX() ||
            									r.getMinY()-dist > oR.getMaxY() ||
            									oR.getMinY()-dist > r.getMaxY()) continue;

            								// they are too close: merge them
            								double lx = Math.min(r.getMinX(), oR.getMinX());
            								double hx = Math.max(r.getMaxX(), oR.getMaxX());
            								double ly = Math.min(r.getMinY(), oR.getMinY());
            								double hy = Math.max(r.getMaxY(), oR.getMaxY());
            								r.setRect(lx, ly, hx-lx, hy-ly);
            								newImplants.remove(j);
            								merged = true;
            								break;
            							}
            							if (merged) i--;
            						}
            					}
                                PrimitiveNode priNode = layer.getPureLayerNode();
                            	for(Rectangle2D r : newImplants)
                            	{
                                    // ignore if one of the original elements
                                    if (polyArray != null)
                                    {
                                        boolean covered = false;
                                        for (Object poly : polyArray)
                                        {
                                        	PolyBase pb = (PolyBase)poly;
                                        	Rectangle2D pbRect = pb.getBox();
                                        	if (pbRect == null) continue;
                                        	covered = pbRect.contains(r);
                                            if (covered) break;
                                        }
                                        if (covered) continue;
                                    }

                                    Point2D center = new Point2D.Double(r.getCenterX(), r.getCenterY());
                                    NodeInst node = NodeInst.makeInstance(priNode, ep, center, r.getWidth(), r.getHeight(), curCell);
                                    nodesToExamine.add(node);
                                    node.setHardSelect();
                            	}
                            }
                        }
                        curCell.killNodes(nodesToDelete);
                        if (noNewNodes)
                            System.out.println("No new areas added");
                    }
                    break;
                case AREA:
                case NETWORK:
                    {
                        double lambdaSqr = 1; // lambdaofcell(np);
                        Rectangle2D bbox = curCell.getBounds();
                        double totalArea =  (bbox.getHeight()*bbox.getWidth())/lambdaSqr;
                        // Traversing tree with merged geometry and sorting layers per name first
                        List<Layer> list = new ArrayList<Layer>(tree.getKeySet());
                        Layer.getLayersSortedByRule(list, Layer.LayerSortingType.ByName);

                        for (Layer layer : list)
                        {
                            Collection<PolyBase> set = tree.getObjects(layer, false, true);

                            if (geoms != null && geoms.onlyThisLayer != null)
                            {
                                if (layer != geoms.onlyThisLayer) continue; // ignore this layer
                                // Add all elements
                                if (overlapPoint == null)
                                    nodesToExamine.addAll(set);
                                else
                                {
                                    // they must be connected
                                    for (PolyBase p : set)
                                    {
                                        if (p.contains(overlapPoint))
                                            nodesToExamine.add(p);
                                    }
                                }
                            }

                            double layerArea = 0;
                            double perimeter = 0;

                            // Get all objects and sum the area
                            for (PolyBase poly : set)
                            {
                                layerArea += poly.getArea();
                                perimeter += poly.getPerimeter();
                            }
                            layerArea /= lambdaSqr;
                            perimeter /= 2;

                            if (geoms != null)
                                geoms.addLayer(layer, layerArea, perimeter);
                            else
                                System.out.println("Layer " + layer.getName() + " covers " + TextUtils.formatDouble(layerArea)
                                        + " square lambda (" + TextUtils.formatDouble((layerArea/totalArea)*100) + "%)");
                        }
                        geoms.setTotalArea(totalArea);
                        if (geoms != null)
                            geoms.print();
                        else
                            System.out.println("Cell is " + TextUtils.formatDouble(totalArea) + " square lambda");
                    }
                    break;
                default:
                    System.out.println("Error in LayerCoverage: function not implemented");
            }
            return true;
        }
    }

    /************************************************************************
     * LayerCoverageJob Class
     ************************************************************************/
    private static class LayerCoverageJob extends Job
    {
        private Cell cell;
        private LCMode func;
        private GeometryHandler.GHMode mode;
        private GeometryOnNetwork geoms;
        private List<Object> nodesAdded;
        private Point2D overlapPoint; // to get to crop the search if a given bbox is not null
        private LayerCoverageTool.LayerCoveragePreferences lcp;

        public LayerCoverageJob(Cell cell, Job.Type jobType, LCMode func, GeometryHandler.GHMode mode,
                                GeometryOnNetwork geoms, Point2D overlapPoint, LayerCoverageTool.LayerCoveragePreferences lcp)
        {
            super("Layer Coverage on " + cell, User.getUserTool(), jobType, null, null, Priority.USER);
            this.cell = cell;
            this.func = func;
            this.mode = mode;
            this.geoms = geoms;
            this.overlapPoint = overlapPoint;
            this.lcp = lcp;
            setReportExecutionFlag(true);
        }

        @Override
        public boolean doIt() throws JobException
        {
            EditingPreferences ep = getEditingPreferences();
            LayerCoverageData data = new LayerCoverageData(this, cell, func, mode, geoms, null, overlapPoint, ep, lcp);
            boolean done = data.doIt();

            if (func == LCMode.IMPLANT || (func == LCMode.NETWORK && geoms != null))
            {
                nodesAdded = new ArrayList<Object>();
                nodesAdded.addAll(data.getNodesToHighlight());
                if (func == LCMode.IMPLANT)
                	fieldVariableChanged("nodesAdded");
            }
            return done;
        }

        @Override
        public void terminateOK()
        {
            // For implant highlight the new nodes
            if (func == LCMode.IMPLANT)
            {
                EditWindow_ wnd = Job.getUserInterface().getCurrentEditWindow_();
                if (wnd == null) return; // no GUI present
                if (nodesAdded == null) return; // nothing to show
                for (Object node : nodesAdded)
                {
                    wnd.addElectricObject((NodeInst)node, cell);
                }
            }
        }
    }

    /************************************************************************
     * AreaCoverageJob Class
     ************************************************************************/
    private static class AreaCoverageJob extends Job
    {
        private Cell curCell;
        private double deltaX, deltaY;
        private double width, height;
        private GeometryHandler.GHMode mode;
        private Map<Layer,Double> internalMap;
        private LayerCoverageTool.LayerCoveragePreferences lcp;

        public AreaCoverageJob(Cell cell, GeometryHandler.GHMode mode,
                               double width, double height, double deltaX, double deltaY, LayerCoveragePreferences lcp)
        {
            super("Layer Coverage", User.getUserTool(), Type.SERVER_EXAMINE, null, null, Priority.USER);
            this.curCell = cell;
            this.mode = mode;
            this.width = width;
            this.height = height;
            this.deltaX = deltaX;
            this.deltaY = deltaY;
            this.lcp = lcp;
            setReportExecutionFlag(true); // Want to report statistics
        }

        @Override
        public boolean doIt() throws JobException
        {
            EditingPreferences ep = getEditingPreferences();
            ErrorLogger errorLogger = ErrorLogger.newInstance("Area Coverage");
            Rectangle2D bBoxOrig = curCell.getBounds();
            double maxY = bBoxOrig.getMaxY();
            double maxX = bBoxOrig.getMaxX();

            // if negative or zero values -> only once
            if (deltaX <= 0) deltaX = bBoxOrig.getWidth();
            if (deltaY <= 0) deltaY = bBoxOrig.getHeight();
            if (width <= 0) width = bBoxOrig.getWidth();
            if (height <= 0) height = bBoxOrig.getHeight();

            internalMap = new HashMap<Layer,Double>();
//            fieldVariableChanged("internalMap");

            for (double posY = bBoxOrig.getMinY(); posY < maxY; posY += deltaY)
            {
                for (double posX = bBoxOrig.getMinX(); posX < maxX; posX += deltaX)
                {
                    Rectangle2D box = new Rectangle2D.Double(posX, posY, width, height);
                    GeometryOnNetwork geoms = new GeometryOnNetwork(curCell, null, 1, true, null, lcp);
                    System.out.println("Calculating Coverage on cell '" + curCell.getName() + "' for area (" +
                            DBMath.round(posX) + "," + DBMath.round(posY) + ") (" +
                            DBMath.round(box.getMaxX()) + "," + DBMath.round(box.getMaxY()) + ")");
                    LayerCoverageData data = new LayerCoverageData(this, curCell, LCMode.AREA, mode, geoms, box, null, ep, lcp);
                    if (!data.doIt())  // aborted by user
                    {
                        return false; // didn't finish
                    }
                    geoms.analyzeCoverage(box, errorLogger);

                    for (int i = 0; i < geoms.layers.size(); i++)
                    {
                        Layer layer = geoms.layers.get(i);
                        Double area = geoms.areas.get(i);

                        Double oldV = internalMap.get(layer);
                        double newV = area.doubleValue();
                        if (oldV != null)
                            newV += oldV.doubleValue();
                        internalMap.put(layer, new Double(newV));
                    }
                }
            }
            errorLogger.termLogging(true);
            return true;
        }

        public Map<Layer,Double> getDataInfo() { return internalMap; }
    }

    public enum LCMode // LC = LayerCoverageTool mode
    {
	    AREA,   // function Layer Coverage
	    MERGE,  // Generic merge polygons function
	    IMPLANT, // Coverage implants
	    NETWORK // List Geometry on Network function
    }


    /************************************************************************
     * LayerVisitor Class
     ************************************************************************/
    public static class LayerVisitor extends HierarchyEnumerator.Visitor
	{
        private Job parentJob;
		private GeometryHandler tree;
		private Set<NodeInst> deleteList; // Only used for coverage Implants. New coverage implants are pure primitive nodes
		private final LCMode function;
		private Map<Layer,Set<PolyBase>> originalPolygons;
		private Set<Network> netSet; // For network type, rest is null
        private Rectangle2D origBBox;
        private Area origBBoxArea;   // Area is always in coordinates of top cell
        private Layer onlyThisLayer;
        private GeometryOnNetwork geoms;

		public LayerVisitor(Job job, GeometryHandler t, Set<NodeInst> delList, LCMode func,
			Map<Layer, Set<PolyBase>> original, Set<Network> netSet, Rectangle2D bBox, Layer onlyThisLayer, GeometryOnNetwork geoms)
		{
            this.parentJob = job;
			this.tree = t;
			this.deleteList = delList;
			this.function = func;
			this.originalPolygons = original;
			this.netSet = netSet;
            this.origBBox = bBox;
            origBBoxArea = (bBox != null) ? new Area(origBBox) : null;
            this.onlyThisLayer = onlyThisLayer;
            this.geoms = geoms;
		}

		/**
		 * Determines if function of given layer is applicable for the corresponding operation
		 */
		private boolean isValidFunction(Layer layer, LCMode function)
		{
            if (onlyThisLayer != null && layer != onlyThisLayer)
                return false;

            Layer.Function func = layer.getFunction();

			switch (function)
			{
                case MERGE:
				case NETWORK:
					return (true);
				case IMPLANT:
					return (func.isSubstrate());
				case AREA:
					return (func.isPoly() || func.isMetal());
				default:
					return (false);
			}
		}

        /**
         * In case of non null bounding box, it will undo the
         * transformation
         * @param info
         */
		public void exitCell(HierarchyEnumerator.CellInfo info)
        {
        }

        private boolean doesIntersectBoundingBox(Rectangle2D rect, HierarchyEnumerator.CellInfo info)
        {
            // Default case when no bounding box is used to crop the geometry
            if (origBBox == null) return true;

            // only because I need to transform the points.
            PolyBase polyRect = new PolyBase(rect);
            // To avoid transformation while traversing the hierarchy
            polyRect.transform(info.getTransformToRoot());
            rect = polyRect.getBounds2D();
            return rect.intersects(origBBox);
        }

        private boolean isJobAborted()
        {
            return (parentJob != null && parentJob.checkAbort());
        }

        public boolean enterCell(HierarchyEnumerator.CellInfo info)
		{
            // Checking if job is scheduled for abort or already aborted
	        if (isJobAborted())
                return (false);

			Cell curCell = info.getCell();
			Netlist netlist = info.getNetlist();

            // Nothing to visit  CAREFUL WITH TRANSFORMATION IN SUBCELL!!
            if (!doesIntersectBoundingBox(curCell.getBounds(), info))
                return false;

			// Checking if any network is found
            boolean found = (netSet == null);
            for (Iterator<Network> it = netlist.getNetworks(); !found && it.hasNext(); )
            {
                // In case there are many networks
                if (isJobAborted())
                    return (false);
                Network parentNet = it.next();
                HierarchyEnumerator.CellInfo cinfo = info;
                boolean netFound = false;
                while ((netFound = netSet.contains(parentNet)) == false && cinfo.getParentInst() != null) {
                    parentNet = cinfo.getNetworkInParent(parentNet);
                    cinfo = cinfo.getParentInfo();
                }
                found = netFound;
            }
            if (!found) return (false);

			// Traversing arcs
			for (Iterator<ArcInst> it = curCell.getArcs(); it.hasNext(); )
			{
				ArcInst arc = it.next();
				int width = netlist.getBusWidth(arc);
                found = (netSet == null);

                for (int i=0; !found && i<width; i++)
                {
                    Network parentNet = netlist.getNetwork(arc, i);
                    HierarchyEnumerator.CellInfo cinfo = info;
                    boolean netFound = false;
                    while ((netFound = netSet.contains(parentNet)) == false && cinfo.getParentInst() != null) {
                        parentNet = cinfo.getNetworkInParent(parentNet);
                        cinfo = cinfo.getParentInfo();
                    }
                    found = netFound;
                }
                if (!found) continue; // skipping this arc
				ArcProto arcType = arc.getProto();
				Technology tech = arcType.getTechnology();
				Poly[] polyList = tech.getShapeOfArc(arc);

				// Treating the arcs associated to each node
				// Arcs don't need to be rotated
				for (Poly poly : polyList)
				{
					Layer layer = poly.getLayer();

					boolean value = isValidFunction(layer, function);
					if (!value) continue;

					poly.transform(info.getTransformToRoot());

                    // If points are not rounded, in IMPLANT map.containsValue() might not work
                    poly.roundPoints();

                    storeOriginalPolygons(layer, poly);

                    Shape pnode = cropGeometry(poly, origBBoxArea);
                    // empty intersection
                    if (pnode == null) continue;

					tree.add(layer, pnode);  // tmp fix
				}
			}
			return (true);
		}

        /**
         * To store original polygons checked by coverage implant run and then
         * able to determine if new implants should be created.
         * @param layer
         * @param poly
         */
        private void storeOriginalPolygons(Layer layer, PolyBase poly)
        {
            if (function != LCMode.IMPLANT) return;
            // For coverage implants
            Set<PolyBase> polySet = originalPolygons.get(layer);
            if (polySet == null)
            {
                polySet = new HashSet<PolyBase>();
                originalPolygons.put(layer, polySet);
            }
            //map.put(pnode, pnode.clone());
            polySet.add(poly);
        }

        /**
         *
         * @param no
         * @param info
         * @return true if node was visited
         */
		public boolean visitNodeInst(Nodable no, HierarchyEnumerator.CellInfo info)
		{
             if (isJobAborted())
                 return false;
            //if (checkAbort()) return false;
			NodeInst node = no.getNodeInst();

			// Its like pins, facet-center
			if (NodeInst.isSpecialNode(node)) return (false);

            boolean inside = doesIntersectBoundingBox(node.getBounds(), info);

			// Its a cell
            if (node.isCellInstance()) return (inside);

            // Geometry outside contour
            if (!inside) return false;

//			boolean found = (netSet == null);
//			for(Iterator<PortInst> pIt = node.getPortInsts(); !found && pIt.hasNext(); )
//			{
//				PortInst pi = pIt.next();
//				PortProto subPP = pi.getPortProto();
//				Network parentNet = info.getNetlist().getNetwork(node, subPP, 0);
//				HierarchyEnumerator.CellInfo cinfo = info;
//				boolean netFound = false;
//				while ((netFound = netSet.contains(parentNet)) == false && cinfo.getParentInst() != null) {
//					parentNet = cinfo.getNetworkInParent(parentNet);
//					cinfo = cinfo.getParentInfo();
//				}
//				found = netFound;
//			}
//			if (!found) return (false); // skipping this node

			// Coverage implants are pure primitive nodes and they are ignored.
			if (node.isPrimtiveSubstrateNode()) //node.getFunction() == PrimitiveNode.Function.NODE)
			{
				if (info.isRootCell())
					deleteList.add(node);
				return (false);
			}
			Technology tech = node.getProto().getTechnology();
			Poly[] polyList = tech.getShapeOfNode(node, true, false, null);
			FixpTransform transform = node.rotateOut();

			boolean includedNode = false;
			for (int i = 0; i < polyList.length; i++)
			{
				Poly poly = polyList[i];
				if (netSet != null)
				{
					PortProto subPP = poly.getPort();
					Network parentNet = info.getNetlist().getNetwork(node, subPP, 0);
					HierarchyEnumerator.CellInfo cinfo = info;
					boolean netFound = false;
					while ((netFound = netSet.contains(parentNet)) == false && cinfo.getParentInst() != null) {
						parentNet = cinfo.getNetworkInParent(parentNet);
						cinfo = cinfo.getParentInfo();
					}
					if (!netFound) continue; // skipping this polygon
				}
				includedNode = true;

				Layer layer = poly.getLayer();

				// Only checking poly or metal for AREA case
				boolean value = isValidFunction(layer, function);
				if (!value) continue;

				if (poly.getPoints().length < 3)
				{
					// When is this happening?
					continue;
				}

				poly.transform(transform);
				// Not sure if I need this for general merge polygons function
				poly.transform(info.getTransformToRoot());

                // If points are not rounded, in IMPLANT map.containsValue() might not work
                poly.roundPoints();

                storeOriginalPolygons(layer, poly);

                Shape pnode = cropGeometry(poly, origBBoxArea);
                // empty intersection
                if (pnode == null)
                    continue;

				tree.add(layer, pnode);
			}

			// add transistor gate/active if any part of the node matched a network
			if (includedNode && geoms != null)
			{
				PrimitiveNode.Function fun = node.getFunction();
				if (fun.isTransistor())
				{
					if (fun.isNTypeTransistor())
					{
						TransistorSize ts = node.getTransistorSize(info.getContext());
						geoms.n_active.area += ts.getDoubleArea();
						geoms.n_active.width += ts.getDoubleWidth();
						geoms.n_gate.area += ts.getDoubleArea();
						geoms.n_gate.width += ts.getDoubleWidth();
					} else if (fun.isPTypeTransistor())
					{
						TransistorSize ts = node.getTransistorSize(info.getContext());
						geoms.p_active.area += ts.getDoubleArea();
						geoms.p_active.width += ts.getDoubleWidth();
						geoms.p_gate.area += ts.getDoubleArea();
						geoms.p_gate.width += ts.getDoubleWidth();
					}
				}
			}
			return (true);
		}

        /**
         * Method to crop original polygon by given bounding box. If they
         * don't intersect, returns original shape
         * @param origGeom polygon to crop
         * @param bBoxArea area that defines bounding box
         * @return cropped shape
         */
        private static Shape cropGeometry(Shape origGeom, Area bBoxArea)
        {
            Shape pnode = origGeom;

            // exclude area outside bounding box
            if (bBoxArea != null)
            {
                Area tmpA = new Area(pnode);
                tmpA.intersect(bBoxArea);
                // Empty intersection
                if (tmpA.isEmpty()) return null;
                pnode = tmpA;
            }
            return pnode;
        }
	}

    public static class TransistorInfo implements Serializable
	{
		/** sum of area of transistors */		public double area;
		/** sum of width of transistors */		public double width;
	};

	/**
	 * Class to represent all geometry on a network during layer coverage analysis.
	 */
	public static class GeometryOnNetwork implements Serializable {
	    public final Cell cell;
	    protected Set<Network> nets;
	    private double lambda;
		private boolean printable;
        private Layer onlyThisLayer;

	    // these lists tie together a layer, its area, and its half-perimeter
	    private ArrayList<Layer> layers;
	    private ArrayList<Double> areas;
	    private ArrayList<Double> halfPerimeters;
	    private double totalWire;
        private double totalArea;
        private LayerCoveragePreferences lcp;

        // these are the area and transistor widths for gate and active in N and P
        TransistorInfo p_gate, n_gate, p_active, n_active;

        public GeometryOnNetwork(Cell cell, Set<Network> nets, double lambda, boolean printable,
                                 Layer onlyThisLayer, LayerCoveragePreferences lcp)
        {
            this.lcp = lcp;
	        this.cell = cell;
	        this.nets = nets;
	        this.lambda = lambda;
            this.onlyThisLayer = onlyThisLayer;
	        layers = new ArrayList<Layer>();
	        areas = new ArrayList<Double>();
	        halfPerimeters = new ArrayList<Double>();
		    this.printable = printable;
	        totalWire = 0;
            totalArea = 0;
            p_gate = new TransistorInfo();
            n_gate = new TransistorInfo();
            p_active = new TransistorInfo();
            n_active = new TransistorInfo();
	    }

	    public double getTotalWireLength() { return totalWire; }

	    protected void setTotalArea(double area) {totalArea = area; }

	    public TransistorInfo getPGate() { return p_gate; }

	    public TransistorInfo getNGate() { return n_gate; }

	    public TransistorInfo getPActive() { return p_active; }

	    public TransistorInfo getNActive() { return n_active; }

	    public List<Layer> getLayers() { return layers; }

	    public List<Double> getAreas() { return areas; }

	    public List<Double> getHalfPerimeters() { return halfPerimeters; }

        private void addLayer(Layer layer, double area, double halfperimeter)
        {
            assert(layer != null);
            if (onlyThisLayer != null && layer != onlyThisLayer) return; // skip this one
	        layers.add(layer);
	        areas.add(new Double(area));
	        halfPerimeters.add(new Double(halfperimeter));

	        Layer.Function func = layer.getFunction();
	        // accumulate total wire length on all metal/poly layers
	        if (func.isPoly() && !func.isGatePoly() || func.isMetal()) {
	            totalWire += halfperimeter;
	        }
	    }

        /**
         * Method to analyze amount of area covered by layer and if meets the minimum
         * specified
         * @param bbox
         * @param errorLogger
         * @return true if no errors are found
         */
        public boolean analyzeCoverage(Rectangle2D bbox, ErrorLogger errorLogger)
        {
            totalArea = (bbox.getHeight()*bbox.getWidth())/(lambda*lambda);
            boolean foundError = false;

            for (int i = 0; i < layers.size(); i++)
            {
                Layer layer = layers.get(i);
                Double area = areas.get(i);
                double percentage = area.doubleValue()/totalArea * 100;
                double minV = lcp.getAreaCoverage(layer);
                if (percentage < minV)
                {
                    String msg = "Error area coverage " + layer.getName() + " min value = " + minV + " actual value = " + percentage;
                    PolyBase poly = new PolyBase(bbox);
                    errorLogger.logError(msg, poly, cell, layer.getIndex());
                    foundError = true;
                }
            }
            return foundError;
        }

	    public void print() {
		    // Doesn't print information
		    if (!printable) return;

            // nets is null for mode=AREA
            if (nets != null)
            {
                for(Network net : nets)
                {
                    System.out.println("For " + net + " in " + cell + ":");
                }
            }

	        for (int i=0; i<layers.size(); i++) {
	            Layer layer = layers.get(i); // autoboxing
	            double area = areas.get(i).doubleValue();  // autoboxing
	            double halfperim = halfPerimeters.get(i).doubleValue();

	            System.out.println("\tLayer " + layer.getName()
	                    + ":\t area " + TextUtils.formatDouble(area) + "(" + TextUtils.formatDouble((area/totalArea)*100) + "%)"
	                    + "\t half-perimeter " + TextUtils.formatDouble(halfperim)
	                    + "\t ratio " + TextUtils.formatDouble(area/halfperim));
	        }
	        if (totalWire > 0)
	            System.out.println("Total wire length = " + TextUtils.formatDouble(totalWire/lambda));
            if (totalArea > 0)
                System.out.println("Total cell area = " + TextUtils.formatDouble(totalArea));
	    }
	}

    /***********************************
     * JUnit interface
     ***********************************/
    public static boolean testAll()
    {
        //return (LayerCoverageToolTest.basicAreaCoverageTest("area.log"));
        return true;
    }

}
