/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: LENetlister1.java
 * Written by Jonathan Gainsley, Sun Microsystems.
 *
 * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Created on November 11, 2003, 3:56 PM
 */

package com.sun.electric.tool.logicaleffort;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.HierarchyEnumerator;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.prototype.PortCharacteristic;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.Tool;
import com.sun.electric.tool.logicaleffort.LENetlister.NetlisterConstants;
import com.sun.electric.tool.user.ErrorLogger;
import com.sun.electric.util.TextUtils;

import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * Creates a logical effort netlist to be sized by LESizer.
 * This is so the LESizer is independent of Electric's Database,
 * and can match George Chen's C++ version being developed for
 * PNP.
 *
 * @author  gainsley
 */
public class LENetlister1 extends LENetlister {

    // ALL GATES SAME DELAY
    /** Netlister constants */                  protected NetlisterConstants constants;

    /** all networks */                         private HashMap<String,Net> allNets;
    /** all instances (LEGATES, not loads) */   private Map<String,Instance> allInstances;

    /** Sizer */                                private LESizer sizer;
    /** Job we are part of */                   private Job job;
    /** Where to direct output */               private PrintStream out;
    /** Mapping between NodeInst and Instance */private List<Instance> instancesMap;

    /** True if we got aborted */               private boolean aborted;
    /** for logging errors */                   private ErrorLogger errorLogger;
    /** record definition errors so no multiple warnings */ private HashMap<Export,Export> lePortError;
    /** The top level cell netlisted */         private Cell topLevelCell;

    private static final boolean DEBUG = false;

    /** Creates a new instance of LENetlister */
    public LENetlister1(Job job, Technology layoutTech) {
        super(layoutTech);
        // get preferences for this package
        Tool leTool = Tool.findTool("logical effort");
        constants = null;
        topLevelCell = null;

        allNets = new HashMap<String,Net>();
        allInstances = new TreeMap/*HashMap*/<String,Instance>();

        this.job = job;
        this.instancesMap = new ArrayList<Instance>();
        this.lePortError = new HashMap<Export,Export>();
        this.out = new PrintStream((OutputStream)System.out);

        errorLogger = null;
        aborted = false;
    }

    // Entry point: This netlists the cell
    @Override
    public boolean netlist(Cell cell, VarContext context, boolean useCaching) {

        //ArrayList connectedPorts = new ArrayList();
        //connectedPorts.add(Schematics.tech.resistorNode.getPortsList());
        assert errorLogger == null;
//        if (errorLogger != null) errorLogger.delete();
        errorLogger = ErrorLogger.newInstance("LE Netlister");

//        Netlist netlist = cell.getNetlist(true);

        // read schematic-specific sizing options
        constants = getSettings(cell);
        if (constants == null) {
            constants = new NetlisterConstants(layoutTech);
            if (!saveSettings(constants, cell)) {
                // couldn't save settings to cell, abort
                return false;
            }
        }

        topLevelCell = cell;
        HierarchyEnumerator.enumerateCell(cell, context, this, SHORT_RESISTORS);
//        HierarchyEnumerator.enumerateCell(cell, context, netlist, this);
        if (aborted) return false;
        return true;
    }

    /**
     * Size the netlist.
     * @return true on success, false otherwise.
     */
    public boolean size(LESizer.Alg algorithm) {
        //lesizer.printDesign();
        boolean verbose = false;
        // create a new sizer
        sizer = new LESizer(algorithm, this, job, errorLogger);
        boolean success = sizer.optimizeLoops(constants.epsilon, constants.maxIterations, verbose,
                constants.alpha, constants.keeperRatio);
        //out.println("---------After optimization:------------");
        //lesizer.printDesign();
        // get rid of the sizer
        sizer = null;
        return success;
    }

    public void getSizes(List<Float> sizes, List<String> varNames, List<NodeInst> nodes, List<VarContext> contexts) {
        // iterator over all LEGATEs
        Set<Map.Entry<String,Instance>> allEntries = allInstances.entrySet();
        for (Map.Entry<String,Instance> entry : allEntries) {

            Instance inst = (Instance)entry.getValue();
            Nodable no = inst.getNodable();
            NodeInst ni = no.getNodeInst();
            if (ni != null) no = ni;

            if (!inst.isLeGate()) continue;

            String varName = "LEDRIVE_" + inst.getName();
            //no.newVar(varName, new Float(inst.getLeX()));
            //topLevelCell.newVar(varName, new Float(inst.getLeX()));
            sizes.add(new Float(inst.getLeX()));
            varNames.add(varName);
            nodes.add(ni);
            contexts.add(inst.getContext());
        }
    }

    public void done() {
        errorLogger.termLogging(true);
        //errorLogger = null;
    }

    public ErrorLogger getErrorLogger() { return errorLogger; }

    public void nullErrorLogger() { errorLogger = null; }

    public NetlisterConstants getConstants() { return constants; }

    /**
	 * Add new instance to design
	 * @param name name of the instance
	 * param leGate true if this is an LEGate
	 * @param leX size
	 * @param pins list of pins on instance
	 *
	 * @return the new instance added, null if error
	 */
	protected Instance addInstance(String name, Instance.Type type, float leSU,
		float leX, ArrayList<Pin> pins, Nodable no)
	{
		if (allInstances.containsKey(name)) {
			out.println("Error: Instance "+name+" already exists.");
			return null;
		}
		// create instance
		Instance instance = new Instance(name, type, leSU, leX, no);

		// create each net if necessary, from pin.
		Iterator<Pin> iter = pins.iterator();
		while (iter.hasNext()) {
			Pin pin = iter.next();
			String netname = pin.getNetName();

			// check to see if net had already been added to the design
			Net net = allNets.get(netname);
			if (net != null) {
				pin.setNet(net);
				pin.setInstance(instance);
				net.addPin(pin);
			} else {
				// create new net
				net = new Net(netname);
				allNets.put(netname, net);
				pin.setNet(net);
				pin.setInstance(instance);
				net.addPin(pin);
			}
		}
		instance.setPins(pins);

		allInstances.put(name, instance);
		return instance;
	}

    //public HashMap getInstancesMap() { return instancesMap; }
    protected Map<String,Instance> getAllInstances() { return allInstances; }

    protected HashMap<String,Net> getAllNets() { return allNets; }

    /** return number of gates sized */
    protected int getNumGates() { return allInstances.size(); }

    protected LESizer getSizer() { return sizer; }

    protected float getKeeperRatio() { return constants.keeperRatio; }

    // ======================= Hierarchy Enumerator ==============================

    /**
     * Override the default Cell info to pass along logical effort specific information
     * @return a LECellInfo
     */
    public HierarchyEnumerator.CellInfo newCellInfo() { return new LECellInfo(); }

    /**
     * Enter cell initializes the LECellInfo.
     * @param info the LECellInfo
     * @return true to process the cell, false to ignore.
     */
    public boolean enterCell(HierarchyEnumerator.CellInfo info) {
        if (aborted) return false;

        if (((LETool.AnalyzeCell)job).checkAbort(null)) {
            aborted = true;
            return false;
        }

        LECellInfo leinfo = (LECellInfo)info;
        leinfo.leInit(constants);

        // check if conflicting settings
        if (topLevelCell != info.getCell()) {
            if (isSettingsConflict(leinfo.getSettings(), topLevelCell, info.getContext(), info.getCell())) {
                aborted = true;
                return false;
            }
        }

        return true;
    }

    /**
     * Visit NodeInst creates a new Logical Effort instance from the
     * parameters found on the Nodable, if that Nodable is an LEGATE.
     * It also creates instances for wire models (LEWIREs).
     * @param ni the Nodable being visited
     * @param info the cell info
     * @return true to push down into the Nodable, false to continue.
     */
    public boolean visitNodeInst(Nodable ni, HierarchyEnumerator.CellInfo info) {
        float leX = (float)0.0;
        boolean wire = false;
        boolean primitiveTransistor = false;
        float x1inverter_totalgate = constants.x1inverter_nwidth + constants.x1inverter_pwidth;

        // Check if this NodeInst is tagged as a logical effort node
        Instance.Type type = null;
        Variable var = null;
        if ((var = getVar(ni, ATTR_LEGATE)) != null) {
            // assume it is LEGATE if can't resolve value
            int gate = VarContext.objectToInt(info.getContext().evalVar(var), 1);
            if (gate == 1)
                type = Instance.Type.LEGATE;
            else
                return true;
        }
        else if ((var = getVar(ni, ATTR_LEKEEPER)) != null) {
            // assume it is LEKEEPER if can't resolve value
            int gate = VarContext.objectToInt(info.getContext().evalVar(var), 1);
            if (gate == 1)
                type = Instance.Type.LEKEEPER;
            else
                return true;
        }
        else if (getVar(ni, ATTR_LEWIRE) != null) {
            type = Instance.Type.WIRE;
            // Note that if inst is an LEWIRE, it will have no 'le' attributes.
            // we therefore assign pins to have default 'le' values of one.
            // This creates an instance which has Type LEWIRE, but has
            // boolean leGate set to false; it will not be sized
            // NEW: If we find ATTR_LEWIRECAP, that is the capacitance to use,
            // and we will not calculate the cap from L and W.
            var = ni.getParameterOrVariable(ATTR_LEWIRECAP);
            float cap = 0;
            if (var != null) {
                cap = VarContext.objectToFloat(info.getContext().evalVar(var), 0.0f);
            } else {
                var = ni.getParameterOrVariable(ATTR_L);
                if (var == null) {
                    System.out.println("Error, no L attribute found on LEWIRE "+info.getContext().push(ni).getInstPath("."));
                }
                float len = VarContext.objectToFloat(info.getContext().evalVar(var), 0.0f);
                var = ni.getParameterOrVariable(Schematics.ATTR_WIDTH);
                if (var == null) {
                    System.out.println("Warning, no width attribute found on LEWIRE "+info.getContext().push(ni).getInstPath("."));
                }
                float width = VarContext.objectToFloat(info.getContext().evalVar(var), 3.0f);
                cap = (float)(0.95f*len + 0.05f*len*(width/3.0f));      // capacitance
            }
            leX = cap*constants.wireRatio/x1inverter_totalgate;     // drive strength X=1 is x1inverter_totalgate lambda of gate
            wire = true;
        }
        else if ((ni.getProto() != null) && (ni.getProto().getFunction().isTransistor())) {
            // handle transistor loads
            type = Instance.Type.STATICGATE;
            var = ni.getParameterOrVariable(Schematics.ATTR_WIDTH);
            if (var == null) {
                System.out.println("Error: transistor "+ni+" has no width in Cell "+info.getCell());
                errorLogger.logError("Error: transistor "+ni+" has no width in Cell "+info.getCell(),
                	ni.getNodeInst(), info.getCell(), info.getContext(), 0);
                return false;
            }
            float width = VarContext.objectToFloat(info.getContext().evalVar(var), (float)3.0);

            // note that LE will handle any gate load bloat due to increased gate length
            leX = (float)(width/x1inverter_totalgate);
            primitiveTransistor = true;
        }
        else if ((ni.getProto() != null) && (ni.getProto().getFunction().isCapacitor())) {
            type = Instance.Type.CAPACITOR;
            var = ni.getVar(Schematics.SCHEM_CAPACITANCE);
            if (var == null) {
                System.out.println("Error: capacitor "+ni+" has no capacitance in Cell "+ni.getParent());
                //errorLogger.logError("Error: capacitor "+no+" has no capacitance in Cell "+info.getCell(), ni.getNodeInst(), true, no.getParent(), context, 0);
                return false;
            }
            float cap = VarContext.objectToFloat(info.getContext().evalVar(var), (float)0.0);
            leX = (float)(cap/constants.gateCap/1e-15/x1inverter_totalgate);
        }
        else if (ni.getParameterOrVariable(ATTR_LESETTINGS) != null)
            return false;
        else if (ni.getParameterOrVariable(ATTR_LEIGNORE) != null)
            return false;


        if (type == null) return true;              // descend into and process

        if (DEBUG) System.out.println("------------------------------------");

        // If got to this point, this is either an LEGATE or an LEWIRE
        // Both require us to build an instance.
        ArrayList<Pin> pins = new ArrayList<Pin>();
		Netlist netlist = info.getNetlist();
		for (Iterator<PortProto> ppIt = ni.getProto().getPorts(); ppIt.hasNext();) {
			PortProto pp = ppIt.next();
            // Note default 'le' value should be one
            float le = getLE(ni, type, pp, info);
            String netName = info.getUniqueNetName(info.getNetID(netlist.getNetwork(ni,pp,0)), ".");
            Pin.Dir dir = Pin.Dir.INPUT;
            // if it's not an output, it doesn't really matter what it is.
            if (pp.getCharacteristic() == PortCharacteristic.OUT) dir = Pin.Dir.OUTPUT;
            if (primitiveTransistor) {
                // primitive Electric Transistors have their source and drain set to BIDIR, we
                // want them set to OUTPUT so that they count as diffusion capacitance
                if (pp.getCharacteristic() == PortCharacteristic.BIDIR) dir = Pin.Dir.OUTPUT;
            }
            if (dir == Pin.Dir.INPUT && type == Instance.Type.STATICGATE) {
                // gate load: check if length > 2, if so, increase LE to account for added capacitance
                var = ni.getParameterOrVariable(Schematics.ATTR_LENGTH);
                if (var == null) {
                    System.out.println("Error: transistor "+ni+" has no length in Cell "+info.getCell());
                    errorLogger.logError("Error: transistor "+ni+" has no length in Cell "+info.getCell(),
                    	ni.getNodeInst(), info.getCell(), info.getContext(), 0);
                }
                float length = VarContext.objectToFloat(info.getContext().evalVar(var), (float)2.0);
                // not exactly correct because assumes all cap is area cap, which it isn't
                if (length != constants.x1inverter_length)
                    le = le * length / constants.x1inverter_length;
            }
            pins.add(new Pin(pp.getName(), dir, le, netName));
            if (DEBUG) System.out.println("    Added "+dir+" pin "+pp.getName()+", le: "+le+", netName: "+netName+", Network: "+netlist.getNetwork(ni,pp,0));
            if (type == Instance.Type.WIRE) break;    // this is LEWIRE, only add one pin of it
        }

        // see if passed-down step-up exists
        float localsu = constants.su;
        if (((LECellInfo)info).getSU() != -1f)
            localsu = ((LECellInfo)info).getSU();
        // check for step-up on gate
        var = ni.getParameterOrVariable(ATTR_su);
        if (var != null) {
            float nisu = VarContext.objectToFloat(info.getContext().evalVar(var), -1f);
            if (nisu != -1f)
                localsu = nisu;
        }

        // create new leGate instance
        VarContext vc = info.getContext().push(ni);                   // to create unique flat name
        Instance inst = addInstance(vc.getInstPath("."), type, localsu, leX, pins, ni);
        inst.setContext(info.getContext());

        // set instance parameters for sizeable gates
        if (type == Instance.Type.LEGATE || type == Instance.Type.LEKEEPER) {
            var = ni.getParameterOrVariable(ATTR_LEPARALLGRP);
            if (var != null) {
                // set parallel group number
                int g = VarContext.objectToInt(info.getContext().evalVar(var), 0);
                inst.setParallelGroup(g);
            }
        }
        // set mfactor
        float parentM = ((LECellInfo)info).getMFactor();
        inst.setMfactor(parentM);
        var = LETool.getMFactor(ni);
        if (var != null) {
            // set mfactor
            float m = VarContext.objectToFloat(info.getContext().evalVar(var), 1.0f);
            m = m * parentM;
            inst.setMfactor(m);
        }

        if (DEBUG) {
            if (wire) System.out.println("  Added LEWire "+vc.getInstPath(".")+", X="+leX);
            else System.out.println("  Added instance "+vc.getInstPath(".")+" of type "+type+", X="+leX);
        }
        instancesMap.add(inst);
        return false;
    }

    private float getLE(Nodable ni, Instance.Type type, PortProto pp, HierarchyEnumerator.CellInfo info) {
        boolean leFound = false;
        // Note default 'le' value should be one
        float le = 1.0f;
		if (!(pp instanceof Export))
			return le;
        Variable var = ((Export)pp).getParameterOrVariable(ATTR_le);
        if (var != null) {
            leFound = true;
            le = VarContext.objectToFloat(info.getContext().evalVar(var), 1.0f);
        } else if ((pp.getCharacteristic() == PortCharacteristic.OUT) &&
                (type == Instance.Type.LEGATE || type == Instance.Type.LEKEEPER)) {
            // if this is an Sizeable gate's output, look for diffn and diffp
            float diff = 0;
            var = ((Export)pp).getParameterOrVariable(ATTR_diffn);
            if (var != null) {
                diff += VarContext.objectToFloat(info.getContext().evalVar(var), 0);
                leFound = true;
            }
            var = ((Export)pp).getParameterOrVariable(ATTR_diffp);
            if (var != null) {
                diff += VarContext.objectToFloat(info.getContext().evalVar(var), 0);
                leFound = true;
            }
            le = diff/3.0f;
        }
        if (!leFound && (type == Instance.Type.LEGATE || type == Instance.Type.LEKEEPER)) {
            Cell cell = (Cell)ni.getProto();
            Export exp = cell.findExport(pp.getName());
            if (exp != null && lePortError.get(exp) == null) {
                String msg = "Warning: Sizeable gate has no logical effort specified for port "+pp.getName()+" in "+cell;
                System.out.println(msg);
                errorLogger.logWarning(msg, exp, cell, info.getContext().push(ni), 0);
                lePortError.put(exp, exp);
            }
        }
        return le;
    }

    private Variable getVar(Nodable no, Variable.Key key) {
        Variable var = no.getParameter(key);
        //if (var == null) var = no.getVarDefaultOwner().getVar(key);
        return var;
    }

    public void doneVisitNodeInst(Nodable ni, HierarchyEnumerator.CellInfo info) {}

    /**
     * Nothing to do for exitCell
     */
    public void exitCell(HierarchyEnumerator.CellInfo info) {
    }

    /**
     * Logical Effort Cell Info class.  Keeps track of:
     * <p>- M factors
     */
    public class LECellInfo extends LENetlister.LECellInfo {

    }



    // =============================== Statistics ==================================


    public void printStatistics() {
        Collection<Instance> instances = getAllInstances().values();
        float totalsize = 0f;
        float instsize = 0f;
        int numLEGates = 0;
        int numLEWires = 0;
        for (Instance inst : instances) {
            totalsize += inst.getLeX();
            if (inst.getType() == Instance.Type.LEGATE || inst.getType() == Instance.Type.LEKEEPER) {
                numLEGates++;
                instsize += inst.getLeX();
            }
            if (inst.getType() == Instance.Type.WIRE)
                numLEWires++;
        }
        System.out.println("Number of LEGATEs: "+numLEGates);
        System.out.println("Number of Wires: "+numLEWires);
        System.out.println("Total size of all LEGATEs: "+instsize);
        System.out.println("Total size of all instances (sized and loads): "+totalsize);
    }

    public float getTotalLESize() {
        return getTotalSize(Instance.Type.LEGATE) + getTotalSize(Instance.Type.LEKEEPER);
    }

    /**
     * return total size of all instances of the specified type
     * if type is null, uses all types
     */
    public float getTotalSize(Instance.Type type) {
        Collection<Instance> instances = getAllInstances().values();
        float totalsize = 0f;
        for (Instance inst : instances) {
            if (type == null)
                totalsize += inst.getLeX();
            else if (inst.getType() == type)
                totalsize += inst.getLeX();
        }
        return totalsize;
    }

    public boolean printResults(Nodable no, VarContext context) {
        // if this is a NodeInst, convert to Nodable
        if (no instanceof NodeInst) {
            no = Netlist.getNodableFor((NodeInst)no, 0);
        }
        Instance inst = null;
        for (Instance instance : instancesMap) {
            if (instance.getNodable() == no) {
                if (instance.getContext().getInstPath(".").equals(context.getInstPath("."))) {
                    inst = instance;
                    break;
                }
            }
        }
        if (inst == null) return false;                 // failed

//        MessagesWindow msgs = TopLevel.getMessagesWindow();
        //Font oldFont = msgs.getFont();
        //msgs.setFont(new Font("Courier", Font.BOLD, oldFont.getSize()));

        // print netlister info
        System.out.println("Netlister: Gate Cap="+constants.gateCap+", Alpha="+constants.alpha);

        // print instance info
        inst.print();

        // collect info about what is driven
        Pin out = inst.getOutputPins().get(0);
        Net net = out.getNet();

        ArrayList<Pin> gatesDrivenPins = new ArrayList<Pin>();
        ArrayList<Pin> loadsDrivenPins = new ArrayList<Pin>();
        ArrayList<Pin> wiresDrivenPins = new ArrayList<Pin>();
        ArrayList<Pin> gatesFightingPins = new ArrayList<Pin>();

        for (Pin pin : net.getAllPins()) {
            Instance in = pin.getInstance();
            if (pin.getDir() == Pin.Dir.INPUT) {
                if (in.isGate()) gatesDrivenPins.add(pin);
                //if (in.getType() == Instance.Type.STATICGATE) staticGatesDriven.add(in);
                if (in.getType() == Instance.Type.LOAD) loadsDrivenPins.add(pin);
                if (in.getType() == Instance.Type.CAPACITOR) loadsDrivenPins.add(pin);
                if (in.getType() == Instance.Type.WIRE) wiresDrivenPins.add(pin);
            }
            if (pin.getDir() == Pin.Dir.OUTPUT) {
                if (in.isGate()) gatesFightingPins.add(pin);
            }
        }
        System.out.println("Note: Load = Size * LE * M");
        System.out.println("Note: Load = Size * LE * M * Alpha, for Gates Fighting");

        float totalLoad = 0f;
        System.out.println("  -------------------- Gates Driven ("+gatesDrivenPins.size()+") --------------------");
        for (Pin pin : gatesDrivenPins) {
            totalLoad += pin.getInstance().printLoadInfo(pin, constants.alpha);
        }
        System.out.println("  -------------------- Loads Driven ("+loadsDrivenPins.size()+") --------------------");
        for (Pin pin : loadsDrivenPins) {
            totalLoad += pin.getInstance().printLoadInfo(pin, constants.alpha);
        }
        System.out.println("  -------------------- Wires Driven ("+wiresDrivenPins.size()+") --------------------");
        for (Pin pin : wiresDrivenPins) {
            totalLoad += pin.getInstance().printLoadInfo(pin, constants.alpha);
        }
        System.out.println("  -------------------- Gates Fighting ("+gatesFightingPins.size()+") --------------------");
        for (Pin pin : gatesFightingPins) {
            totalLoad += pin.getInstance().printLoadInfo(pin, constants.alpha);
        }
        System.out.println("*** Total Load: "+TextUtils.formatDouble(totalLoad, 2));

        //msgs.setFont(oldFont);
        return true;
    }


    // ---- TEST STUFF -----  REMOVE LATER ----
    public static void test1() {
        LESizer.test1();
    }

}
