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

import com.sun.electric.database.CellBackup;
import com.sun.electric.database.CellRevision;
import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.ImmutableArcInst;
import com.sun.electric.database.ImmutableNodeInst;
import com.sun.electric.database.Snapshot;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.constraint.Layout;
import com.sun.electric.database.geometry.*;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.hierarchy.HierarchyEnumerator;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.id.CellId;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.text.PrefPackage;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.text.Version;
import com.sun.electric.database.topology.*;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.*;
import com.sun.electric.technology.technologies.Artwork;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.Listener;
import com.sun.electric.tool.user.ErrorLogger;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.CircuitChangeJobs;
import com.sun.electric.util.ElapseTimer;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.ECoord;
import com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.GenMath;
import com.sun.electric.util.math.MutableDouble;
import com.sun.electric.util.math.MutableLong;
import com.sun.electric.util.math.Orientation;

import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Point2D;
import java.util.*;
import java.io.Serializable;
import java.util.prefs.Preferences;

/**
 * This is the Design Rule Checker tool.
 */
public class DRC extends Listener
{
	/** the DRC tool. */								     protected static DRC tool = new DRC();

    private static final boolean THREAD_SAFE_DRC = true;
     // Client static variables
    /** for logging incremental errors */                    private static ErrorLogger errorLoggerIncremental = ErrorLogger.newInst("DRC (incremental)", true);
	/** map of cells and their objects to DRC */		     private static Map<Cell,Set<Geometric>> cellsToCheck = new HashMap<Cell,Set<Geometric>>();
    /** flag to show that incremental DRC is running */    private static boolean incrementalRunning = false;

   // Server static variables
    /** to temporary store DRC dates for spacing checking */ private static Map<Cell,StoreDRCInfo> storedSpacingDRCDate = new HashMap<Cell,StoreDRCInfo>();
    /** to temporary store DRC dates for area checking */    private static Map<Cell,StoreDRCInfo> storedAreaDRCDate = new HashMap<Cell,StoreDRCInfo>();

    static final double TINYDELTA = DBMath.getEpsilon()*1.1;
    /** key of Variable holding DRC Cell annotations. */	static final Variable.Key DRC_ANNOTATION_KEY = Variable.newKey("ATTR_DRC");

    /*********************************** Annotations ***********************************/
    public static void makeDRCAnnotation()
    {
        CircuitChangeJobs.MakeCellAnnotationJob.makeAnnotationMenuCommand(tool, DRC.DRC_ANNOTATION_KEY, "BLACK");
    }
    /*********************************** End of Annotations ***********************************/

    /*********************************** Crop Functions ***********************************/

    /**
	 * Method to see if polygons in "pList" (describing arc "ai") should be cropped against a
     * connecting transistor.  Crops the polygon if so.
     */
    static void cropActiveArc(ArcInst ai, boolean ignoreCenterCuts, Poly [] pList)
    {
        // look for an active layer in this arc
        int tot = pList.length;
        int diffPoly = -1;
        // note: arcs would never have tot=0 since they are only defined to wire elements with valid rules
        if (tot == 0)
        {
        	if (Job.getDebug())
        		System.out.println("DRC Warning: Analysing arc that doesn't have layers with DRC rules: " + ai.getName());
        	return;
//        	assert(tot!=0);
        }
        
        for(int j=0; j<tot; j++)
        {
            Poly poly = pList[j];
            Layer layer = poly.getLayer();
            if (layer == null) continue;
            Layer.Function fun = layer.getFunction();
            if (fun.isDiff()) { diffPoly = j;   break; }
        }
        if (diffPoly < 0) return;
        Poly poly = pList[diffPoly];

        // must be Manhattan
        Rectangle2D polyBounds = poly.getBox();
        if (polyBounds == null) return;
        polyBounds = new Rectangle2D.Double(polyBounds.getMinX(), polyBounds.getMinY(), polyBounds.getWidth(), polyBounds.getHeight());
        Layer.Function.Set set = Layer.getMultiLayersSet(poly.getLayer());

        // search for adjoining transistor in the cell
        boolean cropped = false;
        boolean halved = false;
        for(int i=0; i<2; i++)
        {
            PortInst pi = ai.getPortInst(i);
            NodeInst ni = pi.getNodeInst();
            if (!ni.getFunction().isFET()) continue;

            // crop the arc against this transistor
            FixpTransform trans = ni.rotateOut();
            Technology tech = ni.getProto().getTechnology();
            Poly [] activeCropPolyList = tech.getShapeOfNode(ni, false, ignoreCenterCuts, set);

            int nTot = activeCropPolyList.length;

            for(int k=0; k<nTot; k++)
            {
                Poly nPoly = activeCropPolyList[k];
                if (nPoly.getLayer() != poly.getLayer()) continue;
                nPoly.transform(trans);
                Rectangle2D nPolyBounds = nPoly.getBox();
                if (nPolyBounds == null) continue;
                // @TODO Why only one half is half crop?
                // Should I change cropBox by cropBoxComplete?
                int result = (halved) ?
                        Poly.cropBox(polyBounds, nPolyBounds) :
                        Poly.halfCropBox(polyBounds, nPolyBounds);

                if (result == 1)
                {
                    // remove this polygon from consideration
                    poly.setLayer(null);
                    return;
                }
                cropped = true;
                halved = true;
            }
        }
        if (cropped)
        {
            Poly.Type style = poly.getStyle();
            Layer layer = poly.getLayer();
            poly = new Poly(polyBounds);
            poly.setStyle(style);
            poly.setLayer(layer);
            pList[diffPoly] = poly;
        }
    }

    /*********************************** End of crip functions ***********************************/

    /**
         * Method to examine cell "cell" in the area (lx<=X<=hx, ly<=Y<=hy) for objects
     * on layer "layer".  Apply transformation "moreTrans" to the objects.  If polygons are
     * found at (xf1,yf1) or (xf2,yf2) or (xf3,yf3) then sets "p1found/p2found/p3found" to 1.
     *  If poly1 or poly2 is not null, ignore geometries that are identical to them.
     * If all locations are found, returns true.
     */
    static boolean lookForLayerCoverage(Geometric geo1, Poly poly1, Geometric geo2, Poly poly2, Cell cell,
                                        Layer layer, FixpTransform moreTrans, Rectangle2D bounds,
                                        Point2D pt1, Point2D pt2, Point2D pt3, boolean[] pointsFound,
                                        boolean overlap, Layer.Function.Set layerFunction, boolean ignoreSameGeometry,
                                        boolean ignoreCenterCuts)
    {
        int j;
        Rectangle2D newBounds = new Rectangle2D.Double();  // Sept 30

        for (Iterator<Geometric> it = cell.searchIterator(bounds); it.hasNext();)
        {
            Geometric g = it.next();

            // You can't skip the same geometry otherwise layers in the same Geometric won't
            // be tested.
            // But it is necessary while testing flat geometries... in minWidthInternal
            if (ignoreSameGeometry && (g == geo1 || g == geo2))
                continue;

            // I can't skip geometries to exclude from the search
            if (g instanceof NodeInst)
            {
                NodeInst ni = (NodeInst) g;
                if (NodeInst.isSpecialNode(ni))
                    continue; // Nov 16, no need for checking pins or other special nodes;
                if (ni.isCellInstance())
                {
                    // compute bounding area inside of sub-cell
                    FixpTransform rotI = ni.rotateIn();
                    FixpTransform transI = ni.translateIn();
                    rotI.preConcatenate(transI);
                    newBounds.setRect(bounds);
                    DBMath.transformRect(newBounds, rotI);

                    // compute new matrix for sub-cell examination
                    FixpTransform trans = ni.translateOut(ni.rotateOut());
                    trans.preConcatenate(moreTrans);
                    if (lookForLayerCoverage(geo1, poly1, geo2, poly2, (Cell) ni.getProto(), layer, trans, newBounds,
                        pt1, pt2, pt3, pointsFound, overlap, layerFunction, false, ignoreCenterCuts))
                        return true;
                    continue;
                }
                Technology tech = ni.getProto().getTechnology();
                // I have to ask for electrical layers otherwise it will retrieve one polygon for polysilicon
                // and poly.polySame(poly1) will never be true. CONTRADICTION!
                Poly[] layerLookPolyList = tech.getShapeOfNode(ni, false, ignoreCenterCuts, layerFunction);
                int tot = layerLookPolyList.length;

                if (tot == 0)
                    continue;

                FixpTransform bound = ni.rotateOut();
                bound.preConcatenate(moreTrans);

                for (int i = 0; i < tot; i++)
                {
                    Poly poly = layerLookPolyList[i];
                    // sameLayer test required to check if Active layer is not identical to thick active layer
                    if (!tech.sameLayer(poly.getLayer(), layer))
                    {
                        // This happens when you have implant with VTH implant, Function.Set doesn't distinguish them
                        if (Job.getDebug())
                            System.out.println("DRC: Wrong Function.Set with " + layer.getName() + " and " + poly.getLayer().getName());
//                        assert(false);
                        continue;
                    }

                    // Should be the transform before?
                    poly.transform(bound);

                    if (poly1 != null && !overlap && poly.polySame(poly1))
                        continue;
                    if (poly2 != null && !overlap && poly.polySame(poly2))
                        continue;

                    if (!pointsFound[0] && poly.isInside(pt1))
                        pointsFound[0] = true; // @TODO Should still evaluate isInside if pointsFound[i] is already valid?
                    if (!pointsFound[1] && poly.isInside(pt2)) pointsFound[1] = true;
                    if (pt3 != null && !pointsFound[2] && poly.isInside(pt3)) pointsFound[2] = true;
                    for (j = 0; j < pointsFound.length && pointsFound[j]; j++) ;
                    if (j == pointsFound.length)
                        return true;
                    // No need of checking rest of the layers
                    break; // assuming only 1 polygon per layer (non-electrical)
                }
            } else
            {
                ArcInst ai = (ArcInst) g;
                Technology tech = ai.getProto().getTechnology();
                Poly[] layerLookPolyList = tech.getShapeOfArc(ai, layerFunction);

                int tot = layerLookPolyList.length;
                for (int i = 0; i < tot; i++)
                {
                    Poly poly = layerLookPolyList[i];
                    // sameLayer test required to check if Active layer is not identical to thich actice layer
                    if (!tech.sameLayer(poly.getLayer(), layer))
                    {
                        // This happens when you have implant with VTH implant, Function.Set doesn't distinguish them
                        if (Job.getDebug())
                            System.out.println("DRC: Wrong Function.Set with " + layer.getName() + " and " + poly.getLayer().getName());
                        continue;
                    }
                    poly.transform(moreTrans);  // @TODO Should still evaluate isInside if pointsFound[i] is already valid?
                    if (!pointsFound[0] && poly.isInside(pt1)) pointsFound[0] = true;
                    if (!pointsFound[1] && poly.isInside(pt2)) pointsFound[1] = true;
                    if (pt3 != null && !pointsFound[2] && poly.isInside(pt3)) pointsFound[2] = true;
                    for (j = 0; j < pointsFound.length && pointsFound[j]; j++) ;
                    if (j == pointsFound.length)
                        return true;
                    // No need of checking rest of the layers
                    break;
                }
            }

            for (j = 0; j < pointsFound.length && pointsFound[j]; j++) ;
            if (j == pointsFound.length)
            {
                assert (false); // test when otherwise the calculation is useless! System.out.println("When?");
                return true;
            }
        }
        return false;
    }

    /**
     * Method to determine if neighbor would help to cover the minimum conditions
     *
     * @return true if error was found (not warning)
     */
    static boolean checkExtensionWithNeighbors(Cell cell, Geometric geom, Poly poly, Layer layer, Rectangle2D bounds,
                                               DRCTemplate minWidthRule, int dir, boolean onlyOne, boolean reportError,
                                               Layer.Function.Set layerFunction, ReportInfo reportInfo)
    {
        double actual = 0;
        Point2D left1, left2, left3, right1, right2, right3;
        //if (bounds.getWidth() < minWidthRule.value)
        String msg = "";

        // potential problem along X
        if (dir == 0)
        {
            actual = bounds.getWidth();
            msg = "(X axis)";
            double leftW = bounds.getMinX() - TINYDELTA;
            left1 = new Point2D.Double(leftW, bounds.getMinY());
            left2 = new Point2D.Double(leftW, bounds.getMaxY());
            left3 = new Point2D.Double(leftW, bounds.getCenterY());
            double rightW = bounds.getMaxX() + TINYDELTA;
            right1 = new Point2D.Double(rightW, bounds.getMinY());
            right2 = new Point2D.Double(rightW, bounds.getMaxY());
            right3 = new Point2D.Double(rightW, bounds.getCenterY());
        } else
        {
            actual = bounds.getHeight();
            msg = "(Y axis)";
            double leftH = bounds.getMinY() - TINYDELTA;
            left1 = new Point2D.Double(bounds.getMinX(), leftH);
            left2 = new Point2D.Double(bounds.getMaxX(), leftH);
            left3 = new Point2D.Double(bounds.getCenterX(), leftH);
            double rightH = bounds.getMaxY() + TINYDELTA;
            right1 = new Point2D.Double(bounds.getMinX(), rightH);
            right2 = new Point2D.Double(bounds.getMaxX(), rightH);
            right3 = new Point2D.Double(bounds.getCenterX(), rightH);
        }
        // see if there is more of this layer adjoining on either side
        boolean[] pointsFound = new boolean[3];
        pointsFound[0] = pointsFound[1] = pointsFound[2] = false;
        Rectangle2D newBounds = new Rectangle2D.Double(bounds.getMinX() - TINYDELTA, bounds.getMinY() - TINYDELTA,
            bounds.getWidth() + TINYDELTA * 2, bounds.getHeight() + TINYDELTA * 2);
        boolean zeroWide = (bounds.getWidth() == 0 || bounds.getHeight() == 0);

        boolean overlapLayer = lookForLayer(poly, cell, layer, DBMath.MATID, newBounds,
            left1, left2, left3, pointsFound, layerFunction, reportInfo.ignoreCenterCuts); //) return false;
    //        if (overlapLayer && !zeroWide) return false;
        if (overlapLayer) return false;

        // Try the other corner
        pointsFound[0] = pointsFound[1] = pointsFound[2] = false;
        overlapLayer = lookForLayer(poly, cell, layer, DBMath.MATID, newBounds,
            right1, right2, right3, pointsFound, layerFunction, reportInfo.ignoreCenterCuts); //) return false;
    //        if (overlapLayer && !zeroWide) return false;
        if (overlapLayer) return false;

        DRCErrorType errorType = DRCErrorType.MINWIDTHERROR;
        String extraMsg = msg;
        String rule = minWidthRule.ruleName;

        // Only when the flat element is fully covered send the warning
        // otherwise it is considered an error.
        if (zeroWide && overlapLayer)
        {
            extraMsg = " but covered by other layer";
            errorType = DRCErrorType.ZEROLENGTHARCWARN;
            rule = null;
        }

        if (reportError)
            createDRCErrorLogger(reportInfo, errorType, extraMsg, cell, minWidthRule.getValue(0), actual, rule,
                (onlyOne) ? null : poly, geom, layer, null, null, null);
        return !overlapLayer;
    }

    /**
         * Method to examine cell "cell" in the area (lx<=X<=hx, ly<=Y<=hy) for objects
     * on layer "layer".  Apply transformation "moreTrans" to the objects.  If polygons are
     * found at (xf1,yf1) or (xf2,yf2) or (xf3,yf3) then sets "p1found/p2found/p3found" to 1.
     * If all locations are found, returns true.
     */
    static boolean lookForLayer(Poly thisPoly, Cell cell, Layer layer, FixpTransform moreTrans,
                                Rectangle2D bounds, Point2D pt1, Point2D pt2, Point2D pt3, boolean[] pointsFound,
                                Layer.Function.Set layerFunction, boolean ignoreCenterCuts)
    {
        int j;
        boolean skip = false;
        Rectangle2D newBounds = new Rectangle2D.Double();  // sept 30

        for (Iterator<Geometric> it = cell.searchIterator(bounds); it.hasNext();)
        {
            Geometric g = it.next();
            if (g instanceof NodeInst)
            {
                NodeInst ni = (NodeInst) g;
                if (NodeInst.isSpecialNode(ni))
                    continue; // Nov 16, no need for checking pins or other special nodes;
                if (ni.isCellInstance())
                {
                    // compute bounding area inside of sub-cell
                    FixpTransform rotI = ni.rotateIn();
                    FixpTransform transI = ni.translateIn();
                    rotI.preConcatenate(transI);
                    newBounds.setRect(bounds);
                    DBMath.transformRect(newBounds, rotI);

                    // compute new matrix for sub-cell examination
                    FixpTransform trans = ni.translateOut(ni.rotateOut());
                    trans.preConcatenate(moreTrans);
                    if (lookForLayer(thisPoly, (Cell) ni.getProto(), layer, trans, newBounds,
                        pt1, pt2, pt3, pointsFound, layerFunction, ignoreCenterCuts))
                        return true;
                    continue;
                }
                Technology tech = ni.getProto().getTechnology();
                Poly[] layerLookPolyList = tech.getShapeOfNode(ni, false, ignoreCenterCuts, layerFunction);
                int tot = layerLookPolyList.length;

                if (tot == 0)
                    continue;

                FixpTransform bound = ni.rotateOut();
                bound.preConcatenate(moreTrans);

                for (int i = 0; i < tot; i++)
                {
                    Poly poly = layerLookPolyList[i];
                    // sameLayer test required to check if Active layer is not identical to thich actice layer
                    if (!tech.sameLayer(poly.getLayer(), layer))
                    {
                        // This happens when you have implant with VTH implant, Function.Set doesn't distinguish them
                        if (Job.getDebug())
                            System.out.println("DRC: Wrong Function.Set with " + layer.getName() + " and " + poly.getLayer().getName());
                        continue;
                    }

                    if (thisPoly != null && poly.polySame(thisPoly)) continue;
                    poly.transform(bound);
                    if (poly.isInside(pt1)) pointsFound[0] = true;
                    if (poly.isInside(pt2)) pointsFound[1] = true;
                    if (pt3 != null && poly.isInside(pt3)) pointsFound[2] = true;
                    for (j = 0; j < pointsFound.length && pointsFound[j]; j++) ;
                    boolean newR = (j == pointsFound.length);
                    if (newR)
                    {
                        return true;
                    }
                    // No need of checking rest of the layers?
                    //break;
                }
            } else
            {
                ArcInst ai = (ArcInst) g;
                Technology tech = ai.getProto().getTechnology();
                Poly[] layerLookPolyList = tech.getShapeOfArc(ai, layerFunction);
                int tot = layerLookPolyList.length;

                for (int i = 0; i < tot; i++)
                {
                    Poly poly = layerLookPolyList[i];
                    // sameLayer test required to check if Active layer is not identical to thich actice layer
                    if (!tech.sameLayer(poly.getLayer(), layer))
                    {
                        // This happens when you have implant with VTH implant, Function.Set doesn't distinguish them
                        if (Job.getDebug())
                            System.out.println("DRC: Wrong Function.Set with " + layer.getName() + " and " + poly.getLayer().getName());
                        continue;
                    }

                    poly.transform(moreTrans);
                    if (poly.isInside(pt1)) pointsFound[0] = true;
                    if (poly.isInside(pt2)) pointsFound[1] = true;
                    if (pt3 != null && poly.isInside(pt3)) pointsFound[2] = true;
                    for (j = 0; j < pointsFound.length && pointsFound[j]; j++) ;
                    boolean newR = (j == pointsFound.length);
                    if (newR)
                        return true;
                    // No need of checking rest of the layers
                    //break;
                }
            }

            for (j = 0; j < pointsFound.length && pointsFound[j]; j++) ;
            if (j == pointsFound.length)
            {
                System.out.println("When?");
                return true;
            }
        }
        if (skip) System.out.println("This case in lookForLayerNew before");

        return false;
    }

    static boolean checkMinWidthInternal(Geometric geom, Layer layer, Poly poly, boolean onlyOne,
                                          DRCTemplate minWidthRule, boolean reportError,
                                          Layer.Function.Set layerFunction, ReportInfo reportInfo)
    {
        Cell cell = geom.getParent();
        if (minWidthRule == null) return false;

        // get polygon information
        double minWidthValue = minWidthRule.getValue(0);
        Rectangle2D bounds = poly.getBox();
        Point2D [] points = poly.getPoints();

        // handle round geometry
        if (poly.getStyle() == Poly.Type.DISC)
        {
        	bounds = poly.getBounds2D();
        	points = Poly.makePoints(bounds.getMinX(), bounds.getMaxX(), bounds.getMinY(), bounds.getMaxY());
        }

        // only in case of flat elements represented by a line
        // most likely a flat arc, vertical or horizontal.
        // It doesn't consider arbitrary angled lines.
        // If bounds is null, it might have area if it is non-Manhattan
        boolean flatPoly = ((bounds == null && GenMath.doublesEqual(poly.getArea(), 0)));
        if (flatPoly)
        {
            Point2D from = points[0];
            Point2D to = points[1];

            // Assuming it is a single segment the flat region
            // looking for two distinct points
            if (DBMath.areEquals(from, to))
            {
                boolean found = false;
                for (int i = 2; i < points.length; i++)
                {
                    if (!DBMath.areEquals(from, points[i]))
                    {
                        to = points[i];
                        found = true;
                        break;
                    }
                }
                if (!found) // single segment where to == from
                {
                    return false; // skipping this case.
                }
            }

            Point2D center = new Point2D.Double((from.getX() + to.getX()) / 2, (from.getY() + to.getY()) / 2);

            // looking if points around the overlapping area are inside another region
            // to avoid the error
            boolean [] pointsFound = new boolean[3];
            pointsFound[0] = pointsFound[1] = pointsFound[2] = false;
            boolean found = lookForLayerCoverage(geom, poly, null, null, cell, layer, DBMath.MATID,  poly.getBounds2D(),
                from, to, center, pointsFound, true, layerFunction, true, reportInfo.ignoreCenterCuts);
            if (found) return false; // no error, flat element covered by other elements.

            if (reportError)
                createDRCErrorLogger(reportInfo, DRCErrorType.MINWIDTHERROR, null, cell, minWidthValue, 0, minWidthRule.ruleName,
                    (onlyOne) ? null : poly, geom, layer, null, null, null);
            return true;
        }

        // simpler analysis if Manhattan
        if (bounds != null)
        {
            boolean tooSmallWidth = DBMath.isGreaterThan(minWidthValue, bounds.getWidth());
            boolean tooSmallHeight = DBMath.isGreaterThan(minWidthValue, bounds.getHeight());
            if (!tooSmallWidth && !tooSmallHeight) return false;

            boolean foundError = false;
            if (tooSmallWidth && checkExtensionWithNeighbors(cell, geom, poly, layer, bounds, minWidthRule,
                0, onlyOne, reportError, layerFunction, reportInfo))
                foundError = true;
            if (tooSmallHeight && checkExtensionWithNeighbors(cell, geom, poly, layer, bounds, minWidthRule,
                1, onlyOne, reportError, layerFunction, reportInfo))
                foundError = true;
            return foundError;
        }

        // nonmanhattan polygon: stop now if it has no size
        Poly.Type style = poly.getStyle();
        if (style != Poly.Type.FILLED && style != Poly.Type.CLOSED && style != Poly.Type.CROSSED &&
            style != Poly.Type.OPENED && style != Poly.Type.OPENEDT1 && style != Poly.Type.OPENEDT2 &&
            style != Poly.Type.OPENEDT3 && style != Poly.Type.VECTORS) return false;

        // simple check of nonmanhattan polygon for minimum width
        bounds = poly.getBounds2D();
        double actual = Math.min(bounds.getWidth(), bounds.getHeight());
        if (actual < minWidthValue)
        {
            if (reportError)
                createDRCErrorLogger(reportInfo, DRCErrorType.MINWIDTHERROR, null, cell, minWidthValue, actual, minWidthRule.ruleName,
                    (onlyOne) ? null : poly, geom, layer, null, null, null);
            return true;
        }

        // check distance of each line's midpoint to perpendicular opposite point
        int count = points.length;
        for (int i = 0; i < count; i++)
        {
            Point2D from;
            if (i == 0) from = points[count - 1];
            else
                from = points[i - 1];
            Point2D to = points[i];
            if (from.equals(to)) continue;

            double ang = DBMath.figureAngleRadians(from, to);
            Point2D center = new Point2D.Double((from.getX() + to.getX()) / 2, (from.getY() + to.getY()) / 2);
            double perpang = ang + Math.PI / 2;
            for (int j = 0; j < count; j++)
            {
                if (j == i) continue;
                Point2D oFrom;
                if (j == 0) oFrom = points[count - 1];
                else
                    oFrom = points[j - 1];
                Point2D oTo = points[j];
                if (oFrom.equals(oTo)) continue;
                double oAng = DBMath.figureAngleRadians(oFrom, oTo);
                double rAng = ang;
                while (rAng > Math.PI) rAng -= Math.PI;
                double rOAng = oAng;
                while (rOAng > Math.PI) rOAng -= Math.PI;
                if (DBMath.doublesEqual(rAng, rOAng))
                {
                    // lines are parallel: see if they are colinear
                    if (DBMath.isOnLine(from, to, oFrom)) continue;
                    if (DBMath.isOnLine(from, to, oTo)) continue;
                    if (DBMath.isOnLine(oFrom, oTo, from)) continue;
                    if (DBMath.isOnLine(oFrom, oTo, to)) continue;
                }
                Point2D inter = DBMath.intersectRadians(center, perpang, oFrom, oAng);
                if (inter == null) continue;
                if (inter.getX() < Math.min(oFrom.getX(), oTo.getX()) || inter.getX() > Math.max(oFrom.getX(), oTo.getX()))
                    continue;
                if (inter.getY() < Math.min(oFrom.getY(), oTo.getY()) || inter.getY() > Math.max(oFrom.getY(), oTo.getY()))
                    continue;
                double fdx = center.getX() - inter.getX();
                double fdy = center.getY() - inter.getY();
                actual = DBMath.round(Math.sqrt(fdx * fdx + fdy * fdy));

                if (actual < minWidthValue)
                {
                    if (reportError)
                    {
                        // look between the points to see if it is minimum width or notch
                        if (poly.isInside(new Point2D.Double((center.getX() + inter.getX()) / 2, (center.getY() + inter.getY()) / 2)))
                        {
                            createDRCErrorLogger(reportInfo, DRCErrorType.MINWIDTHERROR, null, cell, minWidthValue,
                                actual, minWidthRule.ruleName, (onlyOne) ? null : poly, geom, layer, null, null, null);
                        } else
                        {
                            createDRCErrorLogger(reportInfo, DRCErrorType.NOTCHERROR, null, cell, minWidthValue,
                                actual, minWidthRule.ruleName, (onlyOne) ? null : poly, geom, layer, poly, geom, layer);
                        }
                    }
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Method to determine if it is allowed to have both layers touching.
     * special rule for allowing touching:
     *   the layers are the same and either:
     *     they connect and are *NOT* contact layers
	 *   or:
	 *     they don't connect and are implant layers (substrate/well)
     * @param tech
     * @param con
     * @param layer1
     * @param layer2
     * @return true if the layer may touch
     */
    static boolean mayTouch(Technology tech, boolean con, Layer layer1, Layer layer2)
    {
        boolean maytouch = false;
		if (tech.sameLayer(layer1, layer2))
		{
			Layer.Function fun = layer1.getFunction();
			if (con)
			{
				if (!fun.isContact()) maytouch = true;
			} else
			{
				if (fun.isSubstrate()) maytouch = true;
				// Special cases for thick actives
				else
				{
					// Searching for THICK bit
					int funExtras = layer1.getFunctionExtras();
					if (fun.isDiff() && (funExtras&Layer.Function.THICK) != 0)
					{
						if (Job.LOCALDEBUGFLAG) System.out.println("Thick active found in Quick.checkDist");
						maytouch = true;
					}
				}
			}
		}
        return maytouch;
    }

    /**
     * Method to check if a PrimitiveNode contains a layer that is forbidden in the current technology
     * @param ni node to analyze
     * @param reportInfo data for the report
     * @return True if the node is forbidden
     */
    static boolean checkNodeAgainstCombinationRules(NodeInst ni, ReportInfo reportInfo)
    {
        Cell cell = ni.getParent();
        NodeProto np = ni.getProto();
		Technology tech = np.getTechnology();
        if (np instanceof PrimitiveNode)
        {
            DRCTemplate forbidRule =
            DRC.isForbiddenNode(tech.getPrimNodeIndexInTech((PrimitiveNode)np), -1,
                DRCTemplate.DRCRuleType.FORBIDDEN, tech);
            if (forbidRule != null)
            {
                DRC.createDRCErrorLogger(reportInfo, DRC.DRCErrorType.FORBIDDEN, " is not allowed by selected foundry", cell,
                    -1, -1, forbidRule.ruleName, null, ni, null, null, null, null);
                return true;
            }
        }
        return false;
    }

    /**
     * Method to check which combination of OD2 layers are allowed
     * @param layer
     * @return true if there is an invalid combination of OD2 layers
     */
    static boolean checkOD2Combination(Technology tech, NodeInst ni, Layer layer, Map<Layer,NodeInst> od2Layers,
                                       ReportInfo reportInfo)
    {
        int funExtras = layer.getFunctionExtras();
        boolean notOk = false;

        if (layer.getFunction().isImplant() && (funExtras&Layer.Function.THICK) != 0)
        {
            // Only stores first node found
            od2Layers.put(layer, ni);

            // More than one type used.
            if (od2Layers.size() != 1)
            {
                for (Map.Entry<Layer,NodeInst> e : od2Layers.entrySet())
                {
                    Layer lay1 = e.getKey();
                    if (lay1 == layer) continue;
                    DRCTemplate rule = isForbiddenNode(lay1.getIndex(), layer.getIndex(), DRCTemplate.DRCRuleType.FORBIDDEN, tech);
                    if (rule != null)
                    {
                        NodeInst node = e.getValue(); // od2Layers.get(lay1);
                        String message = "- combination of layers '" + layer.getName() + "' and '" + lay1.getName() + "' (in '" +
                                node.getParent().getName() + ":" + node.getName() +"') not allowed by selected foundry";
                        createDRCErrorLogger(reportInfo, DRCErrorType.FORBIDDEN, message, ni.getParent(),
                            -1, -1, rule.ruleName, null, ni, null, null, node, null);

                        return true;
                    }
                }
            }
        }
        return notOk;
    }

    /**
     * Method to retrieve only Polys from a NodeInst associated with layers with rules.
     * @param tech
     * @param reportInfo
     * @param ni
     * @return Polys for the NodeInst.
     */
    static Poly [] getShapeOfNodeBasedOnRules(Technology tech, ReportInfo reportInfo, NodeInst ni)
    {
        // get layers from the NodeInst. Not a cell at this point.
        Layer.Function.Set setFunction = getOnlyLayersWithRulesFromPrimitive(ni.getProto(), tech);

        // get all of the polygons on this node
		return tech.getShapeOfNode(ni, true, reportInfo.ignoreCenterCuts, setFunction);
    }

    /**
     * Method to retrieve only Polyss from an ArcInst associated with layers with rules.
     * @param tech
     * @param ai
     * @return Polys for the ArcInst.
     */
    static Poly[] getShapeOfArcBasedOnRules(Technology tech, ArcInst ai)
    {
                // get layers from the NodeInst. Not a cell at this point.
        Layer.Function.Set setFunction = getOnlyLayersWithRulesFromArc(ai.getProto(), tech);

        // get all of the polygons on this arc
		Poly [] arcInstPolyList = tech.getShapeOfArc(ai, setFunction);
//        Poly [] arcInstPolyListOLD = tech.getShapeOfArc(ai);
        return arcInstPolyList;
    }

    /**
	 * Method to look for an interaction between instances "ni1" and "ni2".  If it is found,
     * return TRUE.  If not found, add to the list and return FALSE.
     */
    static boolean checkInteraction(Map<NodeInst,List<InstanceInter>> instanceInteractionMap,
                                    DRCCheckMode errorTypeSearch,
                                    NodeInst ni1, NodeInst n1Parent, boolean n1cellParameterized,
                                    NodeInst ni2, NodeInst n2Parent, boolean n2cellParameterized,
                                    NodeInst triggerNi, Rectangle2D searchBnd)
    {
        if (errorTypeSearch == DRCCheckMode.ERROR_CHECK_EXHAUSTIVE) return false;

        // must recheck parameterized instances always
        if (n1cellParameterized || n2cellParameterized)
            return false;

//        CheckProto cp = getCheckProto((Cell)ni1.getProto());
//		if (cp.cellParameterized) return false;
//		cp = getCheckProto((Cell)ni2.getProto());
//		if (cp.cellParameterized) return false;

        // keep the instances in proper numeric order
        InstanceInter dii = new InstanceInter();
        if (ni1.getNodeId() < ni2.getNodeId())
        {
            NodeInst swapni = ni1;   ni1 = ni2;   ni2 = swapni;
        } else if (ni1 == ni2)
        {
            int node1Orientation = ni1.getAngle();
            if (ni1.isMirroredAboutXAxis()) node1Orientation += 3600;
            if (ni1.isMirroredAboutYAxis()) node1Orientation += 7200;
            int node2Orientation = ni2.getAngle();
            if (ni2.isMirroredAboutXAxis()) node2Orientation += 3600;
            if (ni2.isMirroredAboutYAxis()) node2Orientation += 7200;
            if (node1Orientation < node2Orientation)
            {
                NodeInst swapNI = ni1;   ni1 = ni2;   ni2 = swapNI;
                System.out.println("Check this case in Quick.checkInteraction");
            }
        }

        // get essential information about their interaction
        dii.cell1 = (Cell)ni1.getProto();
        dii.or1 = ni1.getOrient();
//        dii.bnd = searchBnd;

        dii.cell2 = (Cell)ni2.getProto();
        dii.or2 = ni2.getOrient();

// This has to be calculated before the swap
        dii.dx = ni2.getAnchorCenterX() - ni1.getAnchorCenterX();
        dii.dy = ni2.getAnchorCenterY() - ni1.getAnchorCenterY();
        dii.n1Parent = n1Parent;
        dii.n2Parent = n2Parent;
        dii.triggerNi = triggerNi;

        // if found, stop now
        // Find the interaction
        if (findInteraction(instanceInteractionMap, dii))
            return true;

        // insert it now
//		instanceInteractionList.add(dii);
        List<InstanceInter> list = instanceInteractionMap.get(triggerNi);
        assert (list != null); // if this is the first time looking, then list should be built in findInteraction
        list.add(dii);
        return false;
    }

    /**
	 * Method to look for the instance-interaction in "dii" in the global list of instances interactions
	 * that have already been checked.  Returns the entry if it is found, NOINSTINTER if not.
	 */
	private static boolean findInteraction(Map<NodeInst,List<InstanceInter>> instanceInteractionMap, InstanceInter dii)
	{
        List<InstanceInter> theList = null;

        // new code
        theList = instanceInteractionMap.get(dii.triggerNi);
        if (theList == null) // first time
        {
            theList = new ArrayList<InstanceInter>();
            // ni1 as a key should have been added in findInteraction. Big assumption though
            instanceInteractionMap.put(dii.triggerNi, theList);
        }

        //
//        boolean newValue = false;
        for (InstanceInter thisII : theList)
		{
			if (thisII.cell1 == dii.cell1 && thisII.cell2 == dii.cell2 &&
				thisII.or1.equals(dii.or1) && thisII.or2.equals(dii.or2) &&
				thisII.dx == dii.dx && thisII.dy == dii.dy &&
                thisII.n1Parent == dii.n1Parent && thisII.n2Parent == dii.n2Parent)
//                thisII.bnd == dii.bnd)
            {
                if (dii.triggerNi == thisII.triggerNi)
                {
//                    newValue = true;
                    return true;
                }
            }
		}

//        for (InstanceInter thisII : instanceInteractionList)
//		{
//			if (thisII.cell1 == dii.cell1 && thisII.cell2 == dii.cell2 &&
//				thisII.or1.equals(dii.or1) && thisII.or2.equals(dii.or2) &&
//				thisII.dx == dii.dx && thisII.dy == dii.dy &&
//                thisII.n1Parent == dii.n1Parent && thisII.n2Parent == dii.n2Parent)
////                thisII.bnd == dii.bnd)
//            {
//                if (dii.triggerNi == thisII.triggerNi)
//                {
//                    assert(newValue);
//                    return true;
//                }
//            }
//		}
//        assert(!newValue);
		return false;
	}

    /*********************************** QUICK DRC ERROR REPORTING ***********************************/
    public static enum DRCErrorType
    {
	    // the different types of errors
        SPACINGERROR, MINWIDTHERROR, NOTCHERROR, MINSIZEERROR, BADLAYERERROR, LAYERSURROUNDERROR,
        MINAREAERROR, ENCLOSEDAREAERROR, SURROUNDERROR, FORBIDDEN, RESOLUTION, CUTERROR, SLOTSIZEERROR,
        CROOKEDERROR,
        // Different types of warnings
        ZEROLENGTHARCWARN, TECHMIXWARN
    }

    public static class ReportInfo
    {
        /** DRC preferences */                                      DRCPreferences dp;
        /** error type search */				                    DRCCheckMode errorTypeSearch;
        /** minimum output grid resolution */				        ECoord minAllowedResolution;
        /** true to ignore center cuts in large contacts. */		boolean ignoreCenterCuts;
        /** maximum area to examine (the worst spacing rule). */	double worstInteractionDistance;
        /** time stamp for numbering networks. */					int checkTimeStamp;
        /** for numbering networks. */								int checkNetNumber;
        /** total errors found in all threads. */					int totalSpacingMsgFound;
        /** for logging errors */                                   ErrorLogger errorLogger;
        /** for interactive error logging */                        boolean interactiveLogger;
        /** to cache current extra bits */                          int activeSpacingBits = 0;
        Map<Cell, Area> exclusionMap = new HashMap<Cell,Area>(); // The DRCExclusion object lists areas where Generic:DRC-Nodes exist to ignore errors.
        boolean inMemory;

        public ReportInfo(ErrorLogger eL, Technology tech, DRCPreferences dp, boolean specificGeoms)
        {
            errorLogger = eL;
            this.dp = dp;
            interactiveLogger = dp.interactiveLog;
            activeSpacingBits = DRC.getActiveBits(tech, dp);
            worstInteractionDistance = getWorstSpacingDistance(tech, -1);
            // minimim resolution different from zero if flag is on otherwise stays at zero (default)
            minAllowedResolution = dp.getResolution(tech);
            ignoreCenterCuts = dp.ignoreCenterCuts;
            inMemory = dp.storeDatesInMemory;

            errorTypeSearch = dp.errorType;
            if (specificGeoms)
            {
                errorTypeSearch = DRC.DRCCheckMode.ERROR_CHECK_CELL;
            }
        }
    }

//    private static int errorCount = 0;
    
    public static void createDRCErrorLogger(ReportInfo reportInfo,
                                            DRCErrorType errorType, String msg,
                                            Cell cell, double limit, double actual, String rule,
                                            PolyBase poly1, Geometric geom1, Layer layer1,
                                            PolyBase poly2, Geometric geom2, Layer layer2)
    {
        ErrorLogger errorLogger = reportInfo.errorLogger;

        if (errorLogger == null) return;

		// if this error is in an ignored area, don't record it
		StringBuffer DRCexclusionMsg = new StringBuffer();
        if (reportInfo.exclusionMap != null && reportInfo.exclusionMap.get(cell) != null)
		{
			// determine the bounding box of the error
			List<PolyBase> polyList = new ArrayList<PolyBase>(2);
			List<Geometric> geomList = new ArrayList<Geometric>(2);
			polyList.add(poly1); geomList.add(geom1);
			if (poly2 != null)
			{
				polyList.add(poly2);
				geomList.add(geom2);
			}
            boolean found = checkExclusionMap(reportInfo.exclusionMap, cell, polyList, geomList, DRCexclusionMsg);

            // At least one DRC exclusion that contains both
            if (found) return;
		}

		// describe the error
		Cell np1 = (geom1 != null) ? geom1.getParent() : null;
		Cell np2 = (geom2 != null) ? geom2.getParent() : null;

		// Message already logged
        boolean onlyWarning = (errorType == DRCErrorType.ZEROLENGTHARCWARN || errorType == DRCErrorType.TECHMIXWARN);
        // Until a decent algorithm is in place for detecting repeated errors, ERROR_CHECK_EXHAUSTIVE might report duplicate errros
		if ( geom2 != null && reportInfo.errorTypeSearch != DRCCheckMode.ERROR_CHECK_EXHAUSTIVE &&
            errorLogger.findMessage(cell, geom1, geom2.getParent(), geom2, !onlyWarning))
            return;

		StringBuffer errorMessage = new StringBuffer();
        DRCCheckLogging loggingType = reportInfo.dp.errorLoggingType;

        int sortKey = cell.hashCode(); // 0;
		if (errorType == DRCErrorType.SPACINGERROR || errorType == DRCErrorType.NOTCHERROR || errorType == DRCErrorType.SURROUNDERROR)
		{
			// describe spacing width error
			if (errorType == DRCErrorType.SPACINGERROR)
				errorMessage.append("Spacing");
			else if (errorType == DRCErrorType.SURROUNDERROR)
				errorMessage.append("Surround");
			else
				errorMessage.append("Notch");
			if (layer1 == layer2)
				errorMessage.append(" (layer '" + layer1.getName() + "')");
			errorMessage.append(": ");

			if (np1 != np2)
			{
				errorMessage.append(np1 + ", ");
			} else if (np1 != cell && np1 != null)
			{
				errorMessage.append("[in " + np1 + "] ");
			}

            if (geom1 != null)
                errorMessage.append(geom1);
			if (layer1 != layer2)
				errorMessage.append(", layer '" + layer1.getName() + "'");

			if (actual < 0) errorMessage.append(" OVERLAPS (BY " + TextUtils.formatDistance(limit-actual) + ") ");
			else if (actual == 0) errorMessage.append(" TOUCHES ");
			else errorMessage.append(" LESS (BY " + TextUtils.formatDistance(limit-actual) + ") THAN " + TextUtils.formatDistance(limit) +
                    ((geom2!=null)?" TO ":""));

			if (np1 != np2 && np2 != null)
				errorMessage.append(np2 + ", ");

            if (geom2 != null)
                errorMessage.append(geom2);
			if (layer1 != layer2)
				errorMessage.append(", layer '" + layer2.getName() + "'");
			if (msg != null)
				errorMessage.append("; " + msg);
		} else
		{
			// describe minimum width/size or layer error
			StringBuffer errorMessagePart2 = null;
			switch (errorType)
			{
                case CROOKEDERROR:
                    errorMessage.append("Crooked error:");
					errorMessagePart2 = new StringBuffer(" is not horizontal nor vertical");
                    break;
                case RESOLUTION:
                    errorMessage.append("Resolution error:");
					errorMessagePart2 = new StringBuffer(msg);
                    break;
                case FORBIDDEN:
                    errorMessage.append("Forbidden error:");
					errorMessagePart2 = new StringBuffer(msg);
                    break;
                case SLOTSIZEERROR:
                    errorMessage.append("Slot size error:");
					errorMessagePart2 = new StringBuffer(", layer '" + layer1.getName() + "'");
					errorMessagePart2.append(" BIGGER THAN " + TextUtils.formatDistance(limit) + " IN LENGTH (IS " + TextUtils.formatDistance(actual) + ")");
                    break;
				case MINAREAERROR:
					errorMessage.append("Minimum area error:");
					errorMessagePart2 = new StringBuffer(", layer '" + layer1.getName() + "'");
					errorMessagePart2.append(" LESS THAN " + TextUtils.formatDistance(limit) + " IN AREA (IS " + TextUtils.formatDistance(actual) + ")");
					break;
				case ENCLOSEDAREAERROR:
					errorMessage.append("Enclosed area error:");
					errorMessagePart2 = new StringBuffer(", layer '" + layer1.getName() + "'");
					errorMessagePart2.append(" LESS THAN " + TextUtils.formatDistance(limit) + " IN AREA (IS " + TextUtils.formatDistance(actual) + ")");
					break;
				case TECHMIXWARN:
					errorMessage.append("Technology mixture warning:");
					errorMessagePart2 = new StringBuffer(msg);
					break;
				case ZEROLENGTHARCWARN:
					errorMessage.append("Zero width warning:");
					errorMessagePart2 = new StringBuffer(msg); break;
				case CUTERROR:
                    errorMessage.append("Maximum cut error" + ((msg != null) ? ("(" + msg + "):") : ""));
                    errorMessagePart2 = new StringBuffer(", layer '" + layer1.getName() + "'");
                    errorMessagePart2.append(" BIGGER THAN " + TextUtils.formatDistance(limit) + " WIDE (IS " + TextUtils.formatDistance(actual) + ")");
					break;
				case MINWIDTHERROR:
                    errorMessage.append("Minimum width/height error" + ((msg != null) ? ("(" + msg + "):") : ""));
					errorMessagePart2 = new StringBuffer(", layer '" + layer1.getName() + "'");
					errorMessagePart2.append(" LESS THAN " + TextUtils.formatDistance(limit) + " WIDE (IS " + TextUtils.formatDistance(actual) + ")");
                    break;
				case MINSIZEERROR:
					errorMessage.append("Minimum size error on " + msg + ":");
					errorMessagePart2 = new StringBuffer(" LESS THAN " + TextUtils.formatDistance(limit) + " IN SIZE (IS " + TextUtils.formatDistance(actual) + ")");
					break;
				case BADLAYERERROR:
					errorMessage.append("Invalid layer ('" + layer1.getName() + "'):");
					break;
				case LAYERSURROUNDERROR:
					errorMessage.append("Layer surround error: " + msg);
					errorMessagePart2 = new StringBuffer(", layer '" + layer1.getName() + "'");
                    String layerName = (layer2 != null) ? layer2.getName() : "Select";
					errorMessagePart2.append(" NEEDS SURROUND OF LAYER '" + layerName + "' BY " + limit);
                    break;
                default:
                    assert(false); // it should not happen
            }

			errorMessage.append(" " + cell + " ");
			if (geom1 != null)
			{
				errorMessage.append(geom1);
			}
            // only when is flat -> use layer index for sorting
            if (layer1 != null && loggingType == DRCCheckLogging.DRC_LOG_FLAT) sortKey = layer1.getIndex();
            // errorMessagePart2 is at least null for BADLAYERERROR
            if (errorMessagePart2 != null) errorMessage.append(errorMessagePart2);
		}
		if (rule != null && rule.length() > 0) errorMessage.append(" [rule '" + rule + "']");
		errorMessage.append(DRCexclusionMsg);

		List<Geometric> geomList = new ArrayList<Geometric>();
		List<PolyBase> polyList = new ArrayList<PolyBase>();
		if (poly1 != null) polyList.add(poly1); //else
			if (geom1 != null) geomList.add(geom1);
		if (poly2 != null) polyList.add(poly2); //else
			if (geom2 != null) geomList.add(geom2);

        switch (loggingType)
        {
            case DRC_LOG_PER_CELL:
                errorLogger.setGroupName(sortKey, cell.getName());
                break;
            case DRC_LOG_PER_RULE:
                String theRuleName = rule;
                if (theRuleName == null)
                    theRuleName = errorType.name();
                sortKey = theRuleName.hashCode();
                if (errorLogger.getGroupName(sortKey) == null) // only if nothing was found
                    errorLogger.setGroupName(sortKey, theRuleName);
                break;
            default:
            	break;
        }

        errorLogger.logMessage(errorMessage.toString(), geomList, polyList, cell, sortKey, !onlyWarning);
        // Temporary display of errors.
        if (reportInfo.interactiveLogger)
            Job.getUserInterface().termLogging(errorLogger, false, false);
	}

    private static boolean checkExclusionMap(Map<Cell, Area> exclusionMap, Cell cell, List<PolyBase> polyList,
                                             List<Geometric> geomList, StringBuffer DRCexclusionMsg) {
        Area area = exclusionMap.get(cell);
        if (area == null) return false;

        int count = 0, i = -1;

        for (PolyBase thisPoly : polyList) {
            i++;
            if (thisPoly == null)
                continue; // MinNode case
            boolean found = area.contains(thisPoly.getBounds2D());

            if (found) count++;
            else {
                Rectangle2D rect = (geomList.get(i) != null) ? geomList.get(i).getBounds() : thisPoly.getBounds2D();
                DRCexclusionMsg.append("\n\t(DRC Exclusion in '" + cell.getName() + "' does not completely contain element (" +
                        rect.getMinX() + "," + rect.getMinY() + ") (" + rect.getMaxX() + "," + rect.getMaxY() + "))");
            }
        }
// At least one DRC exclusion that contains both
//        if (count == polyList.size())
        if (count >= 1) // at one element is inside the DRC exclusion
            return true;
        return false;
    }

    /*********************************** END DRC ERROR REPORTING ***********************************/

    private static class StoreDRCInfo
    {
        long date;
        int bits;
        StoreDRCInfo(long d, int b)
        {
            date = d;
            bits = b;
        }
    }

    /** key of Variable for last valid DRC date on a Cell. Only area rules */
//    private static final int DRC_BIT_AREA = 01; /* Min area condition */
    private static final int DRC_BIT_EXTENSION = 02;   /* Coverage DRC condition */
    private static final int DRC_BIT_ST_FOUNDRY = 04; /* For ST foundry selection */
    private static final int DRC_BIT_TSMC_FOUNDRY = 010; /* For TSMC foundry selection */
    private static final int DRC_BIT_MOSIS_FOUNDRY = 020; /* For Mosis foundry selection */
//    private static final int DRC_BIT_NONE_FOUNDRY = 040; /* For NONE foundry selection */

    public enum DRCCheckMinArea
    {
        AREA_BASIC("Simple") /*brute force algorithm*/, AREA_LOCAL("Local"), AREA_MANHATTAN("Manhattan");
        private final String name;
        DRCCheckMinArea(String s)
        {
            name = s;
        }
        public String toString() {return name;}
    }

    public enum DRCCheckLogging
    {
        DRC_LOG_FLAT("Flat")/*original strategy*/, DRC_LOG_PER_CELL("By Cell"), DRC_LOG_PER_RULE("By Rule");
        private final String name;
        DRCCheckLogging(String s)
        {
            name = s;
        }
        public String toString() {return name;}
    }

    /** Control different level of error checking */
    public enum DRCCheckMode
    {
	    ERROR_CHECK_DEFAULT (0),    /** DRC stops after first error between 2 nodes is found (default) */
        ERROR_CHECK_CELL (1),       /** DRC stops after first error per cell is found */
        ERROR_CHECK_EXHAUSTIVE (2);  /** DRC checks all combinations */
        private final int mode;   // mode
        DRCCheckMode(int m) {this.mode = m; assert m == ordinal(); }
        public int mode() { return this.mode; }
        public String toString() {return name();}
    }

    /****************************** TOOL CONTROL ******************************/

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

    /**
	 * Method to initialize the DRC tool.
	 */
	public void init()
	{
		setOn();
	}

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

	private static void includeGeometric(Geometric geom)
	{
        Cell cell = geom.getParent();

        assert !THREAD_SAFE_DRC || Job.isClientThread();
        synchronized (cellsToCheck)
		{
			Set<Geometric> cellSet = cellsToCheck.get(cell);
			if (cellSet == null)
			{
				cellSet = new HashSet<Geometric>();
				cellsToCheck.put(cell, cellSet);
			}
			cellSet.add(geom);
		}
    }

    private static void doIncrementalDRCTask(DRCPreferences dp, Cell cellToCheck)
	{
		if (!dp.incrementalDRC) return;
        assert !THREAD_SAFE_DRC || Job.isClientThread();
		if (incrementalRunning) return;

		Set<Geometric> cellSet = null;

		// get a cell to check
		synchronized (cellsToCheck)
		{
			if (cellToCheck != null)
				cellSet = cellsToCheck.get(cellToCheck);
			if (cellSet == null && cellsToCheck.size() > 0)
			{
				cellToCheck = cellsToCheck.keySet().iterator().next();
				cellSet = cellsToCheck.get(cellToCheck);
			}
			if (cellSet != null)
				cellsToCheck.remove(cellToCheck);
		}

		if (cellToCheck == null) return; // nothing to do

		// don't check if cell not in database anymore
		if (!cellToCheck.isLinked()) return;
		// Handling clipboard case (one type of hidden libraries)
		if (cellToCheck.getLibrary().isHidden()) return;

		// if there is a cell to check, do it
		if (cellSet != null)
		{
			Geometric [] objectsToCheck = new Geometric[cellSet.size()];
			int i = 0;
            for(Geometric geom : cellSet)
				objectsToCheck[i++] = geom;

            // cleaning previous errors on the cells to check now.
            for (Geometric geo : objectsToCheck)
            {
                Cell c = geo.getParent();
                List<ErrorLogger.MessageLog> getAllLogs = errorLoggerIncremental.getAllLogs(c);
                Job.updateIncrementalDRCErrors(c, null, getAllLogs);
            }
            new CheckDRCIncrementally(dp, cellToCheck, objectsToCheck, cellToCheck.getTechnology().isScaleRelevant());
		}
	}

   /**
     * Handles database changes of a Job.
     * @param oldSnapshot database snapshot before Job.
     * @param newSnapshot database snapshot after Job and constraint propagation.
     * @param undoRedo true if Job was Undo/Redo job.
     */
    public void endBatch(Snapshot oldSnapshot, Snapshot newSnapshot, boolean undoRedo)
	{
        DRCPreferences dp = new DRCPreferences(false);
        if (dp.incrementalDRC) {
            for (CellId cellId: newSnapshot.getChangedCells(oldSnapshot)) {
                Cell cell = Cell.inCurrentThread(cellId);
                if (cell == null) continue;
                CellBackup oldBackup = oldSnapshot.getCell(cellId);
                CellRevision oldRevision = oldBackup != null ? oldBackup.cellRevision : null;
                for (Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); ) {
                    NodeInst ni = it.next();
                    ImmutableNodeInst d = ni.getD();
                    if (oldRevision == null || oldRevision.getNodeById(d.nodeId) != d)
                        includeGeometric(ni);
                }
                for (Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); ) {
                    ArcInst ai = it.next();
                    ImmutableArcInst d = ai.getD();
                    if (oldRevision == null || oldRevision.getArcById(d.arcId) != d)
                        includeGeometric(ai);
                }
            }
        }
		Library curLib = Library.getCurrent();
		if (curLib == null) return;
		Cell cellToCheck = curLib.getCurCell();
		doIncrementalDRCTask(new DRC.DRCPreferences(false), cellToCheck);
	}

	/****************************** DRC INTERFACE ******************************/
    public static ErrorLogger getDRCErrorLogger(boolean layout, String extraMsg)
    {
        String title = (layout) ? "Layout " : "Schematic ";
        return ErrorLogger.newInst(title + "DRC (full)" + ((extraMsg != null) ? extraMsg:""));
    }

    public static ErrorLogger getDRCIncrementalLogger() {
        assert !THREAD_SAFE_DRC || Job.isClientThread();
        return errorLoggerIncremental;
    }

    /**
     * This method generates a DRC job from the GUI or for a bash script.
     */
    public static void checkDRCHierarchically(DRCPreferences dp, Cell cell, List<Geometric> objs, Rectangle2D bounds,
                                              GeometryHandler.GHMode mode, boolean onlyArea)
    {
        if (cell == null) return;
        boolean isLayout = true; // hierarchical check of layout by default
		if (cell.isSchematic() || cell.getTechnology() == Schematics.tech() ||
			cell.isIcon() || cell.getTechnology() == Artwork.tech())
			// hierarchical check of schematics
			isLayout = false;

        if (mode == null) mode = GeometryHandler.GHMode.ALGO_SWEEP;
        new CheckDRCHierarchically(dp, cell, isLayout, objs, bounds, mode, onlyArea);
    }

	/**
	 * Base class for checking design rules.
	 *
	 */
	public static class CheckDRCJob extends Job
	{
		Cell cell;
        DRCPreferences dp;
        boolean isLayout; // to check layout

        private static String getJobName(Cell cell) { return "Design-Rule Check " + cell; }
		protected CheckDRCJob(Cell cell, Listener tool, Priority priority, DRCPreferences dp, boolean layout)
		{
			super(getJobName(cell), tool, THREAD_SAFE_DRC ? Job.Type.SERVER_EXAMINE : Job.Type.CLIENT_EXAMINE, null, null, priority);
			this.cell = cell;
            this.dp = dp;
            this.isLayout = layout;

		}
		// never used
		public boolean doIt() { return (false);}
	}

    /**
     * Class for hierarchical DRC for layout and schematics
     */
	private static class CheckDRCHierarchically extends CheckDRCJob
	{
		Rectangle2D bounds;
        private GeometryHandler.GHMode mergeMode; // to select the merge algorithm
        private boolean onlyArea;
        private Geometric[] geoms;

        /**
         * Check bounds within cell. If bounds is null, check entire cell.
         * @param cell
         * @param layout
         * @param bounds
         */
		protected CheckDRCHierarchically(DRCPreferences dp, Cell cell, boolean layout, List<Geometric> objs,
                                         Rectangle2D bounds, GeometryHandler.GHMode mode,
                                         boolean onlyA)
		{
			super(cell, tool, Job.Priority.USER, dp, layout);
			this.bounds = bounds;
            this.mergeMode = mode;
            this.onlyArea = onlyA;
            if (objs != null)
            {
                this.geoms = new Geometric[objs.size()];
                objs.toArray(this.geoms);
            }
            startJob();
		}

		public boolean doIt()
		{
			ElapseTimer timer = ElapseTimer.createInstance();
			timer.start();
            ErrorLogger errorLog = getDRCErrorLogger(isLayout, null);
            checkNetworks(errorLog, cell, isLayout);
            if (isLayout)
                Quick.checkDesignRules(errorLog, cell, geoms, null, bounds, this, dp, mergeMode, onlyArea);
            else
                Schematic.doCheck(errorLog, cell, geoms, dp);
            errorLog.termLogging(true);
            timer.end();
            int errorCount = errorLog.getNumErrors();
            int warnCount = errorLog.getNumWarnings();
            System.out.println(errorCount + " errors and " + warnCount + " warnings found (took " + timer.toString() + ")");
            if (onlyArea)
                Job.getUserInterface().termLogging(errorLog, false, false); // otherwise the errors don't appear
            return true;
		}
	}

	private static class CheckDRCIncrementally extends CheckDRCJob
	{
		Geometric [] objectsToCheck;
        Cell cellToCheck;
        ErrorLogger errorLog;

		protected CheckDRCIncrementally(DRCPreferences dp, Cell cell, Geometric[] objectsToCheck, boolean layout)
		{
			super(cell, tool, Job.Priority.ANALYSIS, dp, layout);
			this.objectsToCheck = objectsToCheck;
            Library curLib = Library.getCurrent();
            if (curLib == null) return;
            cellToCheck = curLib.getCurCell();
            if (THREAD_SAFE_DRC) {
                assert Job.isClientThread();
                incrementalRunning  = true;
            }
			startJob();
		}

		public boolean doIt()
		{
            if (!THREAD_SAFE_DRC)
                incrementalRunning = true;
            errorLog = getDRCErrorLogger(isLayout, null);
            if (isLayout)
                Quick.checkDesignRules(errorLog, cell, objectsToCheck, null, null, null, dp,
                    GeometryHandler.GHMode.ALGO_SWEEP, false);
            else
                Schematic.doCheck(errorLog, cell, objectsToCheck, dp);
            int errorsFound = errorLog.getNumErrors();
			if (errorsFound > 0)
				System.out.println("Incremental DRC found " + errorsFound + " errors/warnings in "+ cell);
            if (THREAD_SAFE_DRC) {
                fieldVariableChanged("errorLog");
            } else {
                errorLoggerIncremental.addMessages(errorLog);
                errorLoggerIncremental.termLogging(true);
                incrementalRunning = false;
                doIncrementalDRCTask(dp, cellToCheck);
            }
			return true;
		}

        public void terminateOK() {
            if (THREAD_SAFE_DRC) {
                errorLoggerIncremental.addMessages(errorLog);
                errorLoggerIncremental.termLogging(true);
    			incrementalRunning = false;
        		doIncrementalDRCTask(dp, cellToCheck);
            }
        }
	}

	/****************************** DESIGN RULE CONTROL ******************************/

	/** The Technology whose rules are cached. */		private static Technology currentTechnology = null;

	/**
	 * Method to build a Rules object that contains the current design rules for a Technology.
	 * The DRC dialogs use this to hold the values while editing them.
	 * It also provides a cache for the design rule checker.
	 * @param tech the Technology to examine.
	 * @return a new Rules object with the design rules for the given Technology.
	 */
	public static XMLRules getRules(Technology tech)
	{
        XMLRules currentRules = tech.getCachedRules();
		if (currentRules != null && tech == currentTechnology) return currentRules;

		// constructing design rules: start with factory rules
		currentRules = tech.getFactoryDesignRules();
		if (currentRules != null)
		{
			// add overrides
            String override = ""; // TODO: propagate getDRCOverrides
//			String override = dp.getDRCOverrides(tech);
			currentRules.applyDRCOverrides(override, tech);
		}

		// remember technology whose rules are cached
		currentTechnology = tech;
        tech.setCachedRules(currentRules);
		return currentRules;
	}

	/**
	 * Method to load a full set of design rules for a Technology.
	 * @param tech the Technology to load.
	 * @param newRules a complete design rules object.
	 */
	public static void setRules(DRC.DRCPreferences dp, Technology tech, XMLRules newRules)
	{
		// get factory design rules
		XMLRules factoryRules = tech.getFactoryDesignRules();

		// determine override differences from the factory rules
		String changes = Technology.getRuleDifferences(factoryRules, newRules).toString();

        if (Job.LOCALDEBUGFLAG)
            System.out.println("This function needs attention");

		// get current overrides of factory rules
		String override = dp.getDRCOverrides(tech);

		// if the differences are the same as before, stop
		if (changes.equals(override)) return;

		// update the preference for the rule overrides
		dp.setDRCOverrides(tech, changes);

		// update variables on the technology
		tech.setRuleVariables(newRules);

		// flush the cache of rules
		if (currentTechnology == tech) currentTechnology = null;
	}

	/****************************** INDIVIDUAL DESIGN RULES ******************************/

	/**
	 * Method to find the worst spacing distance in the design rules.
	 * Finds the largest spacing rule in the Technology.
	 * @param tech the Technology to examine.
     * @param lastMetal
     * @return the largest spacing distance in the Technology. Zero if nothing found
	 */
	public static double getWorstSpacingDistance(Technology tech, int lastMetal)
	{
		XMLRules rules = getRules(tech);
		if (rules == null || rules.getNumberOfRules() == 0)
            return 0;
        MutableDouble mutableDist = new MutableDouble(0);
        boolean found = rules.getWorstSpacingDistance(lastMetal, mutableDist);
        if (!found && Job.getDebug())
            System.out.println("Not found in DRC.getWorstSpacingDistance for metal# " + lastMetal);
        return mutableDist.doubleValue();
    }

    /**
	 * Method to find the worst spacing distance in the design rules for a given list of layers.
	 * Finds the largest spacing rule in the Technology for those layers.
	 * @param tech the Technology to examine.
     * @param layers List of layers to check
     * @param mutableDist the largest spacing distance in the Technology for the given layers. Zero if nothing found
     * @return true if a valid value was found
	 */
    public static boolean getWorstSpacingDistance(Technology tech, Set<Layer> layers, MutableDouble mutableDist)
	{
        mutableDist.setValue(0);
        XMLRules rules = getRules(tech);
		if (rules == null)
            return false;
        boolean found = rules.getWorstSpacingDistance(layers, mutableDist);
        // false when cell has only pins for example
        return found;
    }

    /**
	 * Method to find the maximum design-rule distance around a layer.
	 * @param layer the Layer to examine.
	 * @return the maximum design-rule distance around the layer. -1 if nothing found.
	 */
	public static boolean getMaxSurround(Layer layer, double maxSize, MutableDouble mutableDist)
	{
		Technology tech = layer.getTechnology();
        mutableDist.setValue(-1);
        if (tech == null) return false; // case when layer is a Graphics
        XMLRules rules = getRules(tech);
		if (rules == null) return false;

        return (rules.getMaxSurround(layer, maxSize, mutableDist));
	}

    /**
     * Method to retrieve only the layer functions with rules in a node.
     * A Layer.Function.Set object is needed to pass it to tech.getShapeOfNode()
     * @param np the Primitive node to obtain.
     * @param tech the Technology that holds the Primitive node.
     * @return a Layer.Function.Set describing the rules.
     */
    public static Layer.Function.Set getOnlyLayersWithRulesFromPrimitive(NodeProto np, Technology tech)
    {
        // Cell case
        if (!(np instanceof PrimitiveNode)) return null;

        PrimitiveNode pn = (PrimitiveNode)np;
        XMLRules rules = getRules(tech);
		if (rules == null) return null;

        // In this case, no need of checking the electrical layers
        // since we want the valid layers from the DRC perspective, not the geometry yet
        Layer.Function.Set functionSet = new Layer.Function.Set();
        Technology.NodeLayer[] layers = pn.getNodeLayers();
        if (pn.getElectricalLayers() != null)
            layers = pn.getElectricalLayers();
        for (Technology.NodeLayer layer : layers)
        {
            Layer l = layer.getLayer();
            if (rules.hasLayerRules(l))
                functionSet.add(l);
        }
        return functionSet;
    }

    public static Layer.Function.Set getOnlyLayersWithRulesFromArc(ArcProto ap, Technology tech)
    {
    	XMLRules rules = getRules(tech);
		if (rules == null) return null;

        // in case the arc has valid electric layers
        Layer.Function.Set functionSet = new Layer.Function.Set();
        Technology.ArcLayer[] layers = ap.getArcLayers();
        for (Technology.ArcLayer layer : layers)
        {
            Layer l = layer.getLayer();
            if (rules.hasLayerRules(l))
                functionSet.add(l);
        }
        return functionSet;
    }

    /**
	 * Method to find the edge spacing rule between two layer.
	 * @param layer1 the first layer.
	 * @param layer2 the second layer.
	 * @return the edge rule distance between the layers.
	 * Returns null if there is no edge spacing rule.
	 */
	public static DRCTemplate getEdgeRule(Layer layer1, Layer layer2)
	{
		Technology tech = layer1.getTechnology();
		XMLRules rules = getRules(tech);
		if (rules == null) return null;

		return (rules.getEdgeRule(layer1, layer2));
	}

	/**
	 * Method to find the spacing rule between two layer.
	 * @param layer1 the first layer.
     * @param geo1
	 * @param layer2 the second layer.
     * @param geo2
	 * @param connected true to find the distance when the layers are connected.
	 * @param multiCut true to find the distance when this is part of a multicut contact.
     * @param wideS widest polygon
     * @param length length of the intersection
	 * @return the spacing rule between the layers.
	 * Returns null if there is no spacing rule.
	 */
	public static DRCTemplate getSpacingRule(Layer layer1, Geometric geo1, Layer layer2, Geometric geo2,
                                             boolean connected, int multiCut, double wideS, double length)
	{
		Technology tech = layer1.getTechnology();
		XMLRules rules = getRules(tech);
		if (rules == null) return null;
        return (rules.getSpacingRule(layer1, geo1, layer2, geo2, connected, multiCut, wideS, length));
	}

    /**
     * Method to find all possible rules of DRCRuleType type associated a layer.
     * @param layer1 the layer whose rules are desired.
     * @return a list of DRCTemplate objects associated with the layer.
     */
    public static List<DRCTemplate> getRules(Layer layer1, DRCTemplate.DRCRuleType type)
    {
        Technology tech = layer1.getTechnology();
        XMLRules rules = getRules(tech);
		if (rules == null)
            return null;
		return (rules.getRules(layer1, type));
    }

    /**
	 * Method to find the extension rule between two layer.
	 * @param layer1 the first layer.
	 * @param layer2 the second layer.
     * @param isGateExtension to decide between the rule EXTENSIONGATE or EXTENSION
	 * @return the extension rule between the layers.
	 * Returns null if there is no extension rule.
	 */
	public static DRCTemplate getExtensionRule(Layer layer1, Layer layer2, boolean isGateExtension)
	{
		Technology tech = layer1.getTechnology();
		XMLRules rules = getRules(tech);
		if (rules == null) return null;
        return (rules.getExtensionRule(layer1, layer2, isGateExtension));
	}

	/**
	 * Method to tell whether there are any design rules between two layers.
	 * @param layer1 the first Layer to check.
	 * @param layer2 the second Layer to check.
	 * @return true if there are design rules between the layers.
	 */
	public static boolean isAnySpacingRule(Layer layer1, Layer layer2)
	{
		Technology tech = layer1.getTechnology();
		XMLRules rules = getRules(tech);
		if (rules == null) return false;
        return (rules.isAnySpacingRule(layer1, layer2));
	}

	/**
	 * Method to get the minimum <type> rule for a Layer
	 * where <type> is the rule type. E.g. MinWidth or Area
	 * @param layer the Layer to examine.
	 * @param type rule type
	 * @return the minimum width rule for the layer.
	 * Returns null if there is no minimum width rule.
	 */
	public static DRCTemplate getMinValue(Layer layer, DRCTemplate.DRCRuleType type)
	{
		Technology tech = layer.getTechnology();
		if (tech == null) return null;
		XMLRules rules = getRules(tech);
		if (rules == null) return null;
        return (rules.getMinValue(layer, type));
	}

    /**
     * Determine if node represented by index in DRC mapping table is forbidden under
     * this foundry.
     */
    public static DRCTemplate isForbiddenNode(int index1, int index2, DRCTemplate.DRCRuleType type, Technology tech)
    {
    	XMLRules rules = getRules(tech);
        if (rules == null) return null;
        return isForbiddenNode(rules, index1, index2, type);
    }

    public static DRCTemplate isForbiddenNode(XMLRules rules, int index1, int index2, DRCTemplate.DRCRuleType type)
    {
        int index = index1; // In case of primitive nodes
        if (index2 != -1 )
            index = rules.getRuleIndex(index1, index2);
        else
            index += rules.getTechnology().getNumLayers(); // Node forbidden
        return (rules.isForbiddenNode(index, type));
    }

    /**
	 * Method to get the minimum size rule for a NodeProto.
	 * @param np the NodeProto to examine.
	 * @return the minimum size rule for the NodeProto.
	 * Returns null if there is no minimum size rule.
	 */
	public static PrimitiveNode.NodeSizeRule getMinSize(NodeProto np)
	{
		if (np instanceof Cell) return null;
		PrimitiveNode pnp = (PrimitiveNode)np;
        return pnp.getMinSizeRule();
	}

	/****************************** SUPPORT FOR DESIGN RULES ******************************/

    /**
     * Method to clean those cells that were marked with a valid date due to
     * changes in the DRC rules.
     * @param f
     */
    public static void cleanCellsDueToFoundryChanges(Technology tech, Foundry f, DRCPreferences dp)
    {
        // Need to clean cells using this foundry because the rules might have changed.
        System.out.println("Cleaning good DRC dates in cells using '" + f.getType().getName() +
                "' in '" + tech.getTechName() + "'");
        Set<Cell> cleanSpacingDRCDate = new HashSet<Cell>();
        Set<Cell> cleanAreaDRCDate = new HashSet<Cell>();

        int bit = f.getType().getBit();

        for (Iterator<Library> it = Library.getLibraries(); it.hasNext();)
        {
            Library lib = it.next();
            for (Iterator<Cell> itC = lib.getCells(); itC.hasNext();)
            {
                Cell cell = itC.next();
                if (cell.getTechnology() != tech) continue;

                StoreDRCInfo data = getCellGoodDRCDateAndBits(cell, true, !dp.storeDatesInMemory);

                if (data != null) // there is data
                {
                    // It was marked as valid with previous set of rules
                    if ((data.bits & bit) != 0)
                        cleanSpacingDRCDate.add(cell);
                }

                // Checking area bit
                data = getCellGoodDRCDateAndBits(cell, false, !dp.storeDatesInMemory);

                if (data != null) // there is data
                {
                    // It was marked as valid with previous set of rules
                    if ((data.bits & bit) != 0)
                        cleanAreaDRCDate.add(cell);
                }
            }
        }
        addDRCUpdate(0, null, cleanSpacingDRCDate, null, cleanAreaDRCDate, null, dp);
    }

    /**
     * Method to retrieve Date in miliseconds if a valid date for the given Key is found
     * @param cell
     * @param key
     * @return true if a valid date is found
     */
    private static boolean getDateStored(Cell cell, Variable.Key key, MutableLong date)
    {
        long lastDRCDateInMilliseconds;
        // disk version
        Long lastDRCDateAsLong = cell.getVarValue(key, Long.class); // new strategy
        if (lastDRCDateAsLong != null) {
            lastDRCDateInMilliseconds = lastDRCDateAsLong.longValue();
        } else {
            Integer[] lastDRCDateAsInts = cell.getVarValue(key, Integer[].class);
            if (lastDRCDateAsInts == null) return false;
            long lastDRCDateInSecondsHigh = lastDRCDateAsInts[0].intValue();
            long lastDRCDateInSecondsLow = lastDRCDateAsInts[1].intValue();
            lastDRCDateInMilliseconds = (lastDRCDateInSecondsHigh << 32) | (lastDRCDateInSecondsLow & 0xFFFFFFFFL);
        }
        date.setValue(lastDRCDateInMilliseconds);
        return true;
    }

    /**
     * Method to extract the corresponding DRC bits stored in disk(database) or
     * in memory for a given cell for further analysis
     * @param cell
     * @param fromDisk
     * @return temporary class containing date and bits if available. Null if nothing is found
     */
    private static StoreDRCInfo getCellGoodDRCDateAndBits(Cell cell, boolean spacingCheck, boolean fromDisk)
    {
        Map<Cell,StoreDRCInfo> storedDRCDate = storedSpacingDRCDate;
        Variable.Key dateKey = Layout.DRC_LAST_GOOD_DATE_SPACING;
        Variable.Key bitKey = Layout.DRC_LAST_GOOD_BIT_SPACING;

        if (!spacingCheck)
        {
            storedDRCDate = storedAreaDRCDate;
            dateKey = Layout.DRC_LAST_GOOD_DATE_AREA;
            bitKey = null;
        }

        StoreDRCInfo data = storedDRCDate.get(cell);
        boolean firstTime = false;

        if (data == null)
        {
            boolean validVersion = true;
            Version version = cell.getLibrary().getVersion();
            if (version != null) validVersion = version.compareTo(Version.getVersion()) >=0;
            data = new StoreDRCInfo(-1, Layout.DRC_LAST_GOOD_BIT_DEFAULT);
            storedDRCDate.put(cell, data);
            firstTime = true; // to load Variable date from disk in case of inMemory case.
            if (!validVersion)
                return null; // only the first the data is access the version is considered
        }
        if (fromDisk || (!fromDisk && firstTime))
        {
            MutableLong lastDRCDateInMilliseconds = new MutableLong(0);
            // Nothing found
            if (!getDateStored(cell, dateKey, lastDRCDateInMilliseconds))
                return null;

            int thisByte = Layout.DRC_LAST_GOOD_BIT_DEFAULT;
            if (bitKey != null)
            {
                Integer varBitsAsInt = cell.getVarValue(bitKey, Integer.class);
                if (varBitsAsInt != null) {
                    thisByte = varBitsAsInt.intValue();
                } else {
                    Byte varBitsAsByte = cell.getVarValue(bitKey, Byte.class);
                    if (varBitsAsByte != null)
                        thisByte = varBitsAsByte.byteValue();
                    else
                        System.out.println("No valid bit associated to DRC data was found as cell variable");
                }
            }
            data.bits = thisByte;
            data.date = lastDRCDateInMilliseconds.longValue();
        }
        else
        {
            data = storedDRCDate.get(cell);
        }
        return data;
    }

    /**
     * Method to check if current date is later than cell revision
     * @param cell
     * @param date
     * @return true if DRC date in cell is valid
     */
    public static boolean isCellDRCDateGood(Cell cell, Date date)
    {
        if (date != null)
        {
            Date lastChangeDate = cell.getRevisionDate();
            if (date.after(lastChangeDate)) return true;
        }
        return false;
    }

    /**
     * Method to tell the date of the last successful DRC of a given Cell.
     * @param cell the cell to query.
     * @param fromDisk
     * @return the date of the last successful DRC of that Cell.
     */
    public static Date getLastDRCDateBasedOnBits(Cell cell, boolean spacingCheck,
                                                 int activeBits, boolean fromDisk)
    {
        assert !THREAD_SAFE_DRC || Job.inServerThread();
        StoreDRCInfo data = getCellGoodDRCDateAndBits(cell, spacingCheck, fromDisk);

        // if data is null -> nothing found
        if (data == null)
            return null;

        int thisByte = data.bits;
        if (fromDisk && spacingCheck)
            assert(thisByte!=0);
        if (activeBits != Layout.DRC_LAST_GOOD_BIT_DEFAULT)
        {
//            boolean area = (thisByte & DRC_BIT_AREA) == (activeBits & DRC_BIT_AREA);
            boolean extension = (thisByte & DRC_BIT_EXTENSION) == (activeBits & DRC_BIT_EXTENSION);
            // DRC date is invalid if conditions were checked for another foundry
            boolean sameManufacturer = (thisByte & DRC_BIT_TSMC_FOUNDRY) == (activeBits & DRC_BIT_TSMC_FOUNDRY) &&
                    (thisByte & DRC_BIT_ST_FOUNDRY) == (activeBits & DRC_BIT_ST_FOUNDRY) &&
                    (thisByte & DRC_BIT_MOSIS_FOUNDRY) == (activeBits & DRC_BIT_MOSIS_FOUNDRY);
            assert(activeBits != 0);
            if (activeBits != 0 && (/*!area* || */ !extension || !sameManufacturer))
                return null;
        }

        // If in memory, date doesn't matter
        Date revisionDate = cell.getRevisionDate();
        Date lastDRCDate = new Date(data.date);
        return (lastDRCDate.after(revisionDate)) ? lastDRCDate : null;
    }

    /**
     * Method to clean any DRC date stored previously
     * @param cell the cell to clean
     */
    private static void cleanDRCDateAndBits(Cell cell, Variable.Key key)
    {
        if (key == Layout.DRC_LAST_GOOD_DATE_SPACING)
        {
            cell.delVar(Layout.DRC_LAST_GOOD_DATE_SPACING);
            cell.delVar(Layout.DRC_LAST_GOOD_BIT_SPACING);
        }
        else
            cell.delVar(Layout.DRC_LAST_GOOD_DATE_AREA);
    }

    public static String explainBits(int bits, DRCPreferences dp)
    {
        boolean on = !dp.ignoreAreaCheck; // (bits & DRC_BIT_AREA) != 0;
        String msg = "area bit ";
        msg += on ? "on" : "off";

        on = (bits & DRC_BIT_EXTENSION) != 0;
        msg += ", extension bit ";
        msg += on ? "on" : "off";

        if ((bits & DRC_BIT_TSMC_FOUNDRY) != 0)
            msg += ", TSMC bit";
        else if ((bits & DRC_BIT_ST_FOUNDRY) != 0)
            msg += ", ST bit";
        else if ((bits & DRC_BIT_MOSIS_FOUNDRY) != 0)
            msg += ", Mosis bit";
        return msg;
    }

    public static int getActiveBits(Technology tech, DRCPreferences dp)
    {
        int bits = 0;
//        if (!isIgnoreAreaChecking()) bits |= DRC_BIT_AREA;
        if (!dp.ignoreExtensionRuleChecking) bits |= DRC_BIT_EXTENSION;
        // Adding foundry to bits set
        Foundry foundry = tech.getSelectedFoundry();
        if (foundry != null)
        {
            bits |= foundry.getType().getBit();
        }
        return bits;
    }

    /**
     * Check networks rules of this Cell.
     * @param errorLog error logger
     * @param cell cell to check
     */
    private static void checkNetworks(ErrorLogger errorLog, Cell cell, boolean isLayout) {
        final int errorSortNetworks = 0;
        final int errorSortNodes = 1;
        Map<NodeProto,ArrayList<NodeInst>> strangeNodes = null;
        Map<NodeProto,ArrayList<NodeInst>> unconnectedPins = null;
        for (int i = 0, numNodes = cell.getNumNodes(); i < numNodes; i++) {
            NodeInst ni = cell.getNode(i);
            NodeProto np = ni.getProto();
            if (!cell.isIcon()) {
                if (ni.isIconOfParent() ||
                        np.getFunction() == PrimitiveNode.Function.ART && np != Generic.tech().simProbeNode ||
//                        np == Artwork.tech.pinNode ||
                        np == Generic.tech().invisiblePinNode) {
                    if (ni.hasConnections()) {
                        String msg = "Network: " + cell + " has connections on " + ni;
                        System.out.println(msg);
                        errorLog.logError(msg, ni, cell, null, errorSortNodes);
                    }
                } else if (np.getFunction().isPin() &&
                        cell.getTechnology().isLayout() && !ni.hasConnections()) {
                    if (unconnectedPins == null)
                        unconnectedPins = new HashMap<NodeProto,ArrayList<NodeInst>>();
                    ArrayList<NodeInst> pinsOfType = unconnectedPins.get(np);
                    if (pinsOfType == null) {
                        pinsOfType = new ArrayList<NodeInst>();
                        unconnectedPins.put(np, pinsOfType);
                    }
                    pinsOfType.add(ni);
                }
            }
            if (isLayout) {
                if (ni.getNameKey().isBus()) {
                    String msg = "Network: Layout " + cell + " has arrayed " + ni;
                    System.out.println(msg);
                    errorLog.logError(msg, ni, cell, null, errorSortNetworks);
                }
                boolean isSchematicNode;
                if (ni.isCellInstance()) {
                    Cell subCell = (Cell)np;
                    isSchematicNode = subCell.isIcon() || subCell.isSchematic();
                } else {
                    isSchematicNode = np == Generic.tech().universalPinNode ||np.getTechnology() == Schematics.tech();
                }
                if (isSchematicNode) {
                    if (strangeNodes == null)
                        strangeNodes = new HashMap<NodeProto,ArrayList<NodeInst>>();
                    ArrayList<NodeInst> nodesOfType = strangeNodes.get(np);
                    if (nodesOfType == null) {
                        nodesOfType = new ArrayList<NodeInst>();
                        strangeNodes.put(np, nodesOfType);
                    }
                    nodesOfType.add(ni);
                }
            }
        }
        if (unconnectedPins != null) {
            for (NodeProto np : unconnectedPins.keySet()) {
                ArrayList<NodeInst> pinsOfType = unconnectedPins.get(np);
                String msg = "Network: " + cell + " has " + pinsOfType.size() + " unconnected pins " + np;
                System.out.println(msg);
                errorLog.logMessage(msg, Collections.<Geometric>unmodifiableList(pinsOfType), cell,
                    errorSortNodes, false);
            }
        }
        if (strangeNodes != null) {
            for (NodeProto np : strangeNodes.keySet()) {
                ArrayList<NodeInst> nodesOfType = strangeNodes.get(np);
                String msg = "Network: Layout " + cell + " has " + nodesOfType.size() +
                        " " + np.describe(true) + " nodes";
                System.out.println(msg);
                boolean realError = np != Generic.tech().universalPinNode; // universal pins generate only warnings
                errorLog.logMessage(msg, Collections.<Geometric>unmodifiableList(nodesOfType), cell, errorSortNetworks, realError);
            }
        }
    }

    static boolean checkNodeSize(NodeInst ni, Cell cell, ReportInfo reportInfo)
    {
        boolean errorsFound = false;
        // check node for minimum size
        NodeProto np = ni.getProto();
        PrimitiveNode.NodeSizeRule sizeRule = getMinSize(np);
		if (sizeRule != null)
		{
            PrimitiveNodeSize npSize = ni.getNodeInstSize(null);
            List<PrimitiveNode.NodeSizeRule.NodeSizeRuleError> errorsList = sizeRule.checkSize(npSize);
//            EPoint niSize = new EPoint(ni.getXSize(), ni.getYSize());
//            EPoint niBase = new EPoint(ni.getLambdaBaseXSize(), ni.getLambdaBaseYSize());
//            List<PrimitiveNode.NodeSizeRule.NodeSizeRuleError> errorsLis = sizeRule.checkSize(niSize, niBase);

            if (errorsList != null)
            {
                for (PrimitiveNode.NodeSizeRule.NodeSizeRuleError e : errorsList)
                {
                    createDRCErrorLogger(reportInfo, DRC.DRCErrorType.MINSIZEERROR, e.message, cell,
                        e.minSize, e.actual, sizeRule.getRuleName(), null, ni, null, null, null, null);
                    errorsFound = true;
                }
            }
        }
        return errorsFound;
    }

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

    public static class DRCPreferences extends PrefPackage {
        private static final String DRC_NODE = "tool/drc";
        private static final String KEY_ERROR_LOGGING_TYPE = "ErrorLoggingType";
        private static final String KEY_ERROR_CHECK_LEVEL = "ErrorCheckLevel";
        private static final String KEY_MIN_AREA_ALGORITHM = "MinAreaAlgorithm";
        private static final String KEY_RESOLUTION = "ResolutionValueFor";
        private static final String KEY_OVERRIDES = "DRCOverridesFor";

        /** Whether DRC should DRC should be done incrementally. The default is "false". */
        @BooleanPref(node = DRC_NODE, key = "IncrementalDRCOn", factory = false)
        public boolean incrementalDRC;

        /** Whether DRC violations should be shown while nodes and arcs are dragged. The default is "true". */
        @BooleanPref(node = DRC_NODE, key = "InteractiveDRCDrag", factory = true)
        public boolean interactiveDRCDrag;

        /** Logging type in DRC. The default is "DRC_LOG_PER_CELL". */
        public DRCCheckLogging errorLoggingType;
        private static final DRCCheckLogging DEF_ERROR_LOGGING_TYPE  = DRCCheckLogging.DRC_LOG_PER_CELL;

        /** Checking level in DRC. The default is "ERROR_CHECK_DEFAULT". */
        public DRCCheckMode errorType;
        private static final DRCCheckMode DEF_ERROR_CHECK_LEVEL = DRCCheckMode.ERROR_CHECK_DEFAULT;

        /**	Whether DRC should ignore center cuts in large contacts.
         * Only the perimeter of cuts will be checked. The default is "false".
         */
        @BooleanPref(node = DRC_NODE, key = "IgnoreCenterCuts", factory = false)
        public boolean ignoreCenterCuts;

        /** Whether DRC should ignore minimum/enclosed area checking. The default is "false". */
        @BooleanPref(node = DRC_NODE, key = "IgnoreAreaCheck", factory = false)
        public boolean ignoreAreaCheck;

        /** Whether DRC should should check extension rules. The default is "false". */
        @BooleanPref(node = DRC_NODE, key = "IgnoreExtensionRuleCheck", factory = false)
        public boolean ignoreExtensionRuleChecking;

        /** Whether DRC dates should be stored in memory or not. The default is "false". */
        @BooleanPref(node = DRC_NODE, key = "StoreDatesInMemory", factory = false)
        public boolean storeDatesInMemory;

        /** Whether DRC loggers should be displayed in Explorer immediately. The default is "false". */
        @BooleanPref(node = DRC_NODE, key = "InteractiveLog", factory = false)
        public boolean interactiveLog;

        /** Which min area algorithm to use. The default is AREA_LOCAL */
        public DRCCheckMinArea minAreaAlgoOption;
        private static final DRCCheckMinArea DEF_MIN_AREA_ALGORITHM = DRCCheckMinArea.AREA_LOCAL;

        /** Whether DRC should run in a single thread or multi-threaded. The default is single-threaded. */
        @BooleanPref(node=DRC_NODE, key = "MinMultiThread", factory = false)
        public boolean isMultiThreaded;

        public Map<Technology,ECoord> resolutions = new HashMap<Technology,ECoord>();
        public Map<Technology,String> overrides = new HashMap<Technology,String>();

        public DRCPreferences(boolean factory)
        {
            super(factory);

            Preferences techPrefs = getPrefRoot().node(TECH_NODE);
            Preferences drcPrefs = getPrefRoot().node(DRC_NODE);

            errorLoggingType = DRCCheckLogging.valueOf(drcPrefs.get(KEY_ERROR_LOGGING_TYPE, DEF_ERROR_LOGGING_TYPE.name()));
            errorType = DRCCheckMode.class.getEnumConstants()[drcPrefs.getInt(KEY_ERROR_CHECK_LEVEL, DEF_ERROR_CHECK_LEVEL.ordinal())];
            minAreaAlgoOption = DRCCheckMinArea.valueOf(drcPrefs.get(KEY_MIN_AREA_ALGORITHM, DEF_MIN_AREA_ALGORITHM.name()));

            for (Iterator<Technology> it = Technology.getTechnologies(); it.hasNext(); ) {
                Technology tech = it.next();

                String keyResolution = getKey(KEY_RESOLUTION, tech.getId());
                double lambdaResolution = techPrefs.getDouble(keyResolution, tech.getFactoryResolution().getLambda()); //tech.getFactoryScaledResolution());
                ECoord resolution = ECoord.fromLambdaRoundSizeGrid(lambdaResolution);
                resolutions.put(tech, resolution);

                String keyOverrides = getKey(KEY_OVERRIDES, tech.getId());
                String override = drcPrefs.get(keyOverrides, "");
                overrides.put(tech, override);
            }
        }

        /**
         * 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);
            Preferences drcPrefs = prefRoot.node(DRC_NODE);

            if (removeDefaults && errorLoggingType == DEF_ERROR_LOGGING_TYPE)
                drcPrefs.remove(KEY_ERROR_LOGGING_TYPE);
            else
                drcPrefs.put(KEY_ERROR_LOGGING_TYPE, errorLoggingType.name());
            if (removeDefaults && errorType == DEF_ERROR_CHECK_LEVEL)
                drcPrefs.remove(KEY_ERROR_CHECK_LEVEL);
            else
                drcPrefs.putInt(KEY_ERROR_CHECK_LEVEL, errorType.ordinal());
            if (removeDefaults && minAreaAlgoOption == DEF_MIN_AREA_ALGORITHM)
                drcPrefs.remove(KEY_MIN_AREA_ALGORITHM);
            else
                drcPrefs.put(KEY_MIN_AREA_ALGORITHM, minAreaAlgoOption.name());

            for (Map.Entry<Technology,ECoord> e: resolutions.entrySet()) {
                Technology tech = e.getKey();
                String keyResolution = getKey(KEY_RESOLUTION, tech.getId());
                ECoord factoryResolution = tech.getFactoryResolution();
//                double factoryResolution = tech.getFactoryScaledResolution();
                ECoord resolution = e.getValue();
                if (removeDefaults && resolution.equals(factoryResolution))
                    techPrefs.remove(keyResolution);
                else
                    techPrefs.putDouble(keyResolution, resolution.getLambda());
            }

            for (Map.Entry<Technology,String> e: overrides.entrySet()) {
                Technology tech = e.getKey();
                String keyOverrides = getKey(KEY_OVERRIDES, tech.getId());
                String override = e.getValue();
                if (removeDefaults && override.length() == 0)
                    drcPrefs.remove(keyOverrides);
                else
                    drcPrefs.put(keyOverrides, override);
            }
        }

        /**
         * Method to set the technology resolution.
         * This is the minimum size unit that can be represented.
         * @param tech Technology
         * @param resolution new resolution value.
         */
        public void setResolution(Technology tech, ECoord resolution)
        {
            if (!resolution.isExact(ECoord.SIZE_GRID)) {
                throw new IllegalArgumentException();
            }
            resolutions.put(tech, resolution);
        }

        /**
         * Method to retrieve the resolution associated to specified.
         * This is the minimum size unit that can be represented.
         * @param tech specified technolgy
         * @return the technology's resolution value.
         */
        public ECoord getResolution(Technology tech)
        {
            return resolutions.get(tech);
        }

        /**
         * Method to get the DRC overrides from the preferences for this technology.
         * @param tech specified technolgy
         * @return a Pref describing DRC overrides for the Technology.
         */
        public String getDRCOverrides(Technology tech) {
            return overrides.get(tech);
        }

        /**
         * Method to set the DRC overrides for a this technology.
         * @param tech specified technolgy
         * @param overrides the overrides.
         */
        public void setDRCOverrides(Technology tech, String overrides) {
            if (overrides.length() >= Preferences.MAX_VALUE_LENGTH) {
                System.out.println("Warning: Design rule overrides are too complex to be saved (are " +
                    overrides.length() + " long which is more than the limit of " + Preferences.MAX_VALUE_LENGTH + ")");
            }
            this.overrides.put(tech, overrides);
        }

    }

    /****************************** END OF OPTIONS ******************************/

    /***********************************
     * Update Functions
     ***********************************/

    static void addDRCUpdate(int spacingBits,
                             Set<Cell> goodSpacingDRCDate, Set<Cell> cleanSpacingDRCDate,
                             Set<Cell> goodAreaDRCDate, Set<Cell> cleanAreaDRCDate,
                             Map<Geometric, List<Variable>> newVariables, DRCPreferences dp)
    {
        boolean goodSpace = (goodSpacingDRCDate != null && goodSpacingDRCDate.size() > 0);
        boolean cleanSpace = (cleanSpacingDRCDate != null && cleanSpacingDRCDate.size() > 0);
        boolean goodArea = (goodAreaDRCDate != null && goodAreaDRCDate.size() > 0);
        boolean cleanArea = (cleanAreaDRCDate != null && cleanAreaDRCDate.size() > 0);
        boolean vars = (newVariables != null && newVariables.size() > 0);
        if (!goodSpace && !cleanSpace && !vars && !goodArea && !cleanArea) return; // nothing to do
        new DRCUpdate(spacingBits, goodSpacingDRCDate, cleanSpacingDRCDate,
            goodAreaDRCDate, cleanAreaDRCDate, newVariables, dp);
    }

	/**
	 * Method to delete all cached date information on all cells.
     * @param startJob
     */
	public static void resetDRCDates(boolean startJob)
	{
        new DRCReset(startJob);
	}

    /***********************************
     * DRCReset class
     ***********************************/
    private static class DRCReset extends Job
    {
        DRCReset(boolean startJob)
        {
            super("Resetting DRC Dates", User.getUserTool(), Job.Type.CHANGE, null, null, Priority.USER);
            if (startJob)
                startJob();
            else
                doIt();
        }

        public boolean doIt()
        {
            storedSpacingDRCDate.clear();
            storedAreaDRCDate.clear();
            // Always clean the dates as variables.
//            if (!isDatesStoredInMemory())
            {
                for(Iterator<Library> it = Library.getLibraries(); it.hasNext(); )
                {
                    Library lib = it.next();
                    for(Iterator<Cell> cIt = lib.getCells(); cIt.hasNext(); )
                    {
                        Cell cell = cIt.next();
                        cleanDRCDateAndBits(cell, Layout.DRC_LAST_GOOD_DATE_SPACING);
                        cleanDRCDateAndBits(cell, Layout.DRC_LAST_GOOD_DATE_AREA);
                    }
                }
            }
            return true;
        }
    }

    /***********************************
     * DRCUpdate class
     ***********************************/
    /**
	 * Class to save good Layout DRC dates in a new thread or add new variables in Schematic DRC
	 */
	private static class DRCUpdate extends Job
	{
		Set<Cell> goodSpacingDRCDate;
		Set<Cell> cleanSpacingDRCDate;
        Set<Cell> goodAreaDRCDate;
		Set<Cell> cleanAreaDRCDate;
        Map<Geometric,List<Variable>> newVariables;
        int activeBits = Layout.DRC_LAST_GOOD_BIT_DEFAULT;
        DRCPreferences dp;

		public DRCUpdate(int bits,
                         Set<Cell> goodSpacingDRCD, Set<Cell> cleanSpacingDRCD,
                         Set<Cell> goodAreaDRCD, Set<Cell> cleanAreaDRCD,
                         Map<Geometric, List<Variable>> newVars,
                         DRCPreferences dp)
		{
			super("Update DRC data", tool, Type.CHANGE, null, null, Priority.USER);
            this.goodSpacingDRCDate = goodSpacingDRCD;
			this.cleanSpacingDRCDate = cleanSpacingDRCD;
            this.goodAreaDRCDate = goodAreaDRCD;
            this.cleanAreaDRCDate = cleanAreaDRCD;
            this.newVariables = newVars;
            this.activeBits = bits;
            this.dp = dp;
            // Only works for layout with in memory dates -> no need of adding the job into the queue
            if (dp.storeDatesInMemory && (newVars == null || newVars.isEmpty()))
            {
                try {doIt();} catch (Exception e) {e.printStackTrace();}
            }
            else // put it into the queue
			    startJobOnMyResult();
		}

        /**
         * Template method to set DAte and bits information for a given map.
         * @param inMemory
         */
        private static void setInformation(Map<Cell,StoreDRCInfo> storedDRCDate,
                                           Set<Cell> goodDRCDate, Set<Cell> cleanDRCDate,
                                           Variable.Key key, int bits, boolean inMemory, EditingPreferences ep)
        {
            Set<Cell> goodDRCCells = new HashSet<Cell>();
            long time = System.currentTimeMillis();

            if (goodDRCDate != null)
            {
                for (Cell cell : goodDRCDate)
                {
                    if (!cell.isLinked())
                        new JobException("Cell '" + cell + "' is invalid to clean DRC date");
                    else
                    {
                        if (inMemory)
                            storedDRCDate.put(cell, new StoreDRCInfo(time, bits));
                        else
                            goodDRCCells.add(cell);
                    }
                }
            }
            if (!goodDRCCells.isEmpty())
                Layout.setGoodDRCCells(goodDRCCells, key, bits, inMemory, ep);

            if (cleanDRCDate != null)
            {
                for (Cell cell : cleanDRCDate)
                {
                    if (!cell.isLinked())
                        new JobException("Cell '" + cell + "' is invalid to clean DRC date");
                    else
                    {
                        StoreDRCInfo data = storedDRCDate.get(cell);
                        assert(data != null);
                        data.date = -1;
                        data.bits = Layout.DRC_LAST_GOOD_BIT_DEFAULT; // I can't put null because of the version
                        if (!inMemory)
                            cleanDRCDateAndBits(cell, key);
                    }
                }
            }

        }

        public boolean doIt() throws JobException
		{
            boolean inMemory = dp.storeDatesInMemory;
            EditingPreferences ep = getEditingPreferences();

            setInformation(storedSpacingDRCDate, goodSpacingDRCDate, cleanSpacingDRCDate,
                Layout.DRC_LAST_GOOD_DATE_SPACING, activeBits, inMemory, ep);

            setInformation(storedAreaDRCDate, goodAreaDRCDate, cleanAreaDRCDate,
                Layout.DRC_LAST_GOOD_DATE_AREA, Layout.DRC_LAST_GOOD_BIT_DEFAULT, inMemory, ep);

            // Update variables in Schematics DRC
            if (newVariables != null)
            {
                assert(!inMemory);
                for (Map.Entry<Geometric,List<Variable>> e : newVariables.entrySet())
                {
                    Geometric ni = e.getKey();
                    for (Variable var : e.getValue()) {
                        if (ni.isParam(var.getKey()))
                            ((NodeInst)ni).addParameter(var);
                        else
                            ni.addVar(var);
                    }
                }
            }
			return true;
		}
	}

    /***********************************
     * JUnit interface
     ***********************************/
    public static boolean testAll()
    {
        return true;
    }
}



/**************************************************************************************************************
	 *  CellLayersContainer class
 **************************************************************************************************************/
class CellLayersContainer implements Serializable
{
    private Map<NodeProto, Set<Layer>> cellLayersMap;

    CellLayersContainer() {
        cellLayersMap = new HashMap<NodeProto, Set<Layer>>();
    }

    Set<Layer> getLayersSet(NodeProto cell) {
        return cellLayersMap.get(cell);
    }

    boolean getWorstSpacingDistance(NodeProto cell, MutableDouble mutableDist)
    {
        Set<Layer> layers = getLayersSet(cell);
        Technology tech = cell.getTechnology();
        return DRC.getWorstSpacingDistance(tech, layers, mutableDist);
    }

    void addCellLayers(Cell cell, Set<Layer> set) {
        cellLayersMap.put(cell, set);
    }

    boolean addCellLayers(Cell cell, Layer layer) {
        Set<Layer> set = cellLayersMap.get(cell);

        // first time the cell is accessed
        if (set == null) {
            set = new HashSet<Layer>(1);
            cellLayersMap.put(cell, set);
        }
        return set.add(layer);
    }
}

/**************************************************************************************************************
 *  CheckCellLayerEnumerator class
 **************************************************************************************************************/

/**
 * Class to collect which layers are available in the design
 */
class CheckCellLayerEnumerator extends HierarchyEnumerator.Visitor {
    private Map<Cell, Cell> cellsMap;
    private CellLayersContainer cellLayersCon;

    CheckCellLayerEnumerator(CellLayersContainer cellLayersC) {
        cellsMap = new HashMap<Cell, Cell>();
        cellLayersCon = cellLayersC;
    }

    /**
     * When the cell should be visited. Either it is the first time or the number of layers hasn't reached
     * the maximum
     *
     * @param cell
     * @return
     */
    private boolean skipCell(Cell cell) {
        return cellsMap.get(cell) != null;
    }

    public boolean enterCell(HierarchyEnumerator.CellInfo info) {
        Cell cell = info.getCell();
        if (skipCell(cell)) return false; // skip
        cellsMap.put(cell, cell);
        return true;
    }

    private Set<Layer> getLayersInCell(Cell cell) {
        Map<NodeProto, NodeProto> tempNodeMap = new HashMap<NodeProto, NodeProto>();
        Map<ArcProto, ArcProto> tempArcMap = new HashMap<ArcProto, ArcProto>();
        Set<Layer> set = new HashSet<Layer>();
        Technology tech = cell.getTechnology();

        // Nodes
        for (Iterator<NodeInst> it = cell.getNodes(); it.hasNext();) {
            NodeInst ni = it.next();
            NodeProto np = ni.getProto();
            if (ni.isCellInstance()) {
            	Cell c = (Cell)np;
            	if (!c.isLayout()) continue;
                Set<Layer> s = cellLayersCon.getLayersSet(np);
                assert (s != null); // it must have layers? unless is empty
                set.addAll(s);
            } else {
                if (tempNodeMap.get(np) != null)
                    continue; // done with this PrimitiveNode
                tempNodeMap.put(np, np);

                if (NodeInst.isSpecialNode(ni)) // like pins
                    continue;

                PrimitiveNode pNp = (PrimitiveNode) np;
                for (Technology.NodeLayer nLayer : pNp.getNodeLayers()) {
                    Layer layer = nLayer.getLayer();
                    if (tech.findLayer(layer.getName()) == null) continue;
                    set.add(layer);
                }
            }
        }

        // Arcs
        for (Iterator<ArcInst> it = cell.getArcs(); it.hasNext();) {
            ArcInst ai = it.next();
            ArcProto ap = ai.getProto();
            if (tempArcMap.get(ap) != null)
                continue; // done with this arc primitive
            tempArcMap.put(ap, ap);
            for (int i = 0; i < ap.getNumArcLayers(); i++) {
                Layer layer = ap.getLayer(i);
                if (tech.findLayer(layer.getName()) == null) continue;
                set.add(layer);
            }
        }
        return set;
    }

    public void exitCell(HierarchyEnumerator.CellInfo info) {
        Cell cell = info.getCell();
        Set<Layer> set = getLayersInCell(cell);
        assert (cellLayersCon.getLayersSet(cell) == null);
        cellLayersCon.addCellLayers(cell, set);
    }

    public boolean visitNodeInst(Nodable no, HierarchyEnumerator.CellInfo info) {
        NodeInst ni = no.getNodeInst();

        // true only for Cells
        return ni.isCellInstance();
    }
}

/***************** LAYER INTERACTIONS ******************/

/**************************************************************************************************************
 *  ValidationLayers class
 **************************************************************************************************************/
class ValidationLayers
{
    /* for figuring out which layers are valid for DRC */
	private Technology layersValidTech = null;
	private boolean [] layersValid;

	/* for tracking which layers interact with which nodes */
	private Technology layerInterTech = null;
	private HashMap<PrimitiveNode, boolean[]> layersInterNodes = null;
	private HashMap<ArcProto, boolean[]> layersInterArcs = null;

    private ErrorLogger errorLogger;
    private Cell topCell;
    private XMLRules currentRules;

    /**
	 * Class to determine which layers in a Technology are valid.
	 */
	ValidationLayers(ErrorLogger logger, Cell cell, XMLRules rules)
	{
        topCell = cell;
        errorLogger = logger;
        currentRules = rules;
        layersValidTech = rules.getTechnology();

        // determine the layers that are being used
        fillValidLayers();

        cacheValidLayers(layersValidTech);
        buildLayerInteractions(layersValidTech);
    }

    private void fillValidLayers()
    {
        // determine the layers that are being used
		int numLayers = layersValidTech.getNumLayers();
		layersValid = new boolean[numLayers];
		for(int i=0; i < numLayers; i++)
			layersValid[i] = false;

        for(Iterator<PrimitiveNode> it = layersValidTech.getNodes(); it.hasNext(); )
		{
			PrimitiveNode np = it.next();
			if (np.isNotUsed()) continue;
			Technology.NodeLayer [] layers = np.getNodeLayers();
            for (Technology.NodeLayer l : layers)
			{
                Layer layer = l.getLayer();
                layersValid[layer.getIndex()] = true;
			}
		}
		for(Iterator<ArcProto> it = layersValidTech.getArcs(); it.hasNext(); )
		{
			ArcProto ap = it.next();
			if (ap.isNotUsed()) continue;
			for (Iterator<Layer> lIt = ap.getLayerIterator(); lIt.hasNext(); )
			{
				Layer layer = lIt.next();
				layersValid[layer.getIndex()] = true;
			}
		}
    }
    boolean isABadLayer(Technology tech, int layerNumber)
    {
        return (tech == layersValidTech && !layersValid[layerNumber]);
    }

   /**
     * Method to determine which layers in a Technology are valid.
     */
    void cacheValidLayers(Technology tech)
    {
        if (tech == null) return;
        if (layersValidTech == tech) return;

        layersValidTech = tech;

        // determine the layers that are being used
        fillValidLayers();
    }

    /**
     * Method to build the internal data structures that tell which layers interact with
     * which primitive nodes in technology "tech".
     */
    void buildLayerInteractions(Technology tech)
    {
        Technology old = layerInterTech;
        if (layerInterTech == tech) return;

        layerInterTech = tech;
        int numLayers = tech.getNumLayers();

        // build the node table
        if (layersInterNodes != null && old != null)
        {
            errorLogger.logWarning("Switching from '" + old.getTechName() +
                "' to '" + tech.getTechName() + "' in DRC process. Check for non desired nodes in ",
                topCell, -1);
        }

        layersInterNodes = new HashMap<PrimitiveNode, boolean[]>();
        for (Iterator<PrimitiveNode> it = tech.getNodes(); it.hasNext();)
        {
            PrimitiveNode np = it.next();
            if (np.isNotUsed()) continue;
            boolean[] layersInNode = new boolean[numLayers];
            Arrays.fill(layersInNode, false);

            Technology.NodeLayer[] layers = np.getNodeLayers();
            Technology.NodeLayer[] eLayers = np.getElectricalLayers();
            if (eLayers != null) layers = eLayers;
            for (Technology.NodeLayer l : layers)
            {
                Layer layer = l.getLayer();
                if (layer.isNonElectrical())
                    continue; // such as pseudo
                for (Iterator<Layer> lIt = tech.getLayers(); lIt.hasNext();)
                {
                    Layer oLayer = lIt.next();
                    if (oLayer.isNonElectrical())
                        continue; // such as pseudo
                    if (currentRules.isAnySpacingRule(layer, oLayer))
                        layersInNode[oLayer.getIndex()] = true;
                }
            }
            layersInterNodes.put(np, layersInNode);
        }

        // build the arc table
        layersInterArcs = new HashMap<ArcProto, boolean[]>();
        for (Iterator<ArcProto> it = tech.getArcs(); it.hasNext();)
        {
            ArcProto ap = it.next();
            boolean[] layersInArc = new boolean[numLayers];
            Arrays.fill(layersInArc, false);

            for (Iterator<Layer> alIt = ap.getLayerIterator(); alIt.hasNext();)
            {
                Layer layer = alIt.next();
                for (Iterator<Layer> lIt = tech.getLayers(); lIt.hasNext();)
                {
                    Layer oLayer = lIt.next();
                    if (currentRules.isAnySpacingRule(layer, oLayer))
                        layersInArc[oLayer.getIndex()] = true;
                }
            }
            layersInterArcs.put(ap, layersInArc);
        }
    }

    /**
     * Method to determine whether layer "layer" interacts in any way with a node of type "np".
     * If not, returns FALSE.
     */
    boolean checkLayerWithNode(Layer layer, NodeProto np)
    {
        buildLayerInteractions(np.getTechnology());

        // find this node in the table
        boolean[] validLayers = layersInterNodes.get(np);
        if (validLayers == null) return false;
        return validLayers[layer.getIndex()];
    }

    /**
     * Method to determine whether layer "layer" interacts in any way with an arc of type "ap".
     * If not, returns FALSE.
     */
    boolean checkLayerWithArc(Layer layer, ArcProto ap)
    {
        buildLayerInteractions(ap.getTechnology());

        // find this node in the table
        boolean[] validLayers = layersInterArcs.get(ap);
        if (validLayers == null) return false;
        return validLayers[layer.getIndex()];
    }
}

    /**
	 * The CheckInst object is associated with every cell instance in the library.
	 * It helps determine network information on a global scale.
	 * It takes a "global-index" parameter, inherited from above (intially zero).
	 * It then computes its own index number as follows:
	 *   thisindex = global-index * multiplier + localIndex + offset
	 * This individual index is used to lookup an entry on each network in the cell
	 * (an array is stored on each network, giving its global net number).
	 */
	class CheckInst
	{
		int localIndex;
		int multiplier;
		int offset;
	}

	/**
	 * The CheckProto object is placed on every cell and is used only temporarily
	 * to number the instances.
	 */
	class CheckProto
	{
		/** time stamp for counting within a particular parent */		int timeStamp;
		/** number of instances of this cell in a particular parent */	int instanceCount;
		/** total number of instances of this cell, hierarchically */	int hierInstanceCount;
		/** number of instances of this cell in a particular parent */	int totalPerCell;
		/** true if this cell has been checked */						boolean cellChecked;
		/** true if this cell has parameters */							boolean cellParameterized;
		/** list of instances in a particular parent */					List<CheckInst> nodesInCell;
		/** netlist of this cell */										Netlist netlist;
	}

	/**
	 * The InstanceInter object records interactions between two cell instances and prevents checking
	 * them multiple times.
	 */
	class InstanceInter
	{
		/** the two cell instances being compared */	Cell cell1, cell2;
        /** orientation of cell instance 1 */           Orientation or1;
        /** orientation of cell instance 2 */           Orientation or2;
		/** distance from instance 1 to instance 2 */	double dx, dy;
        /** the two NodeInst parents */                 NodeInst n1Parent, n2Parent, triggerNi;
//        /** bounding used to select first element */    Rectangle2D bnd;
    }
