/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: CachedCell.java
 * Written by Jonathan Gainsley.
 *
 * Copyright (c) 2004, Static Free Software. 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.Cell;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.variable.Variable;

import java.io.PrintStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class CachedCell {

    /** source cell */                  private Cell cell;
    /** map of Nodable to LENodable */  private Map<Nodable,LENodable> lenodables;
    /** map of CachedCell instances */  private Map<Nodable,CellNodable> cellnodables; // key: Nodable, Object: CellNodable
    /** this cell or subcells contains le gates */  private boolean containsSizableGates;
    /** local networks */               private Map<Network,LENetwork> localNetworks; // key: Network, Object: LENetwork
    /** if this cell's nodes and subcell's nodes can be fully evaluated as if this cell was the top level */
                                        private Boolean contextFree;
    /** list of all cached nodables */  //private List allCachedNodables;

    private static final boolean DEBUG = false;

    protected static class CellNodable {
        Nodable no;
        CachedCell subCell;
        Variable mfactorVar;
    }

    protected CachedCell(Cell cell, Netlist netlist) {
        this.cell = cell;
        lenodables = new HashMap<Nodable,LENodable>();
        localNetworks = new HashMap<Network,LENetwork>();
        cellnodables = new HashMap<Nodable,CellNodable>();
        //allCachedNodables = new ArrayList();
        containsSizableGates = false;
        contextFree = null;
        if (netlist != null) {
            // populate local networks
            for (Iterator<Network> it = netlist.getNetworks(); it.hasNext(); ) {
                Network jnet = (Network)it.next();
                LENetwork net = new LENetwork(jnet.describe(false));
                if (localNetworks.containsKey(jnet))
                    System.out.println("Possible hashmap conflict in localNetworks!");
                localNetworks.put(jnet, net);
            }
        }
    }

    protected boolean isContainsSizableGates() { return containsSizableGates; }

    protected LENodable getLENodable(Nodable no) { return lenodables.get(no); }
    protected Iterator getLENodables() { return lenodables.values().iterator(); }

    protected CellNodable getCellNodable(Nodable no) { return cellnodables.get(no); }
    protected Iterator getCellNodables() { return cellnodables.values().iterator(); }

    protected Map<Network,LENetwork> getLocalNetworks() { return localNetworks; }

    //protected List getAllCachedNodables() { return allCachedNodables; }

    /**
     * Adds instance of LENodable to this cached cell.
     * @param no the corresponding nodable
     * @param leno the LENodable
     */
    protected void add(Nodable no, LENodable leno) {
        if (leno.isLeGate()) containsSizableGates = true;
        // hook up gate to local networks
        for (Iterator<LEPin> it = leno.getPins().iterator(); it.hasNext(); ) {
            LEPin pin = (LEPin)it.next();
            Network jnet = pin.getNetwork();
            LENetwork net = localNetworks.get(jnet);
 /*           if (net == null) {
                net = new LENetwork(jnet.describe());
                localNetworks.put(jnet, net);
            }*/
            net.add(pin);
        }

        if (lenodables.containsKey(no))
            System.out.println("Possible hash map conflict in lenodables!");
        lenodables.put(no, leno);
        //allCachedNodables.add(leno);
    }

    /**
     * Adds instance of cached cell "subCell" to this cached cell. This needs to be
     * called from Visitor.exitCell of subcell, because it requires subCell's CellInfo.
     * @param no the nodable for subcell (in parent)
     * @param info the parent info
     * @param subCell the subcell cached cell
     * @param subCellInfo the subcell CellInfo
     */
    protected void add(Nodable no, LENetlister2.LECellInfo info,
                       CachedCell subCell, LENetlister2.LECellInfo subCellInfo, LENetlister.NetlisterConstants constants) {
        CellNodable ceno = new CellNodable();
        ceno.no = no;
        ceno.subCell = subCell;
        ceno.mfactorVar = LETool.getMFactor(no);
        if (cellnodables.containsKey(no))
            System.out.println("Possible hash map conflict in cellnodables!");
        cellnodables.put(no, ceno);
        if (subCell.isContainsSizableGates()) {
            containsSizableGates = true;
            // don't bother caching cells with sizeable gates, they get tossed out later
            return;
        }
        if (!subCell.isContextFree(constants)) {
            // if the subcell in not context free, make a copy of it in this cell with
            // the context. This cell can be instantiated in some other cell and eventually
            // become context free, however then all the instances are tied to that context.
            // If they were simply linked, that context could conflict with some context elsewhere
            // in the hierarchy where the subcell appears.
            subCell = subCell.copy();
            ceno.subCell = subCell;
        }

        //System.out.println("Importing to "+cell.describe()+" from "+no.getName()+":");
        // map subCell networks to this cell's networks through global network id's
        for (Iterator<Map.Entry<Network,LENetwork>> it = subCell.getLocalNetworks().entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<Network,LENetwork> entry = (Map.Entry<Network,LENetwork>)it.next();
            Network subJNet = (Network)entry.getKey();
            LENetwork subLENet = (LENetwork)entry.getValue();
            Network localJNet = subCellInfo.getNetworkInParent(subJNet);
            if (localJNet == null) continue;
            LENetwork net = localNetworks.get(localJNet);
            if (net == null) {
                net = new LENetwork(localJNet.describe(false));
                localNetworks.put(localJNet, net);
            }
            net.add(subLENet);
            if (DEBUG) {
                if (!net.getName().equals("vdd") && !net.getName().equals("gnd")) {
                    System.out.println("  Added to "+net.getName() +" in "+cell.describe(false)+": "+subLENet.getName()+" from "+subCell.cell.describe(false));
                    System.out.println("     subcell="+subCell.cell.describe(false)+" subnet="+subLENet.getName()+": ");
                    subLENet.print();
                    System.out.println("     result: localcell="+cell.describe(false)+" localnet="+net.getName()+": ");
                    net.print();
                }
            }
        }
    }

    protected boolean isContextFree(LENetlister.NetlisterConstants constants) {
        if (contextFree == null) {
            // this cell has not yet been evaluated, do it now
            if (isContainsSizableGates()) {
                contextFree = Boolean.valueOf(false);
            } else {
                if (DEBUG) System.out.println("**** Checking if "+cell+" is context free");
                boolean cf = isContextFreeRecurse(VarContext.globalContext, 1f, constants);
                contextFree = Boolean.valueOf(cf);
            }
            if (DEBUG) System.out.println(">>>> "+cell+" set context free="+contextFree);
        }
        return contextFree.booleanValue();
    }

    private boolean isContextFreeRecurse(VarContext context, float mfactor, LENetlister2.NetlisterConstants constants) {
        // check LENodables
        for (Iterator<LENodable> it = lenodables.values().iterator(); it.hasNext(); ) {
            LENodable leno = (LENodable)it.next();
            boolean b = leno.setOnlyContext(context, null, mfactor, 0, constants);
            if (DEBUG) System.out.println("  gate "+leno.getName()+" cached: "+b+", leX="+leno.leX+", ID="+leno.hashCode());
            if (!b) return false;
        }
        // check cached cell instances
        for (Iterator<Map.Entry<Nodable,CellNodable>> it = cellnodables.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<Nodable,CellNodable> entry = (Map.Entry<Nodable,CellNodable>)it.next();
            Nodable no = (Nodable)entry.getKey();
            CellNodable ceno = (CellNodable)entry.getValue();
            // see if any mfactor var is evaluatable
            float subCellMFactor = mfactor;
            if (ceno.mfactorVar != null) {
                Object retVal = context.evalVar(ceno.mfactorVar);
                if (retVal == null) {
                    if (DEBUG) System.out.println("  subcell "+ceno.no.getName()+" has Mfactor that cannot be evaluated");
                    return false;
                }
                subCellMFactor *= VarContext.objectToFloat(retVal, 1);
            }
            boolean b = ceno.subCell.isContextFree(constants);
            if (b) {
                if (DEBUG) System.out.println("  subcell "+ceno.no.getName()+" is context free");
                continue;
            }
            if (!ceno.subCell.isContextFreeRecurse(context.push(no), subCellMFactor, constants)) {
                if (DEBUG) System.out.println("  subcell "+ceno.no.getName()+" is NOT recursively context free");
                return false;
            } else
                if (DEBUG) System.out.println("  subcell "+ceno.no.getName()+" is recursively context free");
        }
        return true;
    }

    /**
     * Create a copy of the cached cell. This also recursively creates
     * copies of all subcells (cached cells).
     * @return the copy
     */
    private CachedCell copy() {
        CachedCell copy = new CachedCell(cell, null);
        // copy all subcell structures
        Map<LENetwork,LENetwork> origSubNetsToCopySubNets = new HashMap<LENetwork,LENetwork>();
        for (Iterator<Map.Entry<Nodable,CellNodable>> it = cellnodables.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<Nodable,CellNodable> entry = (Map.Entry<Nodable,CellNodable>)it.next();
            Nodable no = (Nodable)entry.getKey();
            CellNodable ceno = (CellNodable)entry.getValue();
            CellNodable cenoCopy = new CellNodable();
            cenoCopy.no = ceno.no;
            cenoCopy.mfactorVar = ceno.mfactorVar;
            cenoCopy.subCell = ceno.subCell.copy();
            if (copy.cellnodables.containsKey(no))
                System.out.println("Possible hash map conflict in copy.cellnodables!");
            copy.cellnodables.put(no, cenoCopy);
            // build table of original subnets to copied subnets, so we
            // can update subnet links when we copy local networks
            for (Iterator<Map.Entry<Network,LENetwork>> nit = ceno.subCell.localNetworks.entrySet().iterator(); nit.hasNext(); ) {
                Map.Entry<Network,LENetwork> netentry = (Map.Entry<Network,LENetwork>)nit.next();
                Network jnet = (Network)netentry.getKey();
                LENetwork origNet = (LENetwork)netentry.getValue();
                LENetwork copyNet = cenoCopy.subCell.localNetworks.get(jnet);
                origSubNetsToCopySubNets.put(origNet, copyNet);
            }
        }
        // create new networks
        // add on subnets, because they are the connectivity to subcells.
        for (Iterator<Map.Entry<Network,LENetwork>> it = localNetworks.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<Network,LENetwork> entry = (Map.Entry<Network,LENetwork>)it.next();
            Network jnet = (Network)entry.getKey();
            LENetwork net = (LENetwork)entry.getValue();
            LENetwork netCopy = new LENetwork(net.getName());
            for (Iterator<LENetwork> nit = net.getSubNets(); nit.hasNext(); ) {
                LENetwork subnet = (LENetwork)nit.next();
                // find copy of subnet in cellnodables copy
                LENetwork copySubNet = origSubNetsToCopySubNets.get(subnet);
                netCopy.add(copySubNet);
            }
            if (copy.localNetworks.containsKey(jnet))
                System.out.println("Possible hashmap conflict in copy.localNetworks!");
            copy.localNetworks.put(jnet, netCopy);
        }
        // copy all lenodables: this sets pins of local networks.
        for (Iterator<LENodable> it = lenodables.values().iterator(); it.hasNext(); ) {
            LENodable leno = (LENodable)it.next();
            LENodable lenoCopy = leno.copy();
            copy.add(leno.getNodable(), lenoCopy);
        }
        copy.containsSizableGates = containsSizableGates;
        copy.contextFree = contextFree;
        //copy.allCachedNodables.addAll(allCachedNodables);
        return copy;
    }

    /**
     * Print the contents of the Cached cell
     * @param indent
     */
    protected void printContents(String indent, PrintStream out) {
        out.println(indent+"CachedCell "+cell.describe(true)+" contents:");
        for (Iterator<LENodable> it = lenodables.values().iterator(); it.hasNext(); ) {
            LENodable leno = (LENodable)it.next();
            out.println(leno.printOneLine(indent+"  "));
        }
        for (Iterator<Map.Entry<Network,LENetwork>> it = localNetworks.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<Network,LENetwork> entry = (Map.Entry<Network,LENetwork>)it.next();
            /* Network jnet =  (Network)*/ entry.getKey();
            LENetwork net = (LENetwork)entry.getValue();
            net.print(indent+"  ", out);
        }
        for (Iterator<Map.Entry<Nodable,CellNodable>> it = cellnodables.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<Nodable,CellNodable> entry = (Map.Entry<Nodable,CellNodable>)it.next();
            /* Nodable no = (Nodable) */ entry.getKey();
            CellNodable ceno = (CellNodable)entry.getValue();
            //ceno.subCell.printContents(indent+"  ", out);
            boolean subCachable = ceno.subCell.isContextFree(null);
            System.out.println(indent+indent+"contains subCachedCell for "+ceno.subCell.cell+" ("+
                    (subCachable?"cachable":"not cachable")+")");
        }
    }
}
