package com.sun.electric.tool.generator.layout;

import com.sun.electric.database.EditingPreferences;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Iterator;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.DRCTemplate;
import com.sun.electric.technology.DRCTemplate.DRCRuleType;
import com.sun.electric.technology.EdgeH;
import com.sun.electric.technology.EdgeV;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.PrimitivePort;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.XMLRules;
import com.sun.electric.tool.Job;
import com.sun.electric.util.math.ECoord;

/** The TechType class holds technology dependent information for the layout
 * generators. Most of the information is available from public static methods.
 * <p> The TechType class queries the appropriate Technology object to get 
 * references to prototypes commonly used by the layout generators such as the 
 * metal 1 arc proto or the metal 1 pin proto. The Tech class also holds 
 * technology dependant dimensions such as the width of a diffusion contact.
 * <p>  The TechType class serves two purposes. First, it makes it convenient
 *  to access technology dependent information. Second, it hides foundry 
 *  specific information that we're not allowed to distribute as open source 
 *  software. */
public abstract class TechType implements Serializable {
    private static class ArcPair implements Serializable {
        private static final long serialVersionUID = 0;

        private ArcProto arc1, arc2;
        public ArcPair(ArcProto a1, ArcProto a2) {arc1=a1; arc2=a2;}
        @Override
        public boolean equals(Object o) {
            if (!(o instanceof ArcPair)) return false;
            ArcPair ap = (ArcPair)o;
            if (ap.arc1==arc1 && ap.arc2==arc2) return true;
            if (ap.arc1==arc2 && ap.arc2==arc1) return true;
            return false;
        }
        @Override
        public int hashCode() {return arc1.hashCode()*arc2.hashCode();}
    }

    private class Transistor implements Serializable {
        private final PrimitiveNode pn;
        private PrimitivePort topPoly, bottomPoly, leftDiff, rightDiff;

        private Transistor(PrimitiveNode pn) {
            this.pn = pn;
            Technology.NodeLayer nmos_gate = findNodeLayer(pn, lgate);
            if (nmos_gate == null) {
                nmos_gate = findNodeLayer(pn, lp1);
            }
            boolean rotate = nmos_gate.getTopEdge().getAdder().subtract(nmos_gate.getBottomEdge().getAdder()).compareTo(
                    nmos_gate.getRightEdge().getAdder().subtract(nmos_gate.getLeftEdge().getAdder())) < 0;
            for (Iterator<PrimitivePort> it = pn.getPrimitivePorts(); it.hasNext(); ) {
                PrimitivePort pp = it.next();
                if (pp.getConnection().getFunction().isPoly()) {
                    if (rotate) {
                        if (pp.getRight().getAdder().signum() > 0) {
                            topPoly = pp;
                        }  else {
                            bottomPoly = pp;
                        }
                    } else {
                        if (pp.getBottom().getAdder().signum() > 0) {
                            topPoly = pp;
                        }  else {
                            bottomPoly = pp;
                        }
                    }
                } else if (pp.getConnection().getFunction().isDiffusion()) {
                    if (rotate) {
                        if (pp.getTop().getAdder().signum() > 0) {
                            leftDiff = pp;
                        }  else {
                            rightDiff = pp;
                        }
                    } else {
                        if (pp.getRight().getAdder().signum() > 0) {
                            rightDiff = pp;
                        }  else {
                            leftDiff = pp;
                        }
                    }

                }
            }
            assert topPoly != null && bottomPoly != null && leftDiff != null && rightDiff != null;
        }
    }

	//----------------------------- public data ----------------------------------
	private static final Variable.Key ATTR_X = Variable.newKey("ATTR_X");
	private static final Variable.Key ATTR_S = Variable.newKey("ATTR_S");
	private static final Variable.Key ATTR_SN = Variable.newKey("ATTR_SN");
	private static final Variable.Key ATTR_SP = Variable.newKey("ATTR_SP");

    private final Technology generic = Technology.findTechnology("generic");
    private final Technology technology;
    private final XMLRules drcRules;
    private final PrimitiveNode essentialBounds =
        generic.findNodeProto("Essential-Bounds");
    private final PrimitiveNode facetCenter =
        generic.findNodeProto("Facet-Center");

    private final int nbLay;
    private Layer lgate;
    private final Layer lp1;
    private final Layer[] lmets;
    private final ArcProto[] arcs;
    private final PrimitiveNode[] vias;
    private final HashMap<ArcPair,PrimitiveNode> viaMap = new HashMap<ArcPair,PrimitiveNode>();

    /** layers
     *
     * Poly and metal are considered to be routing layers.  In contrast
     * we assume we never want to route in diffusion or well.  This
     * allows us to assign a unique "height" to each of the routing
     * layers.  Layers at the same height can connect directly.  Layers
     * at adjacent heights can connect using vias.
     *
     * For now, well and diffusion don't have heights. */
    private final ArcProto pdiff, ndiff, p1, m1, m2, m3, m4, m5, m6, m7, m8, m9,
        ndiff18, pdiff18, ndiff25, pdiff25, ndiff33, pdiff33;

    /** layer pins */
    private final PrimitiveNode ndpin, pdpin, p1pin, m1pin, m2pin, m3pin,
        m4pin, m5pin, m6pin, m7pin, m8pin, m9pin;

    /** vias */
    private final PrimitiveNode nwm1, pwm1, nwm1Y, pwm1Y, ndm1, pdm1, p1m1,
        m1m2, m2m3, m3m4, m4m5, m5m6, m6m7, m7m8, m8m9;

    /** Transistors */
    private final boolean rotateTransistors;
    private final Transistor nmos, pmos, nmos18, pmos18, nmos25, pmos25,
        nmos33, pmos33;
    // nvth, pvth, nvtl, pvtl, nnat, pnat;

    /** special threshold transistor contacts */
    private final PrimitiveNode nmos18contact, pmos18contact, nmos25contact,
        pmos25contact, nmos33contact, pmos33contact;

    /** Pure layer nodes for Well and Select */
    private final PrimitiveNode nwellNode, pwellNode;

    /** Layer nodes are sometimes used to patch notches */
    private final PrimitiveNode m1Node, m2Node, m3Node, m4Node, m5Node, m6Node,
        m7Node, m8Node, m9Node, p1Node, pdNode, ndNode, pselNode, nselNode;

    /** Transistor layer nodes */
    private final PrimitiveNode od18Node, od25Node, od33Node, vthNode, vtlNode;

    //-------------------- Technology dependent dimensions  -------------------
    protected double
        // Gilda: gate length depending on foundry
        gateLength,

        // Wire offset from center of the poly contact to form a L-shape arc to gate
        offsetLShapePolyContact, offsetTShapePolyContact,

        // denote Select spacing rule
        selectSpace,

        // surround distance of select from active in transistor
        selectSurroundDiffInTrans, selectSurroundDiffAlongGateInTrans,

        // select surround over poly. PP/NP.R.1 in 90nm
        selectSurround;

    // RKao my first attempt to embed technology specific dimensions
    protected double
        wellSurroundDiff,
        gateExtendPastMOS,
        p1Width,
        p1ToP1Space,
        gateToGateSpace,
        gateToDiffContSpace,
        gateToDiffContSpaceDogBone,
        selectSurroundDiffInWellContact,	// select surround in well contacts
        selectSurroundDiffInActiveContact,	// select surround in active contacts
        m1MinArea, 							// min area rules, sq lambda
        polyContWidth,                      // width of poly of min size poly contact
        wellContWidth,                      // width of diff of min sized well contact
        diffContWidth,                      // width of diff of min sized diff contact
        diffCont_m1Width,					// width of m1 of min sized diff contact  
        diffContIncr;						// when diff cont increases by diffContIncr,
                                            // we get an additional cut

    //----------------------------- private methods  -----------------------------
    private static void error(boolean pred, String msg) {
        Job.error(pred, msg);
    }
    private Layer getMetalLayer(int l) {
        return l <= lmets.length ? lmets[l-1] : null;
    }
    private ECoord getSpacing(Layer layer1, Layer layer2) {
    	DRCTemplate rule = drcRules.getSpacingRule(layer1, null, layer2, null, false, -1, -1.0, -1.0);
    	double lambda = (rule != null) ? rule.getValue(0) : 0;
        return ECoord.fromLambdaRoundGrid(lambda);
    }
    private ArcProto getArc(int n) {
        return n>(arcs.length-1) ? null : arcs[n];
    }
    private ArcProto findArc(Layer... layers) {
        for (Iterator<ArcProto> it = technology.getArcs(); it.hasNext(); ) {
            ArcProto ap = it.next();
            boolean allLayersFound = true;
            for (Layer layer: layers) {
                if (ap.indexOf(layer) < 0) {
                    allLayersFound = false;
                }
            }
            if (allLayersFound) {
                return ap;
            }
        }
        return null;
    }
//    private double getArcLayerGridExtend(ArcProto ap, Layer layer1, Layer layer2) {
//        if (ap == null) {
//            return Double.NaN;
//        }
//        int arcIndex1 = ap.indexOf(layer1);
//        int arcIndex2 = ap.indexOf(layer2);
//        if (arcIndex1 < 0 || arcIndex2 < 0) {
//            return Double.NaN;
//        }
//        return DBMath.gridToLambda((ap.getLayerGridExtend(arcIndex1) - ap.getLayerGridExtend(arcIndex2)));
//    }
    private PrimitiveNode findPureNode(Layer layer) {
        return layer != null ? layer.getPureLayerNode() : null;
    }

    private Technology.NodeLayer findNodeLayer(PrimitiveNode pn, Layer l) {
        if (pn == null) {
            return null;
        }
        Technology.NodeLayer[] nls = pn.getNodeLayers();
        for (Technology.NodeLayer nl: nls) {
            if (nl.getLayer() == l) {
                return nl;
            }
        }
        return null;
    }

    private Technology.NodeLayer findMulticut(PrimitiveNode pn) {
        if (pn == null) {
            return null;
        }
        Technology.NodeLayer[] nls = pn.getNodeLayers();
        for (Technology.NodeLayer nl: nls) {
            if (nl.getRepresentation() == Technology.NodeLayer.MULTICUTBOX) {
                return nl;
            }
        }
        return null;
    }
    
    private static ECoord dist(EdgeH right, EdgeH left) {
        return right.getAdder().subtract(left.getAdder());
    }
    
    private static ECoord dist(EdgeV top, EdgeV bottom) {
        return top.getAdder().subtract(bottom.getAdder());
    }
    
    private static ECoord width(Technology.NodeLayer nl) {
        return dist(nl.getRightEdge(), nl.getLeftEdge());
    }

    private static ECoord height(Technology.NodeLayer nl) {
        return dist(nl.getTopEdge(), nl.getBottomEdge());
    }

    private PrimitiveNode getVia(int n) {
        return n>(vias.length-1) ? null : vias[n];
    }
    /**
     * get the PrimitiveNode of a particular type that connects to the
     * complete set of wires given
     */
    private PrimitiveNode findNode(PrimitiveNode.Function type, ArcProto... arcs) {
        for (Iterator<PrimitiveNode> it=technology.getNodes(); it.hasNext();) {
            PrimitiveNode pn = it.next();
            boolean found = true;
            if (pn.getFunction() == type) {
                for (int j=0; j<arcs.length; j++) {
                    if (pn.connectsTo(arcs[j]) == null) {
                        found = false;
                        break;
                    }
                }
                if (found) return pn;
            }
        }
        return null;
    }
    private Transistor findTransistor(PrimitiveNode.Function type) {
        PrimitiveNode pn = findNode(type);
        if (pn == null) {
            return null;
        }
        return new Transistor(pn);
    }

    private PrimitiveNode findPin(ArcProto arc) {
        return arc==null ? null : arc.findPinProto();
    }

    private void putViaMap(ArcProto arc1, ArcProto arc2, PrimitiveNode via) {
        if (arc1==null || arc2==null || via==null) return;
        ArcPair ap = new ArcPair(arc1, arc2);
        error(viaMap.containsKey(ap), "two contacts for same pair of arcs?");
        viaMap.put(ap, via);
    }

    // initialize map from pair of layers to via that connects them
    private void initViaMap() {
        putViaMap(m1, m2, m1m2);
        putViaMap(m2, m3, m2m3);
        putViaMap(m3, m4, m3m4);
        putViaMap(m4, m5, m4m5);
        putViaMap(m5, m6, m5m6);
        putViaMap(m6, m7, m6m7);
        putViaMap(m7, m8, m7m8);
        putViaMap(m8, m9, m8m9);
        putViaMap(ndiff, m1, ndm1);
        putViaMap(pdiff, m1, pdm1);
        putViaMap(p1, m1, p1m1);
    }

    protected TechType(Technology techy) {
        // This error could happen when there are XML errors while uploading the technologies.
        error((techy==null), "Null technology in TechType constructor");
        
        // I can't break this into subroutines because most data members are
        // final.
        lmets = new Layer[techy.getNumMetals()];
        nbLay = 1 + lmets.length;
        technology = techy;
        drcRules = technology.getFactoryDesignRules();

        //--------------------------- initialize layers -----------------------
        arcs = new ArcProto[nbLay];
        lp1 = techy.findLayerFromFunction(Layer.Function.POLY1, -1);
        lgate = techy.findLayerFromFunction(Layer.Function.GATE, -1);
        if (lgate == null)
        	lgate = lp1; // case for bicmos
        arcs[0] = findArc(lp1);
        for (int i=1; i<nbLay; i++) {
            Layer lm = techy.findLayerFromFunction(Layer.Function.getMetal(i), -1);
            lmets[i - 1] = lm;
            arcs[i] = findArc(lm);
            error(arcs[i]==null, "No such arc: " + Layer.Function.getMetal(i) + " in technology " + techy.getTechName());
        }
        Layer lnwell = techy.findLayerFromFunction(Layer.Function.WELLN, 0);
        Layer lpwell = techy.findLayerFromFunction(Layer.Function.WELLP, 0);
        Layer lnd = techy.findLayerFromFunction(Layer.Function.DIFFN, 0);
        if (lnd == null)
        	lnd = techy.findLayerFromFunction(Layer.Function.DIFF, 0); // at least the case of bicmos
        Layer lpd = techy.findLayerFromFunction(Layer.Function.DIFFP, 0);
        Layer lnsel = techy.findLayerFromFunction(Layer.Function.IMPLANTN, 0);
        Layer lpsel = techy.findLayerFromFunction(Layer.Function.IMPLANTP, 0);
        Layer lod18 = techy.findLayer("OD18");
        Layer lod25 = techy.findLayer("OD25");
        Layer lod33 = techy.findLayer("OD33");

        p1 = getArc(0);
        m1 = getArc(1);
        m2 = getArc(2);
        m3 = getArc(3);
        m4 = getArc(4);
        m5 = getArc(5);
        m6 = getArc(6);
        m7 = getArc(7);
        m8 = getArc(8);
        m9 = getArc(9);

        ndiff = findArc(lnd);
        pdiff = findArc(lpd);
        ndiff18 = findArc(lnd, lod18);
        pdiff18 = findArc(lpd, lod18);
        ndiff25 = findArc(lnd, lod25);
        pdiff25 = findArc(lpd, lod25);
        ndiff33 = findArc(lnd, lod33);
        pdiff33 = findArc(lpd, lod33);

        // Layer Nodes
        nwellNode = findPureNode(lnwell);
        pwellNode = findPureNode(lpwell);
        m1Node = findPureNode(getMetalLayer(1));
        m2Node = findPureNode(getMetalLayer(2));
        m3Node = findPureNode(getMetalLayer(3));
        m4Node = findPureNode(getMetalLayer(4));
        m5Node = findPureNode(getMetalLayer(5));
        m6Node = findPureNode(getMetalLayer(6));
        m7Node = findPureNode(getMetalLayer(7));
        m8Node = findPureNode(getMetalLayer(8));
        m9Node = findPureNode(getMetalLayer(9));
        p1Node = findPureNode(lp1);
        pdNode = findPureNode(lpd);
        ndNode = findPureNode(lnd);
        nselNode = findPureNode(lnsel);
        pselNode = findPureNode(lpsel);
        // transistor layers
        od18Node = findPureNode(lod18);
        od25Node = findPureNode(lod25);
        od33Node = findPureNode(lod33);
        vthNode = null;
        vtlNode = null;

        //--------------------------- initialize pins -------------------------
        pdpin = findPin(pdiff);
        ndpin = findPin(ndiff);
        p1pin = findPin(p1);
        m1pin = findPin(m1);
        m2pin = findPin(m2);
        m3pin = findPin(m3);
        m4pin = findPin(m4);
        m5pin = findPin(m5);
        m6pin = findPin(m6);
        m7pin = findPin(m7);
        m8pin = findPin(m8);
        m9pin = findPin(m9);

        //--------------------------- initialize vias -------------------------
        vias = new PrimitiveNode[nbLay - 1];
        for (int i = 0; i < nbLay - 1; i++) {
            vias[i] = findNode(PrimitiveNode.Function.CONTACT, arcs[i], arcs[i+1]);
            error(vias[i] == null, "No via for layer: " + arcs[i]);
        }

        p1m1 = getVia(0);
        m1m2 = getVia(1);
        m2m3 = getVia(2);
        m3m4 = getVia(3);
        m4m5 = getVia(4);
        m5m6 = getVia(5);
        m6m7 = getVia(6);
        m7m8 = getVia(7);
        m8m9 = getVia(8);

        ndm1 = findNode(PrimitiveNode.Function.CONTACT, ndiff, arcs[1]);
        pdm1 = findNode(PrimitiveNode.Function.CONTACT, pdiff, arcs[1]);
        nwm1 = findNode(PrimitiveNode.Function.WELL, arcs[1]);//techy.findNodeProto("Metal-1-N-Well-Con");
        pwm1 = findNode(PrimitiveNode.Function.SUBSTRATE, arcs[1]);//techy.findNodeProto("Metal-1-P-Well-Con");
        nwm1Y = techy.findNodeProto("Y-Metal-1-N-Well-Con");
        pwm1Y = techy.findNodeProto("Y-Metal-1-P-Well-Con");

        // initialize special threshold transistor contacts
        nmos18contact = findNode(PrimitiveNode.Function.CONTACT, ndiff18, arcs[1]);
        pmos18contact = findNode(PrimitiveNode.Function.CONTACT, pdiff18, arcs[1]);
        nmos25contact = findNode(PrimitiveNode.Function.CONTACT, ndiff25, arcs[1]);
        pmos25contact = findNode(PrimitiveNode.Function.CONTACT, pdiff25, arcs[1]);
        nmos33contact = findNode(PrimitiveNode.Function.CONTACT, ndiff33, arcs[1]);
        pmos33contact = findNode(PrimitiveNode.Function.CONTACT, pdiff33, arcs[1]);

        initViaMap();

        //------------------------ initialize transistors ---------------------
        nmos = findTransistor(PrimitiveNode.Function.TRANMOS);
        pmos = findTransistor(PrimitiveNode.Function.TRAPMOS);
        nmos18 = findTransistor(PrimitiveNode.Function.TRANMOSHV1);
        pmos18 = findTransistor(PrimitiveNode.Function.TRAPMOSHV1);
        nmos25 = findTransistor(PrimitiveNode.Function.TRANMOSHV2);
        pmos25 = findTransistor(PrimitiveNode.Function.TRAPMOSHV2);
        nmos33 = findTransistor(PrimitiveNode.Function.TRANMOSHV3);
        pmos33 = findTransistor(PrimitiveNode.Function.TRAPMOSHV3);

        Technology.NodeLayer nmos_gate = findNodeLayer(nmos.pn, lgate);
        if (nmos_gate == null) {
            nmos_gate = findNodeLayer(nmos.pn, lp1);
        }
        Technology.NodeLayer nmos_nd = findNodeLayer(nmos.pn, lnd);
        Technology.NodeLayer nmos_nsel = findNodeLayer(nmos.pn, lnsel);
        rotateTransistors = height(nmos_gate).compareTo(width(nmos_gate)) < 0;
        ECoord diffExtendAlongMos;
        if (rotateTransistors) {
            p1Width = gateLength = height(nmos_gate).getLambda();
            gateExtendPastMOS = dist(nmos_gate.getRightEdge(), nmos_nd.getRightEdge()).getLambda();
            diffExtendAlongMos = dist(nmos_nd.getTopEdge(), nmos_gate.getTopEdge());
            selectSurroundDiffInTrans = dist(nmos_nsel.getTopEdge(), nmos_nd.getTopEdge()).getLambda();
            selectSurroundDiffAlongGateInTrans = dist(nmos_nsel.getRightEdge(), nmos_nd.getRightEdge()).getLambda();
        } else {
            p1Width = gateLength = width(nmos_gate).getLambda();
            gateExtendPastMOS = dist(nmos_gate.getTopEdge(), nmos_nd.getTopEdge()).getLambda();
            diffExtendAlongMos = dist(nmos_nd.getRightEdge(), nmos_gate.getRightEdge());
            selectSurroundDiffInTrans = dist(nmos_nsel.getRightEdge(), nmos_nd.getRightEdge()).getLambda();
            selectSurroundDiffAlongGateInTrans = dist(nmos_nsel.getTopEdge(), nmos_nd.getTopEdge()).getLambda();
        }

        wellContWidth = Double.NaN;
        selectSurroundDiffInWellContact = Double.NaN;
        wellSurroundDiff = Double.NaN;
        if (nwm1Y == null) {
            Technology.NodeLayer nwm1_nwell = findNodeLayer(nwm1, lnwell);
            Technology.NodeLayer nwm1_nsel = findNodeLayer(nwm1, lnsel);
            Technology.NodeLayer nwm1_nd = findNodeLayer(nwm1, lnd);
            if (nwm1_nwell != null && nwm1_nd != null) {
                wellContWidth = height(nwm1_nd).getLambda();
                selectSurroundDiffInWellContact = dist(nwm1_nsel.getTopEdge(), nwm1_nd.getTopEdge()).getLambda();
                wellSurroundDiff = dist(nwm1_nwell.getTopEdge(), nwm1_nd.getTopEdge()).getLambda();
            }
        }

        p1ToP1Space = getSpacing(lp1, lp1).getLambda();
        gateToGateSpace = getSpacing(lgate, lgate).max(diffExtendAlongMos).getLambda();
        selectSpace = getSpacing(lnsel, lnsel).getLambda();
        DRCTemplate m1MinAreaRule = drcRules.getMinValue(lmets[0], DRCRuleType.MINAREA);
        m1MinArea = m1MinAreaRule != null ? m1MinAreaRule.getValue(0) : 0.0;

        Technology.NodeLayer ndm1_nd = findNodeLayer(ndm1, lnd);
        Technology.NodeLayer ndm1_nsel = findNodeLayer(ndm1, lnsel);
        Technology.NodeLayer ndm1_m1 = findNodeLayer(ndm1, lmets[0]);
        Technology.NodeLayer ndm1_multicut = findMulticut(ndm1);
        diffContWidth = width(ndm1_nd).getLambda();
        diffCont_m1Width = width(ndm1_m1).getLambda();
        selectSurroundDiffInActiveContact = dist(ndm1_nsel.getRightEdge(), ndm1_nd.getRightEdge()).getLambda();
        diffContIncr = ndm1_multicut.getMulticutSizeX().add(ndm1_multicut.getMulticutSep1D()).getLambda();
        if (drcRules.isAnySpacingRule(ndm1_multicut.getLayer(), lgate)) {
            gateToDiffContSpace = gateToDiffContSpaceDogBone = getSpacing(ndm1_multicut.getLayer(), lgate).subtract(
                width(ndm1_nd).subtract(ndm1_multicut.getMulticutSizeX()).multiply(0.5)).getLambda();
        } else {
            gateToDiffContSpace = gateToDiffContSpaceDogBone = 0;
        }

        Technology.NodeLayer p1m1_p1 = findNodeLayer(p1m1, lp1);
        polyContWidth = width(p1m1_p1).getLambda();
        offsetLShapePolyContact = p1m1_p1.getRightEdge().getAdder().subtract(p1.getLayerExtend(lp1)).getLambda();
        offsetTShapePolyContact = p1m1_p1.getRightEdge().getAdder().add(p1.getLayerExtend(lp1)).getLambda();

        selectSurround = Double.NaN;
    }

    //---------------------------- public classes -----------------------------
    /** Hide the differences between technologies. A MosInst's gate is always
     * vertical. */
    public static class MosInst {
        private final NodeInst mos;
        private final String leftDiff, rightDiff, topPoly, botPoly;
        private static void error(boolean pred, String msg) {
            Job.error(pred, msg);
        }
        private MosInst(char np, double x, double y, double width, double length,
                        TechType tech, EditingPreferences ep, Cell parent) {
            Transistor t = np=='n' ? tech.nmos : tech.pmos;
            this.leftDiff = t.leftDiff.getName();
            this.rightDiff = t.rightDiff.getName();
            this.topPoly = t.topPoly.getName();
            this.botPoly = t.bottomPoly.getName();
            if (tech.rotateTransistors) {
                mos = LayoutLib.newNodeInst(t.pn, ep, x, y, width, length, 90, parent);
            } else {
                mos = LayoutLib.newNodeInst(t.pn, ep, x, y, length, width, 0, parent);
            }
        }
        private PortInst getPort(String portNm) {
            PortInst pi = mos.findPortInst(portNm);
            error(pi==null, "MosInst can't find port!");
            return pi;
        }
        public PortInst leftDiff() {return getPort(leftDiff);}
        public PortInst rightDiff() {return getPort(rightDiff);}
        public PortInst topPoly() {return getPort(topPoly);}
        public PortInst botPoly() {return getPort(botPoly);}
    }

    //------------------------------ public data ------------------------------

    public static TechType getTechType(Technology technology) {
        if (technology == Technology.getMocmosTechnology()) {
            return getMOCMOS();
        } else if (technology == Technology.getTSMC180Technology()) {
            return getTSMC180();
        } else if (technology == Technology.getCMOS90Technology()) {
            return getCMOS90();
        } else {
            return new TechTypeWizard(technology);
//            System.out.println("Invalid TechTypeEnum");
//            throw new AssertionError();
        }
    }

    private static TechType techTypeMoCMOS;

    public static TechType getMOCMOS() {
        if (techTypeMoCMOS == null) {
            techTypeMoCMOS = new TechTypeMoCMOS();
        }
        return techTypeMoCMOS;
    }

    private static TechType techTypeTSMC180;

    public static TechType getTSMC180() {
        if (techTypeTSMC180 == null) {
            try {
                Class tsmc180Class = Class.forName("com.sun.electric.plugins.tsmc.TechTypeTSMC180");
                Constructor<TechType> techConstr = tsmc180Class.getConstructor();
                techTypeTSMC180 = techConstr.newInstance();
            } catch (Exception e) {
                assert (false); // runtime error
            }
        }
        return techTypeTSMC180;
    }

    private static TechType techTypeCMOS90;

    public static TechType getCMOS90() {
        if (techTypeCMOS90 == null) {
            try {
                Class cmos90Class = Class.forName("com.sun.electric.plugins.tsmc.TechTypeCMOS90");
                Constructor<TechType> techConstr = cmos90Class.getConstructor();
                techTypeCMOS90 = techConstr.newInstance();
    //			java.lang.reflect.Field techField = cmos90Class.getDeclaredField("CMOS90");
    //			cmos90 = (TechType) techField.get(null);
            } catch (Exception e) {
                assert (false); // runtime error
            }
        }
        return techTypeCMOS90;
    }

    //----------------------------- public methods ----------------------------

    public int getNumMetals() { return lmets.length; }
    public Technology getTechnology() {return technology;}

    /** layers */
    public ArcProto pdiff() {return pdiff;}
    public ArcProto ndiff() {return ndiff;}
    public ArcProto p1() {return p1;}
    public ArcProto m1() {return m1;}
    public ArcProto m2() {return m2;}
    public ArcProto m3() {return m3;}
    public ArcProto m4() {return m4;}
    public ArcProto m5() {return m5;}
    public ArcProto m6() {return m6;}
    public ArcProto m7() {return m7;}
    public ArcProto m8() {return m8;}
    public ArcProto m9() {return m9;}
    public ArcProto ndiff18() {return ndiff18;}
    public ArcProto pdiff18() {return pdiff18;}
    public ArcProto ndiff25() {return ndiff25;}
    public ArcProto pdiff25() {return pdiff25;}
    public ArcProto ndiff33() {return ndiff33;}
    public ArcProto pdiff33() {return pdiff33;}

    /** pins */
    public PrimitiveNode ndpin() {return ndpin;}
    public PrimitiveNode pdpin() {return pdpin;}
    public PrimitiveNode p1pin() {return p1pin;}
    public PrimitiveNode m1pin() {return m1pin;}
    public PrimitiveNode m2pin() {return m2pin;}
    public PrimitiveNode m3pin() {return m3pin;}
    public PrimitiveNode m4pin() {return m4pin;}
    public PrimitiveNode m5pin() {return m5pin;}
    public PrimitiveNode m6pin() {return m6pin;}
    public PrimitiveNode m7pin() {return m7pin;}
    public PrimitiveNode m8pin() {return m8pin;}
    public PrimitiveNode m9pin() {return m9pin;}

    /** vias */
    public PrimitiveNode nwm1() {return nwm1;}
    public PrimitiveNode pwm1() {return pwm1;}
    public PrimitiveNode nwm1Y() {return nwm1Y;}
    public PrimitiveNode pwm1Y() {return pwm1Y;}
    public PrimitiveNode ndm1() {return ndm1;}
    public PrimitiveNode pdm1() {return pdm1;}
    public PrimitiveNode p1m1() {return p1m1;}
    public PrimitiveNode m1m2() {return m1m2;}
    public PrimitiveNode m2m3() {return m2m3;}
    public PrimitiveNode m3m4() {return m3m4;}
    public PrimitiveNode m4m5() {return m4m5;}
    public PrimitiveNode m5m6() {return m5m6;}
    public PrimitiveNode m6m7() {return m6m7;}
    public PrimitiveNode m7m8() {return m7m8;}
    public PrimitiveNode m8m9() {return m8m9;}

    /** Transistors */
    public PrimitiveNode nmos() {return nmos != null ? nmos.pn : null;}
    public PrimitiveNode pmos() {return pmos != null ? pmos.pn : null;}
    public PrimitiveNode nmos18() {return nmos18 != null ? nmos18.pn : null;}
    public PrimitiveNode pmos18() {return pmos18 != null ? pmos18.pn : null;}
    public PrimitiveNode nmos25() {return nmos25 != null ? nmos25.pn : null;}
    public PrimitiveNode pmos25() {return pmos25 != null ? pmos25.pn : null;}
    public PrimitiveNode nmos33() {return nmos33 != null ? nmos33.pn : null;}
    public PrimitiveNode pmos33() {return pmos33 != null ? pmos33.pn : null;}

    /** special threshold transistor contacts */
    public PrimitiveNode nmos18contact() {return nmos18contact;}
    public PrimitiveNode pmos18contact() {return pmos18contact;}
    public PrimitiveNode nmos25contact() {return nmos25contact;}
    public PrimitiveNode pmos25contact() {return pmos25contact;}
    public PrimitiveNode nmos33contact() {return nmos33contact;}
    public PrimitiveNode pmos33contact() {return pmos33contact;}

    /** Well */
    public PrimitiveNode nwell() {return nwellNode;}
    public PrimitiveNode pwell() {return pwellNode;}

    /** Layer nodes are sometimes used to patch notches */
    public PrimitiveNode m1Node() {return m1Node;}
    public PrimitiveNode m2Node() {return m2Node;}
    public PrimitiveNode m3Node() {return m3Node;}
    public PrimitiveNode m4Node() {return m4Node;}
    public PrimitiveNode m5Node() {return m5Node;}
    public PrimitiveNode m6Node() {return m6Node;}
    public PrimitiveNode m7Node() {return m7Node;}
    public PrimitiveNode m8Node() {return m8Node;}
    public PrimitiveNode m9Node() {return m9Node;}
    public PrimitiveNode p1Node() {return p1Node;}
    public PrimitiveNode pdNode() {return pdNode;}
    public PrimitiveNode ndNode() {return ndNode;}
    public PrimitiveNode pselNode() {return pselNode;}
    public PrimitiveNode nselNode() {return nselNode;}

    /** Transistor layer nodes */
    public PrimitiveNode od18() {return od18Node;}
    public PrimitiveNode od25() {return od25Node;}
    public PrimitiveNode od33() {return od33Node;}
    public PrimitiveNode vth() {return vthNode;}
    public PrimitiveNode vtl() {return vtlNode;}

    /** Essential-Bounds */
    public PrimitiveNode essentialBounds() {return essentialBounds;}

    /** Facet-Center */
    public PrimitiveNode facetCenter() {return facetCenter;}

    public PrimitiveNode getViaFor(ArcProto a1, ArcProto a2) {
        return viaMap.get(new ArcPair(a1, a2));
    }

    public int layerHeight(ArcProto p) {
        for (int i = 0; i < nbLay; i++) {
            if (arcs[i] == p)
                return i;
        }
        error(true, "Can't find layer: " + p);
        return -1;
    }

    public ArcProto closestLayer(PortProto port, ArcProto layer) {
        int h = layerHeight(layer);
        for (int dist = 0; dist < nbLay; dist++) {
            int lookUp = h + dist;
            int lookDn = h - dist;
            if (lookUp < nbLay) {
                ArcProto lay = layerAtHeight(lookUp);
                if (port.connectsTo(lay))
                    return lay;
            }
            if (lookDn >= 0) {
                ArcProto lay = layerAtHeight(lookDn);
                if (port.connectsTo(lay))
                    return lay;
            }
        }
        error(true, "port can't connect to any layer?!!");
        return null;
    }
    
    public ArcProto highestLayer(PortProto port) {
    	for (int h=arcs.length-1; h>=0; h--) {
    		if (port.connectsTo(arcs[h])) return arcs[h];
    	}
    	error(true, "port can't connect to any layer?!!");
    	return null;
    }
    
    public ArcProto layerAtHeight(int layHeight) {return arcs[layHeight];	}

    public PrimitiveNode viaAbove(int layHeight) {return vias[layHeight];}

    public PrimitiveNode viaBelow(int layHeight) {return vias[layHeight - 1];}

    /** round to avoid MOCMOS CIF resolution errors */
    public double roundToGrid(double x) {
        return x;
    }

	public MosInst newNmosInst(double x, double y,
							   double w, double l, Cell parent, EditingPreferences ep) {
		return new MosInst('n', x, y, w, l, this, ep, parent);
	}
	public MosInst newPmosInst(double x, double y,
							   double w, double l, Cell parent, EditingPreferences ep) {
		return new MosInst('p', x, y, w, l, this, ep, parent);
	}
    public abstract String name();

    public abstract double reservedToLambda(int layer, double nbTracks);
    
	/** @return min width of Well */
	public double getWellWidth() {return nwm1.getMinSizeRule().getWidth();}
	/** @return amount that well surrounds diffusion */
	public double getWellSurroundDiffInWellContact() {return wellSurroundDiff;}
	/** @return MOS edge to gate edge */
    public double getGateExtendPastMOS() {return gateExtendPastMOS;}
    /** @return min width of polysilicon 1 */
    public double getP1Width() {return p1Width;}
    /** @return min spacing between polysilicon 1 */
    public double getP1ToP1Space() {return p1ToP1Space;}
    /** @return min spacing between gates of series transistors */ 
    public double getGateToGateSpace() {return gateToGateSpace;}
    /** @return min spacing between MOS gate and diffusion edge of diff contact */
    public double getGateToDiffContSpace() {return gateToDiffContSpace;}
    /** @return min spacing between MOS gate and diffusion edge of diff contact
     * when the diffusion width is larger than the gate width */
    public double getGateToDiffContSpaceDogBone() {return gateToDiffContSpaceDogBone;}
    /** @return min width of diffusion surrounding well contact */
    public double getWellContWidth() { return wellContWidth; }
    /** @return min width of diffusion surrounding diff contact */
    public double getDiffContWidth() { return diffContWidth; }
//        SizeOffset so = ndm1().getProtoSizeOffset();
//        return (ndm1().getMinSizeRule().getHeight() - so.getHighYOffset() - so.getLowYOffset());
//    }
    /** @return min width of poly contact */
    public double getP1M1Width() { return polyContWidth; }
//        SizeOffset so = p1m1().getProtoSizeOffset();
//        return (p1m1().getMinSizeRule().getHeight() - so.getHighYOffset() - so.getLowYOffset());
//    }
    /** @return gate length that depends on foundry */
    public double getGateLength() {return gateLength;}
    /** @return amount that select surrounds diffusion in well? */
    public double selectSurroundDiffInWellContact() {return selectSurroundDiffInWellContact;}
    /** @return amount that select surrounds diffusion in well? */
    public double selectSurroundDiffInDiffContact() {return selectSurroundDiffInActiveContact;}
    /** @return amount that Select surrounds MOS, along gate width dimension */
    public double selectSurroundDiffAlongGateInTrans() {return selectSurroundDiffAlongGateInTrans;}
    /** @return y offset of poly arc connecting poly contact and gate in a L-Shape case */
    public double getPolyLShapeOffset() {return offsetLShapePolyContact;}
    /** @return y offset of poly arc connecting poly contact and gate in a T-Shape case */
    public double getPolyTShapeOffset() {return offsetTShapePolyContact;}
    /** @return select spacing rule */
    public double getSelectSpacingRule() {return selectSpace;}
    /** @return select surround active in transistors but not along the gate */
    public double getSelectSurroundDiffInTrans() {return selectSurroundDiffInTrans;}
    /** @return select surround over poly */
    public double getSelectSurroundOverPoly() {return selectSurround;}
    /** @return minimum metal1 area (sq lambda) */
    public double getM1MinArea() {return m1MinArea;}
    /** @return width of metal-1 in min sized diffusion contact */
    public double getDiffCont_m1Width() {return diffCont_m1Width;}
    /** @return amount diffusion contact grows to accomodate an one additional contact cut */ 
    public double getDiffContIncr() {return diffContIncr;}
    
    public Variable.Key getAttrX() {return ATTR_X;}
    public Variable.Key getAttrS() {return ATTR_S;}
    public Variable.Key getAttrSP() {return ATTR_SP;}
    public Variable.Key getAttrSN() {return ATTR_SN;}


}
