/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: CompareList.java
 *
 * Copyright (c) 2003, 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.ncc.basic;

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

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Cell.CellGroup;
import com.sun.electric.tool.Job;

/** A CompareList is a collection of Cells that are 
 * SUPPOSED to be topologically identical. NCC's job is
 * to make 
 * sure that they are, in fact, topologically identical.
 * 
 * Most of the time
 * the Cells in a CompareList belong to the same Cell group. 
 * However, NCC's
 * "joinGroup" annotation allows the designer to tell NCC to 
 * compare Cells that, for practical reasons, can't be placed into
 * an Electric CellGroup. 
 * 
 * Note that NCC needs a VarContext for a 
 * Cell in order to evaluate variables such as transistor width.
 * Therefore, the CompareList is actually
 * a list of CellContexts to compare. Also, each CompareList has a 
 * boolean that says whether it's safe to compare sizes in addition to 
 * topologies. */
public class CompareList implements Iterable<CellContext> {
	private final List<CellContext> cellContexts = new ArrayList<CellContext>();
	private boolean safeToCheckSizes;

	private boolean hasSkipAnnotation(List<CellContext> cellCtxts) {
		for (CellContext cc : cellCtxts) {
			Cell c = cc.cell;
			NccCellAnnotations ann = NccCellAnnotations.getAnnotations(c);
			if (ann==null) continue;
			String reason = ann.getSkipReason();
			if (reason!=null) {
				System.out.println("Not checking: "+NccUtils.fullName(c)+
								   " because it has a \"skipNCC\" annotation "+
								   "with the comment: "+reason);
				return true;							   
			}
		}
		return false;
	}

	/** Very Subtle: If the exact same Cell is used by both designs being 
	 * compared (for example if you're comparing 2 schematics) then
	 * cellContextsInGroup will contain two CellContexts for that Cell, each 
	 * with a different context. If there are the only 2 objects in 
	 * cellContextsInGroup (ie these 2) then this is desireable because NCC
	 * will compare the two CellContexts thereby creating the SubcircuitInfo
	 * necessary to treat the Cell hierarchically for higher level comparisons.
	 *  
	 * <p>However, if cellContextsInGroup is larger than two then the two
	 * CellContexts that refer to the same Cell are unnecessary and can, in
	 * fact, be harmful.  For example, if cellContextsInGroup contained 
	 * {A, B, B} then NCC might compare {A, B} and {A, B}. The second 
	 * comparison is redundant. Worse, it confuses a lower layer of software 
	 * which panics when it tries to create a second Subcircuit model for B. */ 
	private void purgeUnnecessaryDuplicateCells(List<CellContext> compareList) {
		Set<Cell> cells = new HashSet<Cell>();
		for (Iterator<CellContext> it=compareList.iterator(); 
		     it.hasNext() && compareList.size()>2;) {
			Cell c = (it.next()).cell;
			if (cells.contains(c))  it.remove();
			else  cells.add(c);
		}
	}

	private boolean safeToCompareSizes(List<CellContext> cellContexts, CellUsage use1, CellUsage use2) {
		for (CellContext cc : cellContexts) {
			Cell c = cc.cell;
			if (c.isSchematic()) {
				if (use1.cellIsUsed(c) && !use1.cellHasOnlyOneSize(c)) return false;
				if (use2.cellIsUsed(c) && !use2.cellHasOnlyOneSize(c)) return false;
			}
		}
		return true;
	}

	/** Collect all Cells in cell's CellGroup that are used by our designs.
	 * These must be compared. 
	 * <p>Since Java-Electric's CellGroups can't span libraries, 
	 * simulate the addition of Cells (e.g. from other libraries) to a 
	 * CellGroup using the joinGroup annotation.
	 * 
	 * <p>Tricky: If a Cell is used in two layouts or two schematics then 
	 * that Cell will occur twice in returned List, each with a 
	 * different VarContext. This has advantages and pitfalls.
	 */
	public CompareList(Cell cell, CellUsage use1, CellUsage use2,
			           Set<CellGroup> visitedGroups) {
		NccCellAnnotations ann = NccCellAnnotations.getAnnotations(cell);
		Cell.CellGroup group = cell.getCellGroup();

		// if a Cell is moving to another group then explore that group
		// instead of the Cell's current group
		if (ann!=null && ann.getGroupToJoin()!=null) group = ann.getGroupToJoin();
		
		// joinGroups may cause us to visit a group more than once
		if (group == null) return;
		if (visitedGroups.contains(group)) return;
		visitedGroups.add(group);

		// make sure we have at least one cell from use1 and one from use2
		boolean used1=false, used2=false;
		
		// add Cells with "joinGroup" annotations
		Set<CellContext> compareSet = new HashSet<CellContext>();
		Set<CellContext> additions = use1.getGroupAdditions(group);
		if (!additions.isEmpty()) {used1=true; compareSet.addAll(additions);}
		additions = use2.getGroupAdditions(group);
		if (!additions.isEmpty()) {used2=true; compareSet.addAll(additions);}
		
		// add all Cells that are actually used
		for (Iterator<Cell> gi=group.getCells(); gi.hasNext();) {
			Cell c = gi.next();
			// A cell with a joinGroup is not in this group
			NccCellAnnotations a = NccCellAnnotations.getAnnotations(c);
			if (a!=null && a.getGroupToJoin()!=null && a.getGroupToJoin()!=group)
				continue;
				
			if (use1.cellIsUsed(c)) {used1=true; compareSet.add(use1.getCellContext(c));}
			if (use2.cellIsUsed(c)) {used2=true; compareSet.add(use2.getCellContext(c));}

			// if c is root of one design, add root of other design
			if (c==use1.getRoot()) {
				compareSet.add(use2.getCellContext(use2.getRoot()));
				used1 = used2 = true;
			}
			if (c==use2.getRoot()) {
				compareSet.add(use1.getCellContext(use1.getRoot()));
				used1 = used2 = true;
			}
		}

		Job.error(compareSet.size()==0, "Cell not in its own group?");

		// make sure we have at least one cell from use1 and one from use2
		if (!(used1 && used2)) return;

		cellContexts.addAll(compareSet);
		
		purgeUnnecessaryDuplicateCells(cellContexts);

		if (hasSkipAnnotation(cellContexts)) cellContexts.clear();
		
		safeToCheckSizes = safeToCompareSizes(cellContexts, use1, use2);
		
		Job.error(compareSet.size()<2, "nothing to compare?");
	}
	/** printCells is useful for debugging
	 */
	public void printCells() {
		System.out.print("Compare List contains:=");
		for (Iterator<CellContext> it=iterator(); it.hasNext();) {
			CellContext cc = it.next();
			System.out.print(" "+cc.cell.getName());
		}
		System.out.println();
	}
	/** Get all the CellContexts in this CompareList.
	 * @return an iterator over all the CellContexts.
	 */
	public Iterator<CellContext> iterator() {return cellContexts.iterator();}
	/** Say whether or not there are any CellContexts in CompareList.
	 * @return true if there is nothing in CompareList
	 */
	public boolean empty() {return cellContexts.size()==0;}
	/** Say whether or not transistors sizes can be accurately determined
	 * for this CompareList. If there are multiple instances of Cells
	 * in this CompareList and the transistor sizes are different
	 * for each instance then it is NOT safe.
	 * @return true if we can safely check transistor sizes.
	 */
	public boolean isSafeToCheckSizes() {return safeToCheckSizes;}
}
