/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: CellUsage.java
 *
 * 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/>.
 */

package com.sun.electric.tool.ncc.basic;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.HierarchyEnumerator;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.tool.logicaleffort.LENetlister;
import com.sun.electric.tool.ncc.netlist.NccNetlist;
import com.sun.electric.tool.Job;

/** Find all Cells used in the design. Collect information about Cell usage. */
class CellUsage extends HierarchyEnumerator.Visitor {
	// map from Cell to CellContext
	private Map<Cell,CellContext> cellsInUse = new HashMap<Cell,CellContext>();
	private List<Cell> cellsInRevTopoOrder = new ArrayList<Cell>();
	private Map<Cell.CellGroup,Set<CellContext>> groupToAdditions = new HashMap<Cell.CellGroup,Set<CellContext>>();
	private Cell root;
	// Cells that have exactly one instantiation
	private Set<Cell> singleUseCells;
	// Cells with at least one LEGATE descendent
	private Set<Cell> cellsWithLeGates;

	private void prln(String s) {System.out.println(s);}
	private void processCellGroupAdditions(CellContext cellCtxt) {
		NccCellAnnotations ann = NccCellAnnotations.getAnnotations(cellCtxt.cell);
		if (ann==null) return;
		Cell.CellGroup group = ann.getGroupToJoin();
		if (group==null) return;
		Set<CellContext> additions = groupToAdditions.get(group);
		if (additions==null) {
			additions = new HashSet<CellContext>();
			groupToAdditions.put(group, additions);
		}
		additions.add(cellCtxt);
	}
	@Override
	public boolean enterCell(HierarchyEnumerator.CellInfo info) {
		Cell cell = info.getCell();
		if (root==null) root = cell;
		VarContext context = info.getContext();
		if (cellsInUse.containsKey(cell))  return false;
		CellContext cellCtxt = new CellContext(cell, context);
		cellsInUse.put(cell, cellCtxt);
		processCellGroupAdditions(cellCtxt);
		return true;
	}
	@Override
	public void exitCell(HierarchyEnumerator.CellInfo info) {
		cellsInRevTopoOrder.add(info.getCell());
	}
	@Override
	public boolean visitNodeInst(Nodable no, HierarchyEnumerator.CellInfo info) {
		return true;
	}

	private void addUse(Set<Cell> usedOnce, Set<Cell> usedMoreThanOnce, Cell c) {
		if (usedMoreThanOnce.contains(c)) return;
		if (usedOnce.contains(c)) {
			usedOnce.remove(c);
			usedMoreThanOnce.add(c);
		} else {
			usedOnce.add(c);
		}
	}
	private Set<Cell> findSingleUseCells() {
		Set<Cell> singleUseCells = new HashSet<Cell>();
		Map<Cell,Set<Cell>> cellToUsedOnce = new HashMap<Cell,Set<Cell>>();
		Set<Cell> usedMoreThanOnce = new HashSet<Cell>();
		// For each Cell in the design: c in reverse topological order
		for (Iterator<Cell> it=cellsInReverseTopologicalOrder(); it.hasNext();) {
			Cell c = it.next();
			// usedOnce holds all Cells instantiated exactly once by the design hierarchy
			// rooted at c
			Set<Cell> usedOnce = new HashSet<Cell>();
			// For each child Cell of c: child
			for (Iterator<Nodable> noIt=c.getNetlist(NccNetlist.SHORT_RESISTORS).getNodables(); noIt.hasNext();) {
				NodeProto np = noIt.next().getProto();
				if (!(np instanceof Cell)) continue;
				Cell child = (Cell) np;
				addUse(usedOnce, usedMoreThanOnce, child);

				// Subtle: If child is an icon without a schematic then child
				// won't have usedOnce set. Apparently Nodables aren't created
				// for icons with no schematic.
				if (!cellToUsedOnce.containsKey(child))  continue;

				Set<Cell> childUsedOnce = cellToUsedOnce.get(child);
				// For each descendent of child instantiated exactly once by child
				for (Cell ci : childUsedOnce) {
					addUse(usedOnce, usedMoreThanOnce, ci);
				}
			}
			cellToUsedOnce.put(c, usedOnce);
			if (!it.hasNext()) {
				// Cell c is the root.  Create set of all cells
				// in the design that are instantiated exactly once.
				singleUseCells.add(c);
				singleUseCells.addAll(usedOnce);
			}
		}
		return singleUseCells;
	}
	// I copied these tests from Spice.java. However, they
	// don't feel right to me.
	// I think the Cell should be marked LEGATE and not the instance. RKao
	private boolean isLeGate(Nodable no) {

		// The following two lines don't work because too many NodeInsts
		// of non-LE-Gates have the LEGATE attribute. This causes NCC
		// to flatten too much when size checking is turned on. This causes
		// NCC's hierarchy heuristics to behave badly.
//        return no.getVar(LENetlister.ATTR_LEGATE)!=null ||
//               no.getVar(LENetlister.ATTR_LEKEEPER)!=null;
		boolean attrLeGate = no.getParameterOrVariable(LENetlister.ATTR_LEGATE)!=null ||
			                 no.getParameterOrVariable(LENetlister.ATTR_LEKEEPER)!=null;
		boolean hasGetDrive = false;
		for (Iterator<Variable> vIt=no.getDefinedParameters(); vIt.hasNext();) {
//		for (Iterator<Variable> vIt=no.getVariables(); vIt.hasNext();) {
			Variable v = vIt.next();
			if (v.isJava()) {
				String expr = v.getObject().toString();
				// It's spelled two different ways
				if (expr.indexOf("getDrive")!=-1 || expr.indexOf("getdrive")!=-1) {
					hasGetDrive = true;
				}
			}
		}
		if (hasGetDrive && !attrLeGate) {
			prln("  Warning: instance: "+no.getName()+" in Cell: "+
				 no.getParent().describe(false)+
				 " has a variable that calls getDrive but the instance "+
				 "has no variable named LEGATE or LEKEEPER");
		}
		if (attrLeGate && !hasGetDrive) {
			// This is no longer illegal. In the new standard cell
			// logical effort methodology
			// an LEGATE indicates that the LE tool should substitute a
			// standard cell of the appropriate size.
//			prln("  Warning: instance: "+no.getName()+" in Cell: "+
//				 no.getParent().describe(false)+
//				 " has a variable named LEGATE or LEKEEPER but has"+
//				 " no variable that calls getDrive()");
		}
		return hasGetDrive;
	}
	// find Cells that have at least one LEGATE descendent
	private Set<Cell> findCellsWithLeGate() {
		Set<Cell> cellsWithLeGate = new HashSet<Cell>();
		for (Iterator<Cell> it=cellsInReverseTopologicalOrder(); it.hasNext();) {
			Cell c = it.next();
			boolean hasLeGate = false;
			for (Iterator<Nodable> noIt=c.getNetlist(NccNetlist.SHORT_RESISTORS).getNodables(); noIt.hasNext();) {
				Nodable no = noIt.next();
				if (isLeGate(no)) {hasLeGate=true; break;}
				NodeProto np = no.getProto();
				if (!(np instanceof Cell)) continue;
				Cell child = (Cell) np;
				if (cellsWithLeGate.contains(child)) {hasLeGate=true; break;}
			}
			if (hasLeGate) {
//				// Debug
				System.out.println("  Cell contains LE gate: "+c.describe(false));
				cellsWithLeGate.add(c);
			}
		}
		return cellsWithLeGate;
	}
	private boolean cellIsParameterized(Cell c) {
        return c.hasParameters();
	}
	private void postProcess() {
		singleUseCells = findSingleUseCells();
		cellsWithLeGates = findCellsWithLeGate();
	}

	private CellUsage() {}

	// ------------------ Here's the real interface ------------------------
	// static constructor
	/** Scan the design hierarchy rooted at root and construct a list of
	 * all Cells instantiated by that design.
	 * @param root the root of the design
	 */
	public static CellUsage getCellUsage(CellContext root) {
		CellUsage visitor = new CellUsage();
		HierarchyEnumerator.enumerateCell(root.cell, root.context, visitor);
		visitor.postProcess();
		return visitor;
	}
	/** See if a particular Cell is used by the design.
	 * @param cell the Cell to look for.
	 * @return true if Cell occurs in the design
	 */
	public boolean cellIsUsed(Cell cell) {return cellsInUse.containsKey(cell);}
	/** Cell has only one size in the design */
	public boolean cellHasOnlyOneSize(Cell cell) {
        return singleUseCells.contains(cell) ||
           (!cellsWithLeGates.contains(cell)) && !cellIsParameterized(cell);
	}
	/** Get an iterator over a list of all the cells used in the design. The
	 * list is sorted in reverse topological order.
	 * @return the iterator over the list
	 */ 
	public Iterator<Cell> cellsInReverseTopologicalOrder() {
		return cellsInRevTopoOrder.iterator();
	}
	/** Get a CellContext for Cell cell. If the Cell is instantiated multiple
	 * times then arbitrarily select one instance and return the CellContext 
	 * for that instance. 
	 * @param cell the cell for which we want the CellContext
	 * @return a CellContext for the instance of Cell cell
	 */
	public CellContext getCellContext(Cell cell) {
		Job.error(!cellsInUse.containsKey(cell), "cell not found");
		return cellsInUse.get(cell);
	}
	/** A joinGroup annotation indicates that NCC should treat a Cell
	 * as belonging to a particular Electric CellGroup. The method
	 * getGroupAdditions passes along information from joinGroup
	 * annotations.  
	 * @return a Set of CellContexts to add to group */
	public Set<CellContext> getGroupAdditions(Cell.CellGroup group) {
		Set<CellContext> additions = groupToAdditions.get(group);
		return additions!=null ? additions : new HashSet<CellContext>();
	}
	/** Get the root Cell of the design */
	public Cell getRoot() {return root;}
}
