/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: LENetlister.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.EditingPreferences;
import com.sun.electric.database.hierarchy.*;
import com.sun.electric.database.network.Netlist;
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.Technology;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.user.ErrorLogger;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.List;
import java.io.Serializable;


public abstract class LENetlister extends HierarchyEnumerator.Visitor {
    static final Netlist.ShortResistors SHORT_RESISTORS = Netlist.ShortResistors.ALL;

    public static final Variable.Key ATTR_su = Variable.newKey("ATTR_su");
    public static final Variable.Key ATTR_le = Variable.newKey("ATTR_le");
    public static final Variable.Key ATTR_wire_ratio = Variable.newKey("ATTR_wire_ratio");
    public static final Variable.Key ATTR_epsilon = Variable.newKey("ATTR_epsilon");
    public static final Variable.Key ATTR_max_iter = Variable.newKey("ATTR_max_iter");
    public static final Variable.Key ATTR_gate_cap = Variable.newKey("ATTR_gate_cap");
    public static final Variable.Key ATTR_alpha = Variable.newKey("ATTR_alpha");
    public static final Variable.Key ATTR_diffn = Variable.newKey("ATTR_diffn");
    public static final Variable.Key ATTR_diffp = Variable.newKey("ATTR_diffp");
    public static final Variable.Key ATTR_keeper_ratio = Variable.newKey("ATTR_keeper_ratio");
    public static final Variable.Key ATTR_x1inverter_nwidth = Variable.newKey("ATTR_x1inverter_nwidth");
    public static final Variable.Key ATTR_x1inverter_pwidth = Variable.newKey("ATTR_x1inverter_pwidth");
    public static final Variable.Key ATTR_x1inverter_length = Variable.newKey("ATTR_x1inverter_length");
    public static final Variable.Key ATTR_LEGATE = Variable.newKey("ATTR_LEGATE");
    public static final Variable.Key ATTR_LEKEEPER = Variable.newKey("ATTR_LEKEEPER");
    public static final Variable.Key ATTR_LEWIRE = Variable.newKey("ATTR_LEWIRE");
    public static final Variable.Key ATTR_LEIGNORE = Variable.newKey("ATTR_LEIGNORE");
    public static final Variable.Key ATTR_LESETTINGS = Variable.newKey("ATTR_LESETTINGS");
    public static final Variable.Key ATTR_LEPARALLGRP = Variable.newKey("ATTR_LEPARALLGRP");
    public static final Variable.Key ATTR_L = Variable.newKey("ATTR_L");
    public static final Variable.Key ATTR_LEWIRECAP = Variable.newKey("ATTR_LEWIRECAP");

    public static class NetlisterConstants implements Serializable {
        /** global step-up */                       public final float su;
        /** wire to gate cap ratio */               public final float wireRatio;
        /** convergence criteron */                 public final float epsilon;
        /** max number of iterations */             public final int maxIterations;
        /** gate cap, in fF/lambda */               public final float gateCap;
        /** ratio of diffusion to gate cap */       public final float alpha;
        /** ratio of keeper to driver size */       public final float keeperRatio;
        /** width in lambda of x=1 inverter nmos */ public final float x1inverter_nwidth;
        /** width in lambda of x=1 inverter pmos */ public final float x1inverter_pwidth;
        /** length in lambda of x=1 mosfets */      public final float x1inverter_length;

        public NetlisterConstants(float su, float wireRatio, float epsilon,
                                  int maxIterations, float gateCap, float alpha, float keeperRatio,
                                  float x1inverter_nwidth, float x1inverter_pwidth, float x1inverter_length) {
            this.su = su;
            this.wireRatio = wireRatio;
            this.epsilon = epsilon;
            this.maxIterations = maxIterations;
            this.gateCap = gateCap;
            this.alpha = alpha;
            this.keeperRatio = keeperRatio;
            this.x1inverter_nwidth = x1inverter_nwidth;
            this.x1inverter_pwidth = x1inverter_pwidth;
            this.x1inverter_length = x1inverter_length;
        }

        /** Create a new set of constants from the user's settings */
        public NetlisterConstants(Technology layoutTech) {
            su = (float)LETool.getGlobalFanout();
            epsilon = (float)LETool.getConvergenceEpsilon();
            maxIterations = LETool.getMaxIterations();
            gateCap = (float)layoutTech.getGateCapacitance();
            wireRatio = (float)layoutTech.getWireRatio();
            alpha = (float)layoutTech.getDiffAlpha();
            keeperRatio = (float)LETool.getKeeperRatio();
            x1inverter_nwidth = (float)LETool.getX1InverterNWidth();
            x1inverter_pwidth = (float)LETool.getX1InverterPWidth();
            x1inverter_length = (float)LETool.getX1InverterLength();
        }

        /** Returns true if the two NetlisterConstants have the same values for all fields */
        public boolean equals(NetlisterConstants other) {
            if (su != other.su) return false;
            if (wireRatio != other.wireRatio) return false;
            if (epsilon != other.epsilon) return false;
            if (maxIterations != other.maxIterations) return false;
            if (gateCap != other.gateCap) return false;
            if (alpha != other.alpha) return false;
            if (keeperRatio != other.keeperRatio) return false;
            if (x1inverter_nwidth != other.x1inverter_nwidth) return false;
            if (x1inverter_pwidth != other.x1inverter_pwidth) return false;
            if (x1inverter_length != other.x1inverter_length) return false;
            return true;
        }
    }
    
    Technology layoutTech;
    
    LENetlister(Technology layoutTech) {
        this.layoutTech = layoutTech;
    }

    /** Call to start netlisting. Returns false if failed */
    public abstract boolean netlist(Cell cell, VarContext context, boolean useCaching);

    /** Call to stop or interrupt netlisting */
    public abstract void done();

    /**
     * Call to size netlist with the specified algorithm
     * @return true if successful, false otherwise
     */
    public abstract boolean size(LESizer.Alg algorithm);

    /** Get the error logger */
    public abstract ErrorLogger getErrorLogger();
    /** Destroy the error logger */
    public abstract void nullErrorLogger();

    /** Get the settings used for sizing */
    public abstract NetlisterConstants getConstants();

    /** Get the sizes and associated variable names to store on the top level cell */
    public abstract void getSizes(List<Float> sizes, List<String> varNames,
                                  List<NodeInst> nodes, List<VarContext> contexts);

    public abstract void printStatistics();

    // ---------------------------- statistics ---------------------------------

    /** print the results for the Nodable
     * @return true if successful, false otherwise */
    public abstract boolean printResults(Nodable no, VarContext context);

    /** Get the total size of all gates sized using Logical Effort */
    public abstract float getTotalLESize();

    // ------------------------- Utility ---------------------------------------

    /**
     * Get any Logical Effort settings saved on the specified cell
     * @param cell the cell in question
     * @return the netlister constants settings, or null if none found
     */
    protected NetlisterConstants getSettings(Cell cell) {
        if (!LETool.isUseLocalSettings()) return null;
        for (Iterator<NodeInst> instIt = cell.getNodes(); instIt.hasNext();) {
            NodeInst ni = (NodeInst)instIt.next();
            if (ni.isIconOfParent()) continue;
            if (!ni.isCellInstance()) continue;
            if (ni.getParameterOrVariable(ATTR_LESETTINGS) != null) {
                Technology tech = cell.getTechnology();
                if (cell.isSchematic())
                    tech = layoutTech;
                float su = (float)LETool.getGlobalFanout();
                float epsilon = (float)LETool.getConvergenceEpsilon();
                int maxIterations = LETool.getMaxIterations();
                float gateCap = (float)tech.getGateCapacitance();
                float wireRatio = (float)tech.getWireRatio();
                float alpha = (float)tech.getDiffAlpha();
                float keeperRatio = (float)LETool.getKeeperRatio();
                float x1inverter_nwidth = (float)LETool.getX1InverterNWidth();
                float x1inverter_pwidth = (float)LETool.getX1InverterPWidth();
                float x1inverter_length = (float)LETool.getX1InverterLength();
                Variable var;
                VarContext context = VarContext.globalContext;
                if ((var = ni.getParameterOrVariable(ATTR_su)) != null) su = VarContext.objectToFloat(context.evalVar(var), su);
                if ((var = ni.getParameterOrVariable(ATTR_wire_ratio)) != null) wireRatio = VarContext.objectToFloat(context.evalVar(var), wireRatio);
                if ((var = ni.getParameterOrVariable(ATTR_epsilon)) != null) epsilon = VarContext.objectToFloat(context.evalVar(var), epsilon);
                if ((var = ni.getParameterOrVariable(ATTR_max_iter)) != null) maxIterations = VarContext.objectToInt(context.evalVar(var), maxIterations);
                if ((var = ni.getParameterOrVariable(ATTR_gate_cap)) != null) gateCap = VarContext.objectToFloat(context.evalVar(var), gateCap);
                if ((var = ni.getParameterOrVariable(ATTR_alpha)) != null) alpha = VarContext.objectToFloat(context.evalVar(var), alpha);
                if ((var = ni.getParameterOrVariable(ATTR_keeper_ratio)) != null) keeperRatio = VarContext.objectToFloat(context.evalVar(var), keeperRatio);
                if ((var = ni.getParameterOrVariable(ATTR_x1inverter_nwidth)) != null) x1inverter_nwidth = VarContext.objectToFloat(context.evalVar(var), x1inverter_nwidth);
                if ((var = ni.getParameterOrVariable(ATTR_x1inverter_pwidth)) != null) x1inverter_pwidth = VarContext.objectToFloat(context.evalVar(var), x1inverter_pwidth);
                if ((var = ni.getParameterOrVariable(ATTR_x1inverter_length)) != null) x1inverter_length = VarContext.objectToFloat(context.evalVar(var), x1inverter_length);
                return new NetlisterConstants(su, wireRatio, epsilon, maxIterations, gateCap, alpha, keeperRatio, x1inverter_nwidth, x1inverter_pwidth, x1inverter_length);
            }
        }
        return null;
    }

    /**
     * This checks for LE settings in the cell, and returns true if they conflict.
     * It also warns the user that there are conflicting settings from the subcell.
     * @param current the current settings (from the top level cell, or global options)
     * @return true if there was a conflict, false otherwise
     */
    protected boolean isSettingsConflict(NetlisterConstants current, Cell topLevelCell, VarContext context, Cell localCell) {
        assert(current != null);
        NetlisterConstants local = getSettings(localCell);
        if (local == null) return false;
        if (!current.equals(local)) {
            System.out.println("Error: Global settings from "+topLevelCell+" do not match global settings from \""+context.getInstPath("/")
                    +": "+localCell.noLibDescribe()+"\" in (" + localCell.getLibrary().getName()+")");
            System.out.println("       Global settings are by definition global, and differences may indicate an inconsistency in your design.");
            System.out.println("       Note that step-up, \"su\", can be made local by defining a \"su\" parameter on an instance.");
            System.out.println("\tglobal/parent vs local:");
            if (current.su != local.su) System.out.println("su:\t"+current.su+" vs "+local.su);
            if (current.wireRatio != local.wireRatio) System.out.println("wireRatio:\t"+current.wireRatio+" vs "+local.wireRatio);
            if (current.epsilon != local.epsilon) System.out.println("epsilon:\t"+current.epsilon+" vs "+local.epsilon);
            if (current.maxIterations != local.maxIterations) System.out.println("maxIterations:\t"+current.maxIterations+" vs "+local.maxIterations);
            if (current.gateCap != local.gateCap) System.out.println("gateCap:\t"+current.gateCap+" vs "+local.gateCap);
            if (current.alpha != local.alpha) System.out.println("alpha:\t"+current.alpha+" vs "+local.alpha);
            if (current.keeperRatio != local.keeperRatio) System.out.println("keeperRatio:\t"+current.keeperRatio+" vs "+local.keeperRatio);
            if (current.x1inverter_nwidth != local.x1inverter_nwidth) System.out.println("x=1 inverter nwidth:\t"+current.x1inverter_nwidth+" vs "+local.x1inverter_nwidth);
            if (current.x1inverter_pwidth != local.x1inverter_pwidth) System.out.println("x=1 inverter pwidth:\t"+current.x1inverter_pwidth+" vs "+local.x1inverter_pwidth);
            if (current.x1inverter_length != local.x1inverter_length) System.out.println("x=1 inverter length:\t"+current.x1inverter_length+" vs "+local.x1inverter_length);
            //SwingUtilities.invokeLater(new Runnable() {
            //    public void run() {
                	Job.getUserInterface().showErrorMessage("Conflicting global parameter settings were found, " +
                            "please see message window for details", "Settings Conflict Found!!");
            //    }
            //});
            return true;
        }
        return false;
    }

    /**
     * Saves the Global settings to the cell. Note that this does not overwrite
     * settings already there.  If any settings are found, it does nothing and returns false.
     * @return true if settings saved, false otherwise
     */
    protected boolean saveSettings(NetlisterConstants constants, Cell cell) {
        // make sure no settings already on cell
        if (getSettings(cell) != null) return false;

        // first we need to find the LESettings Cell
        Cell settings = null;
        for (Iterator<Library> it = Library.getLibraries(); it.hasNext(); ) {
            Library lib = (Library)it.next();
            for (Iterator<Cell> it2 = lib.getCells(); it2.hasNext(); ) {
                Cell c = (Cell)it2.next();
                if (c.getParameterOrVariable(ATTR_LESETTINGS) != null) {
                    settings = c;
                    break;
                }
            }
            if (settings != null) break;
        }
        if (settings == null) {
            System.out.println("Could not find LESETTINGS cell in order to save settings to "+cell);
            return false;
        }
        System.out.println("Creating new LESETTINGS box on "+cell+" from User Preferences because none found. Logical effort requires this box");
        SaveSettings job = new SaveSettings(cell, settings, constants);
        return true;
    }

    private static class SaveSettings extends Job {
        private Cell cell;
        private Cell settings;
        private NetlisterConstants constants;

		public SaveSettings(Cell cell, Cell settings, NetlisterConstants constants) {
            super("Clear LE Sizes", LETool.getLETool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
            this.cell = cell;
            this.settings = settings;
            this.constants = constants;
            startJob();
        }

        public boolean doIt() throws JobException {
            EditingPreferences ep = getEditingPreferences();
            Rectangle2D bounds = cell.getBounds();
            int x = (int)bounds.getMaxX();
            int y = (int)bounds.getMinY();
            Cell temp = settings.iconView();
            if (temp != null) settings = temp;
            NodeInst ni = NodeInst.makeInstance(settings, ep,new Point2D.Double(x, y), settings.getDefWidth(),
                    settings.getDefHeight(), cell);
            if (ni == null) {
                System.out.println("Could not make instance of LESETTINGS in "+cell+" to save settings.");
                return false;
            }
            ni.updateVar(ATTR_su, new Float(constants.su), ep);
            ni.updateVar(ATTR_wire_ratio, new Float(constants.wireRatio), ep);
            ni.updateVar(ATTR_epsilon, new Float(constants.epsilon), ep);
            ni.updateVar(ATTR_max_iter, new Integer(constants.maxIterations), ep);
            ni.updateVar(ATTR_gate_cap, new Float(constants.gateCap), ep);
            ni.updateVar(ATTR_alpha, new Float(constants.alpha), ep);
            ni.updateVar(ATTR_keeper_ratio, new Float(constants.keeperRatio), ep);
            ni.updateVar(ATTR_x1inverter_nwidth, new Float(constants.x1inverter_nwidth), ep);
            ni.updateVar(ATTR_x1inverter_pwidth, new Float(constants.x1inverter_pwidth), ep);
            ni.updateVar(ATTR_x1inverter_length, new Float(constants.x1inverter_length), ep);

            return true;
        }

    }

    protected static class LECellInfo extends HierarchyEnumerator.CellInfo {

        /** M-factor to be applied to size */       private float mFactor;
        /** SU to be applied to gates in cell */    private float cellsu;
        /** local settings */                       private NetlisterConstants localSettings;

        protected void leInit(NetlisterConstants constants) {

            HierarchyEnumerator.CellInfo parent = getParentInfo();

            // check for M-Factor from parent
            if (parent == null) mFactor = 1f;
            else mFactor = ((LECellInfo)parent).getMFactor();

            // check for su from parent
            if (parent == null) cellsu = constants.su;
            else cellsu = ((LECellInfo)parent).getSU();

            // get info from node we pushed into
            Nodable ni = getContext().getNodable();
            if (ni != null) {
                // get mfactor from instance we pushed into
                Variable mvar = LETool.getMFactor(ni);
                if (mvar != null) {
                    Object mval = getContext().evalVar(mvar, null);
                    if (mval != null)
                        mFactor = mFactor * VarContext.objectToFloat(mval, 1f);
                }

                // get su from instance we pushed into
                Variable suvar = ni.getParameterOrVariable(ATTR_su);
                if (suvar != null) {
                    float su = VarContext.objectToFloat(getContext().evalVar(suvar, null), -1f);
                    if (su != -1f) cellsu = su;
                }
            }

            localSettings = new NetlisterConstants(
                    cellsu,
                    constants.wireRatio,
                    constants.epsilon,
                    constants.maxIterations,
                    constants.gateCap,
                    constants.alpha,
                    constants.keeperRatio,
                    constants.x1inverter_nwidth,
                    constants.x1inverter_pwidth,
                    constants.x1inverter_length
            );
        }

        /** get mFactor */
        protected float getMFactor() { return mFactor; }

        protected float getSU() { return cellsu; }

        protected NetlisterConstants getSettings() {
            return localSettings;
        }
    }

}
