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

import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.util.TextUtils;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class LENodable {

    /** Type is a typesafe enum class that describes the type of Instance this is */
    protected static class Type {
        private final String name;
        private Type(String name) { this.name = name; }
        public String toString() { return name; }

        /** NotSizeable */  protected static final Type STATICGATE = new Type("Static Gate");
        /** NotSizeable */  protected static final Type LOAD = new Type("Load");
        /** NotSizeable */  protected static final Type WIRE = new Type("Wire");
        /** LeGate */       protected static final Type LEGATE = new Type("LE Gate");
        /** LeKeeper */     protected static final Type LEKEEPER = new Type("LE Keeper");
        /** NotSizeable */  protected static final Type TRANSISTOR = new Type("Transistor");
        /** NotSizeable */  protected static final Type CAPACITOR = new Type("Capacitor");
        /** Cached cell */  protected static final Type CACHEDCELL = new Type("Cached Cell");
        /** Ignore */       protected static final Type IGNORE = new Type("LE Ignore");
    }

    // --------- Definition fields ----------
    /** list of pins */                     private List<LEPin> pins; // do not point to networks
    /** nodable */                          private Nodable no;
    /** gate type */                        private Type type;
    /** output Network */                   private Network outputNet;
    /** mfactor variable */                 private Variable mfactorVar;
    /** su variable */                      private Variable suVar;
    /** parallel group # variable */        private Variable parallelGroupVar;

    // --------- instance fields ------------
    protected VarContext context;
    protected LENetwork outputNetwork;        // global network
    private float mfactor = 1f;
    protected float su;
    protected float leX;
    protected int parallelGroup;

    /**
     * Create a new LEInstance tied to the Nodable.
     * If the type represents a singular instance, i.e. whose properties
     * do not change based on it's location in the hierarchy, then
     * leContext is the singular (one and only) context.
     * Otherwise, leContext is merely the first of many possible contexts.
     * @param no the Nodable
     */
    public LENodable(Nodable no, Type type, Variable mfactorVar, Variable suVar, Variable parallelGroupVar) {
        this.no = no;
        this.type = type;
        pins = new ArrayList<LEPin>();
        this.outputNet = null;
        this.mfactorVar = mfactorVar;
        this.suVar = suVar;
        this.parallelGroupVar = parallelGroupVar;
        this.context = VarContext.globalContext;
        this.mfactor = 1f;
        this.leX = 0f;
    }

    protected LENodable copy() {
        LENodable copy = new LENodable(no, type, mfactorVar, suVar, parallelGroupVar);
        for (LEPin pin : pins) {
            copy.addPort(pin.getName(), pin.getDir(), pin.getLE(), pin.getNetwork());
        }
        return copy;
    }

    /**
     * Add a port to this LEInstance
     * @param name the name of the port
     * @param dir the direction of the port
     * @param le the logical effort of the port
     */
    protected void addPort(String name, LEPin.Dir dir, float le, Network jnet) {
        LEPin pin = new LEPin(name, dir, le, jnet, this);
        pins.add(pin);
    }

    /** Set the output network */
    protected void setOutputNet(Network jnet) { outputNet = jnet; }

    /** Get the output network */
    protected Network getOutputNet() { return outputNet; }

    /** Get the nodable */
    protected Nodable getNodable() { return no; }

    /** Get the type */
    protected Type getType() { return type; }

    /** Get the pins */
    protected List<LEPin> getPins() { return pins; }

    float getMfactor() { return mfactor; }

    /** Return true if this is a sizeable gate */
    protected boolean isLeGate() {
        if (type == Type.LEKEEPER || type == Type.LEGATE) return true;
        return false;
    }

    /** True if this is a gate */
    protected boolean isGate() {
        if (type == Type.LEGATE || type == Type.LEKEEPER || type == Type.STATICGATE)
            return true;
        return false;
    }

    /**
     * Set the only context of this LENodable. This is used when the nodable is cacheable,
     * and remains the same throughout all contexts above the passed context.
     * Returns false if provided context is not enough to evaluate all variables properly.
     * @param context the context
     * @param outputNetwork the global network loading the output
     * @param mfactor the parent's mfactor
     * @param su the parent's step-up
     */
    protected boolean setOnlyContext(VarContext context, LENetwork outputNetwork, float mfactor, float su, LENetlister2.NetlisterConstants constants) {
        boolean evalOk = instantiate(this, context, outputNetwork, mfactor, su, constants, true);
        //print();
        return evalOk;
    }

    /**
     * Factory method to create a copy of this Nodable with the context-relevant info
     * evaluated.
     * @param context the context
     * @param outputNetwork the global network loading the output
     * @param mfactor the parent's mfactor
     * @param su the parent's step-up
     */
    protected LENodable createUniqueInstance(VarContext context, LENetwork outputNetwork, float mfactor, float su, LENetlister.NetlisterConstants constants) {
        LENodable instance = new LENodable(no, type, mfactorVar, suVar, parallelGroupVar);
        // copy pins
        for (LEPin pin : pins) {
            instance.addPort(pin.getName(), pin.getDir(), pin.getLE(), pin.getNetwork());
        }
        instantiate(instance, context, outputNetwork, mfactor, su, constants, false);
        return instance;
    }

    /**
     * Fill-in the given LENodable with evalutated instance-specific info for the given context.
     * Returns false if there are variables that cannot be evaluated in the given context
     * @param instance the LENodable to fill-in
     * @param context the context
     * @param outputNetwork the global network loading the output
     * @param mfactor the parent's mfactor
     * @param su the parent's step-up
     */
    private boolean instantiate(LENodable instance, VarContext context, LENetwork outputNetwork, float mfactor, float su,
                                LENetlister2.NetlisterConstants constants, boolean testCachebility) {
        instance.outputNetwork = outputNetwork;
        boolean evalOk = true;

        // default values
        instance.context = context;
        instance.leX = getLeX(context, constants, testCachebility);
        if (instance.leX == -1f) evalOk = false;
        instance.mfactor = mfactor;
        instance.su = su;
        instance.parallelGroup = 0;

        // evaluate variables in context, if any
        if (parallelGroupVar != null) {
            Object retVal = context.evalVar(parallelGroupVar);
            if (retVal == null) evalOk = false;
            else instance.parallelGroup = VarContext.objectToInt(retVal, instance.parallelGroup);
        }
        if (suVar != null) {
            Object retVal = context.evalVar(suVar);
            if (retVal == null) evalOk = false;
            else {
                float localsu = VarContext.objectToFloat(retVal, -1f);
                instance.su = (localsu == -1f) ? instance.su : localsu;
            }
        }
        if (mfactorVar != null) {
            Object retVal = context.evalVar(mfactorVar);
            if (retVal == null) evalOk = false;
            else instance.mfactor *= VarContext.objectToFloat(retVal, 1f);
        }
        return evalOk;
    }

    /**
     * Get the leX size for the given LENodable
     * Returns -1 if any evaluations failed.
     * @param context the VarContext
     * @return the leX size, or -1 if eval failed
     */
    private float getLeX(VarContext context, LENetlister2.NetlisterConstants constants, boolean testCachebility) {
        float leX = (float)0.0;

        float x1inverter_totalgate = constants.x1inverter_nwidth + constants.x1inverter_pwidth;

        Variable var = null;
        Object retVal = null;
        if (type == LENodable.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 = no.getParameterOrVariable(LENetlister.ATTR_LEWIRECAP);
            float cap = 0;
            if (var != null) {
                retVal = context.evalVar(var);
                if (testCachebility && (retVal == null)) return -1f;
                cap = VarContext.objectToFloat(retVal, 0.0f);
            } else {
                var = no.getParameterOrVariable(LENetlister.ATTR_L);
                if (var == null) {
                    System.out.println("Error, no L attribute found on LEWIRE "+no.getName()+" in Cell "+no.getParent());
                    if (testCachebility) return -1f;
                }
                retVal = context.evalVar(var);
                if (testCachebility && (retVal == null)) return -1f;
                float len = VarContext.objectToFloat(retVal, 0.0f);

                var = no.getParameterOrVariable(Schematics.ATTR_WIDTH);
                if (var == null) {
                    System.out.println("Warning, no width attribute found on LEWIRE "+no.getName()+" in Cell "+no.getParent());
                    if (testCachebility) return -1f;
                }
                retVal = context.evalVar(var);
                if (testCachebility && (retVal == null)) return -1f;
                float width = VarContext.objectToFloat(retVal, 3.0f);
                cap = (0.95f*len + 0.05f*len*(width/3.0f));
            }
            leX = cap*constants.wireRatio;  // equivalent lambda of gate
            leX = leX/x1inverter_totalgate;                         // drive strength X=1 is x1inverter_totalgate lambda of gate
        }
        else if (type == LENodable.Type.TRANSISTOR) {
            var = no.getParameterOrVariable(Schematics.ATTR_WIDTH);
            if (var == null) {
                System.out.println("Error: transistor "+no.getName()+" has no width in Cell "+no.getParent());
                //ErrorLogger.ErrorLog log = errorLogger.logError("Error: transistor "+no+" has no width in Cell "+info.getCell(), info.getCell(), 0);
                //log.addGeom(ni.getNodeInst(), true, no.getParent(), context);
                return -1f;
            }
            retVal = context.evalVar(var);
            if (retVal == null) return -1f;
            //float width = VarContext.objectToFloat(retVal, (float)3.0);
            float width = VarContext.objectToFloat(retVal, -1f);
            if (width == -1f) return -1f;

//            var = no.getVar(Schematics.ATTR_LENGTH);
//            if (var == null) {
//                System.out.println("Error: transistor "+no.getName()+" has no length in Cell "+no.getParent());
//                //ErrorLogger.ErrorLog log = errorLogger.logError("Error: transistor "+ni+" has no length in Cell "+info.getCell(), info.getCell(), 0);
//                //log.addGeom(ni.getNodeInst(), true, info.getCell(), info.getContext());
//                return -1f;
//            }
//            retVal = context.evalVar(var);
//            if (retVal == null) return -1f;
//            float length = VarContext.objectToFloat(retVal, (float)2.0);
            // not exactly correct because assumes all cap is area cap, which it isn't
            leX = (float)(width/x1inverter_totalgate);
        }
        else if (type == Type.CAPACITOR) {
            var = no.getVar(Schematics.SCHEM_CAPACITANCE);
            if (var == null) {
                System.out.println("Error: capacitor "+no.getName()+" has no capacitance in Cell "+no.getParent());
                //ErrorLogger.ErrorLog log = errorLogger.logError("Error: capacitor "+no+" has no capacitance in Cell "+info.getCell(), info.getCell(), 0);
                //log.addGeom(ni.getNodeInst(), true, no.getParent(), context);
                return -1f;
            }
            retVal = context.evalVar(var);
            if (testCachebility && (retVal == null)) return -1f;
            float cap = VarContext.objectToFloat(retVal, (float)0.0);
            leX = (float)(cap/constants.gateCap/1e-15/x1inverter_totalgate);
        }
        return leX;
    }

    // -----------------------------------------------------------------

    protected String getName() {
        if (context == null) return no.getName();
        return context.push(getNodable()).getInstPath(".");
    }

    protected void print() {
        System.out.println(getType().toString()+": "+getName());
        System.out.println("    Size    \t= "+leX);
        System.out.println("    Step-up \t= "+su);
        System.out.println("    Parallel Group\t= "+parallelGroup);
        System.out.println("    M Factor\t= "+mfactor);
    }

    protected String printOneLine(String indent) {
        StringBuffer buf = new StringBuffer(indent);
        buf.append(getType().toString());
        buf.append(": Size="+TextUtils.formatDouble(leX, 2));
        //buf.append(" M="+TextUtils.formatDouble(mfactor, 2));
        buf.append(" M="+mfactor);
        buf.append(" tPG="+parallelGroup);
        buf.append(" "+getName());
        return buf.toString();
    }

    protected void printPins() {
        for (LEPin pin : pins) {
            System.out.println("Pin "+pin.getName()+", le="+pin.getLE()+", dir="+pin.getDir()+" on network "+pin.getNetwork());
        }
    }

    protected float printLoadInfo(LEPin pin, float alpha) {
        StringBuffer buf = new StringBuffer();
        buf.append(getType().toString());
        buf.append("\tSize="+TextUtils.formatDouble(leX, 2));
        buf.append("\tLE="+TextUtils.formatDouble(pin.getLE(), 2));
        buf.append("\tM="+TextUtils.formatDouble(mfactor, 2));
        float load;
        if (pin.getDir() == LEPin.Dir.OUTPUT) {
            load = (float)(leX*pin.getLE()*mfactor*alpha);
            buf.append("\tAlpha="+alpha);
            buf.append("\tLoad="+TextUtils.formatDouble(load, 2));
        } else {
            load = (float)(leX*pin.getLE()*mfactor);
            buf.append("\tLoad="+TextUtils.formatDouble(load, 2));
        }
        buf.append("\t"+getName());
        System.out.println(buf.toString());
        return load;
    }
}
