/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

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

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.GeometryHandler;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.prototype.PortCharacteristic;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.extract.LayerCoverageTool;
import com.sun.electric.tool.generator.layout.FoldedMos;
import com.sun.electric.tool.generator.layout.FoldedNmos;
import com.sun.electric.tool.generator.layout.FoldedPmos;
import com.sun.electric.tool.generator.layout.FoldsAndWidth;
import com.sun.electric.tool.generator.layout.LayoutLib;
import com.sun.electric.tool.generator.layout.StdCellParams;
import com.sun.electric.tool.generator.layout.TechType;
import com.sun.electric.tool.generator.layout.TrackRouter;
import com.sun.electric.tool.generator.layout.TrackRouterH;
import com.sun.electric.util.math.Orientation;

import java.util.Iterator;

/**
 *
 * @author dn146861
 */
public class Inv350 {

    private static final double inY = 0.0;

    private static final double p1_nd_sp = 0;
    private static final double p1m1_met_wid = 0.8;
    private static final double m1m2_m1_wid = 0.9;

    private static final double totWidN = 1.1;
    private static final double totWidP = 1.7;
    private static final String nm = "inv{lay}";

    private static final double wellOverhangDiff = 1.2;
    private static final double m1_m1_sp = 0.45;
    private static final double m1_wid = 0.5;
    private static final double pd_p1_sp = 0.4;
    private static final double nd_p1_sp = 0.4;

	private static void error(boolean pred, String msg) {
		Job.error(pred, msg);
	}

    public static Cell makePart(double sz, String threshold, StdCellParams stdCell) {
		TechType tech = stdCell.getTechType();
        EditingPreferences ep = stdCell.getEditingPreferences();
		sz = stdCell.roundSize(sz);
		error(!threshold.equals("") && !threshold.equals("LT")
			  && !threshold.equals("HT"),
			  "Inv: threshold not \"\", \"LT\", or \"HT\": " + threshold);
		String nm = "inv"+threshold
		            +(stdCell.getDoubleStrapGate() ? "_strap" : "");

		sz = stdCell.checkMinStrength(sz, threshold.equals("LT") ? .5 : 1, nm);

        double p1m1_wid = tech.getP1Width();
        double p1_p1_sp = tech.getP1ToP1Space();

        double exp_wid = 0.9;//4
        double in_wid = 0.8;//3
        double outHiY = inY + (in_wid/2 + m1_m1_sp + exp_wid/2);
        double outLoY = inY - (in_wid/2 + m1_m1_sp + exp_wid/2);

        // Space needed at the top of the PMOS well and bottom of MOS well.
        // We need more space if we're double strapping poly.
        double outsideSpace = stdCell.getDoubleStrapGate() ? (
          p1_nd_sp + p1m1_wid + p1_p1_sp/2
        ) : (
          wellOverhangDiff
        );

        double tranHiInnerY = wellOverhangDiff;
        double tranLoInnerY = -wellOverhangDiff;
//        double tranHiInnerY = Math.max(wellOverhangDiff, outHiY + (exp_wid/2 + m1_m1_sp));
//        double tranLoInnerY = Math.min(-wellOverhangDiff, outLoY - (exp_wid/2 + m1_m1_sp));
        double tranHiOuterY = Math.min(stdCell.getCellTop() - outsideSpace, stdCell.getVddY() - stdCell.getVddWidth()/2 - m1_m1_sp);
        double tranLoOuterY = Math.max(stdCell.getCellBot() + outsideSpace, stdCell.getGndY() + stdCell.getGndWidth()/2 + m1_m1_sp);

        // find number of folds and width of PMOS
        double spaceAvail = tranHiOuterY - tranHiInnerY;
		double lamPerSz = threshold.equals("HT") ? 3.4 : 1.7;
		double totWidP = sz * lamPerSz;
        FoldsAndWidth fwP = stdCell.calcFoldsAndWidth(spaceAvail, totWidP, 1);

        // find number of folds and width of NMOS
        spaceAvail = tranLoInnerY - tranLoOuterY;
		lamPerSz = threshold.equals("LT") ? 2.2 : 1.1;
		double totWidN = sz * lamPerSz;
        FoldsAndWidth fwN = stdCell.calcFoldsAndWidth(spaceAvail, totWidN, 1);

		// create Inverter Part
		Cell inv = stdCell.findPart(nm, sz);
		if (inv!=null)  return inv;
		inv = stdCell.newPart(nm, sz);

        // leave vertical m1 track for in
        double inX = m1_m1_sp/2 + m1m2_m1_wid/2;
        Export eIn = LayoutLib.newExport(inv, "in", ep, PortCharacteristic.IN, tech.m1(),
                            exp_wid, inX, inY);

        double mosX = tech.getDiffContWidth()/2 + tech.getSelectSurroundDiffInTrans() + tech.getSelectSpacingRule()/2;
        double nmosY = tranLoInnerY - fwN.physWid / 2;
        FoldedMos nmos = new FoldedNmos(mosX, nmosY, fwN.nbFolds, 1,
                                        fwN.gateWid, null, 'C', inv, tech, ep);
        double pmosY = tranHiInnerY + fwP.physWid / 2;
        FoldedMos pmos = new FoldedPmos(mosX, pmosY, fwP.nbFolds, 1,
                                        fwP.gateWid, null, 'C', inv, tech, ep);

        // inverter output:  m1_wid/2 + m1_m1_sp + m1_wid/2
        double rightestGateX = Math.max(
                nmos.getGate(nmos.nbGates() - 1, 'T').getCenter().getX(),
                pmos.getGate(pmos.nbGates() - 1, 'B').getCenter().getX());
        double outX = rightestGateX + p1m1_met_wid/2 + m1_m1_sp + exp_wid/2;
//        double outX = StdCellParams.getRightDiffX(nmos, pmos) + m1_wid/2 + m1_m1_sp + m1_wid/2;
        double outY = 0;
        Export eOut = LayoutLib.newExport(inv, "out", ep, PortCharacteristic.OUT,
                            tech.m1(), exp_wid, outX, outY);

        // create vdd and gnd exports and connect to MOS source/drains
        stdCell.wireVddGnd(nmos, StdCellParams.EVEN, inv);
        stdCell.wireVddGnd(pmos, StdCellParams.EVEN, inv);

        // Connect up input. Do PMOS gates first because PMOS gate spacing
        // is a valid spacing for p1m1 vias even for small strengths.
        TrackRouter in = new TrackRouterH(tech.p1(), tech.getP1M1Width(), inY, tech, ep, inv);
        in.connect(eIn);
        for (int i=0; i<pmos.nbGates(); i++)  in.connect(pmos.getGate(i, 'B'));
        for (int i=0; i<nmos.nbGates(); i++)  in.connect(nmos.getGate(i, 'T'));

//        if (stdCell.getDoubleStrapGate()) {
//            // Connect gates using metal1 along bottom of cell
//            double gndBot = stdCell.getGndY() - stdCell.getGndWidth() / 2;
//            double inLoFromGnd = gndBot -m1_m1_sp -m1_wid/2;
//            double nmosBot = nmosY - fwN.physWid / 2;
//            double inLoFromMos = nmosBot -nd_p1_sp - p1m1_wid/2;
//            double inLoY = Math.min(inLoFromGnd, inLoFromMos);
//
//            TrackRouter inLo = new TrackRouterH(tech.m1(), 3, inLoY, tech, inv);
//            inLo.connect(eIn);
//            for (int i = 0; i < nmos.nbGates(); i++) {
//                inLo.connect(nmos.getGate(i, 'B'));
//            }
//
//            // Connect gates using metal1 along top of cell
//            double vddTop = stdCell.getVddY() + stdCell.getVddWidth() / 2;
//            double inHiFromVdd = vddTop +m1_m1_sp + m1_wid/2;
//            double pmosTop = pmosY + fwP.physWid / 2;
//            double inHiFromMos = pmosTop +pd_p1_sp + p1m1_wid/2;
//            double inHiY = Math.max(inHiFromVdd, inHiFromMos);
//
//            TrackRouter inHi = new TrackRouterH(tech.m1(), 3, inHiY, tech, inv);
//            inHi.connect(eIn);
//            for (int i=0; i<pmos.nbGates(); i++) {
//                inHi.connect(pmos.getGate(i, 'T'));
//            }
//        }

        // connect up output
        TrackRouter outH = new TrackRouterH(tech.m1(), exp_wid, outY, tech, ep, inv);
        TrackRouter outHi = outH;
//        TrackRouter outHi = new TrackRouterH(tech.m1(), exp_wid, outHiY, tech, inv);
//        outHi.setShareableViaDist(0);
        outHi.connect(eOut);
        for (int i=1; i<pmos.nbSrcDrns(); i += 2) {
            outHi.connect(pmos.getSrcDrn(i));
        }

        TrackRouter outLo = outH;
//        TrackRouter outLo = new TrackRouterH(tech.m1(), exp_wid, outLoY, tech, inv);
        outLo.connect(eOut);
        for (int i = 1; i < nmos.nbSrcDrns(); i += 2) {
            outLo.connect(nmos.getSrcDrn(i));
        }

        // add wells
        double wellMinX = 0;
        double wellMaxX = outX + m1_wid/2 + m1_m1_sp/2;
        stdCell.addNmosWell(wellMinX, wellMaxX, inv);
        stdCell.addPmosWell(wellMinX, wellMaxX, inv);

        double boundsLeftX = 0;
        double boundsRightX = outX + exp_wid/2 + m1_m1_sp/2;

        EPoint gndLeft = EPoint.fromLambda(boundsLeftX, stdCell.getGndY());
        EPoint gndRight = EPoint.fromLambda(boundsRightX, stdCell.getGndY());
        EPoint vddLeft = EPoint.fromLambda(boundsLeftX, stdCell.getVddY());
        EPoint vddRight = EPoint.fromLambda(boundsRightX, stdCell.getVddY());
        NodeInst gndPinLeft = NodeInst.newInstance(tech.m1pin(), ep, gndLeft, 0, 0, inv);
        NodeInst gndPinRight = NodeInst.newInstance(tech.m1pin(), ep, gndRight, 0, 0, inv);
        NodeInst vddPinLeft = NodeInst.newInstance(tech.m1pin(), ep, vddLeft, 0, 0, inv);
        NodeInst vddPinRight = NodeInst.newInstance(tech.m1pin(), ep, vddRight, 0, 0, inv);
        ArcInst gndArcLeft = ArcInst.makeInstance(tech.m1(), ep, gndPinLeft.getOnlyPortInst(), inv.findExport("gnd").getOriginalPort());
        gndArcLeft.setLambdaBaseWidth(stdCell.getGndWidth());
        gndArcLeft.setTailExtended(false);
        gndArcLeft.setHeadExtended(false);
        ArcInst gndArcRight = ArcInst.makeInstance(tech.m1(), ep, gndPinRight.getOnlyPortInst(), inv.findExport("gnd").getOriginalPort());
        gndArcRight.setLambdaBaseWidth(stdCell.getGndWidth());
        gndArcRight.setTailExtended(false);
        gndArcRight.setHeadExtended(false);
        ArcInst vddArcLeft = ArcInst.makeInstance(tech.m1(), ep, vddPinLeft.getOnlyPortInst(), inv.findExport("vdd").getOriginalPort());
        vddArcLeft.setLambdaBaseWidth(stdCell.getVddWidth());
        vddArcLeft.setTailExtended(false);
        vddArcLeft.setHeadExtended(false);
        ArcInst vddArcRight = ArcInst.makeInstance(tech.m1(), ep, vddPinRight.getOnlyPortInst(), inv.findExport("vdd").getOriginalPort());
        vddArcRight.setLambdaBaseWidth(stdCell.getVddWidth());
        vddArcRight.setTailExtended(false);
        vddArcRight.setHeadExtended(false);

        NodeInst deviceMark = NodeInst.newInstance(tech.getTechnology().findNodeProto("DeviceMark-Node"), ep,
                EPoint.fromLambda((boundsRightX + boundsLeftX)/2, (stdCell.getVddY() + stdCell.getGndY())/2),
                (boundsRightX - boundsLeftX), (stdCell.getVddY() - stdCell.getGndY()), inv);

        // add essential bounds
        stdCell.addEssentialBounds(boundsLeftX, boundsRightX, inv);

        for (Iterator<Export> it = inv.getExports(); it.hasNext(); ) {
            Export e = it.next();
            TextDescriptor td = e.getTextDescriptor(Export.EXPORT_NAME).withRelSize(0.5);
            e.setTextDescriptor(Export.EXPORT_NAME, td);
        }

        double gridTran = 0.10;
        double epsWidP = fwP.gateWid*fwP.nbFolds - totWidP;
        int nfixWidP = (int)Math.rint(epsWidP/gridTran);
        for (int i = pmos.nbGates() - nfixWidP; i < pmos.nbGates(); i++) {
            pmos.getGate(i, 'B').getNodeInst().modifyInstance(0, 0/*-gridTran/2*/, -gridTran, 0, Orientation.IDENT);
            pmos.getSrcDrn(i + 1).getNodeInst().modifyInstance(0, 0/*-gridTran/2*/, 0, -gridTran, Orientation.IDENT);
        }
        double epsWidN = fwN.gateWid*fwN.nbFolds - totWidN;
        int nfixWidN = (int)Math.rint(epsWidN/gridTran);
        for (int i = nmos.nbGates() - nfixWidN; i < nmos.nbGates(); i++) {
            nmos.getGate(i, 'T').getNodeInst().modifyInstance(0, 0/*gridTran/2*/, -gridTran, 0, Orientation.IDENT);
            nmos.getSrcDrn(i + 1).getNodeInst().modifyInstance(0, 0/*gridTran/2*/, 0, -gridTran, Orientation.IDENT);
        }
        System.out.println(inv.getName() + " " + nfixWidP + " " + nfixWidN);

        LayerCoverageTool.LayerCoveragePreferences lcp = new LayerCoverageTool.LayerCoveragePreferences(true);
        LayerCoverageTool.layerCoverageCommand(LayerCoverageTool.LCMode.IMPLANT, GeometryHandler.GHMode.ALGO_SWEEP, inv, false, lcp);
        
		// perform Network Consistency Check
		stdCell.doNCC(inv, nm+"{sch}");

        return inv;
    }
}
