/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: NccBottomUp.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;
import java.util.ArrayList;
import java.util.Date;
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.variable.VarContext;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.ncc.basic.CellContext;
import com.sun.electric.tool.ncc.basic.CompareList;
import com.sun.electric.tool.ncc.basic.CompareLists;
import com.sun.electric.tool.ncc.basic.NccCellAnnotations;
import com.sun.electric.tool.ncc.basic.NccUtils;
import com.sun.electric.tool.ncc.netlist.PrimitiveNameToFunction;
import com.sun.electric.tool.ncc.processing.HierarchyInfo;
import com.sun.electric.tool.ncc.result.NccResult;
import com.sun.electric.tool.ncc.result.NccResults;


/** Run NCC hierarchically. By default, treat every Cell with both a layout and
 * a schematic view as a hierarchical entity. 
 * More specifically, when we are asked to compare a schematic and a layout,
 * we scan both hierarchies to identify cell groups that have one or more cells
 * in the schematic hierarchy AND one or more cells in the schematic hierarchy.
 */
public class NccBottomUp {
	
	private void prln(String s) {System.out.println(s);}

	/** Prefer a schematic reference cell because mismatch diagnostics
	 * will be easier for the user to understand. */
	private CellContext selectAndRemoveReferenceCellContext(List<CellContext> cellCtxts) {
		for (Iterator<CellContext> it=cellCtxts.iterator(); it.hasNext();) {
			CellContext cc = it.next();
			if (cc.cell.isSchematic()) {
				it.remove();
				return cc;
			}
		}
		Iterator<CellContext> it=cellCtxts.iterator(); 
		CellContext refCell = it.next();
		it.remove();
		return refCell;
	}

	private boolean hasBlackBoxAnnotation(CompareList compareList) {
		for (Iterator<CellContext> it=compareList.iterator(); it.hasNext();) {
			Cell c = it.next().cell;
			NccCellAnnotations ann = NccCellAnnotations.getAnnotations(c);
			if (ann==null) continue;
			String reason = ann.getBlackBoxReason(); 
			if (reason!=null && reason.length() > 0) {
				prln("Black box: "+NccUtils.fullName(c)+ " because "+reason);
				return true;
			} 			
		}
		return false;
	}
	private NccResult compareAndPrintStatus(Cell cell1, VarContext ctxt1, 
			                                Cell cell2, VarContext ctxt2, 
                                            HierarchyInfo hierInfo,
                                            NccOptions options,
											Aborter aborter) {
        prln("Comparing: "+NccUtils.fullName(cell1)+
             " with: "+NccUtils.fullName(cell2));
        System.out.flush();
        Date before = new Date();
        NccResult result = NccEngine.compare(cell1, ctxt1, cell2, ctxt2,  
	                                         hierInfo, options, aborter);
        //NccUtils.hang("Just dumped Ncc Globals");
//        if (options.checkNetEquivalenceMap) 
//            result.getEquivalence().regressionTest(cell1, cell2);

        Date after = new Date();

        if (!aborter.userWantsToAbort()) {
        	String timeStr = NccUtils.hourMinSec(before, after);
        	prln(result.summary(options.checkSizes)+" in "+timeStr+".");
            System.out.flush();
        }

        return result;
    }
	/** @return true if error building black box */
	private boolean compareCellsInCompareList(NccResults results,
			                                  CompareList compareList, 
			                                  HierarchyInfo hierInfo,
			                                  boolean blackBoxAnn,
			                                  PassedNcc passed,
			                                  NccOptions options,
			                                  Aborter aborter) {
		// build our own list because we need to modify it
		List<CellContext> cellCntxts = new ArrayList<CellContext>();
		// build Set of Cells because we need to exclude them from subcircuit 
		// detection
		Cell layout = null;
		
		Set<Cell> compareListCells = new HashSet<Cell>();
		for (CellContext cc : compareList) { 
		    cellCntxts.add(cc);
		    compareListCells.add(cc.cell);
		    if (cc.cell.isLayout())
		    {
		    	if (layout != null && layout.getTechnology() != cc.cell.getTechnology())
		    		System.out.println("Error: found two different layout technologies associated to layout cells. Using the last one found."); 
		    	layout = cc.cell;
		    }
		}

		// at least one should be layout cell to initialize transistor and resistor types
		// If layout is null -> use layout tech assigned to schematics (user's pref value)
		Technology layTech = (layout != null) ? layout.getTechnology() : options.defaultLayoutTech;
		
		if (layTech != null) // tech could still be null due to regression.
			PrimitiveNameToFunction.prepareToFunctionData(layTech);
		
		Cell cell = cellCntxts.iterator().next().cell;
		String grpNm = cell.getLibrary().getName()+":"+cell.getName();

		hierInfo.beginNextCompareList(grpNm);

		// notSubcircuit means check it but don't use it as a subcircuit
		if (hasNotSubcircuitAnnotation(cellCntxts))  
			hierInfo.purgeCurrentCompareList();

		CellContext refCC = selectAndRemoveReferenceCellContext(cellCntxts);
		for (CellContext thisCC : cellCntxts) {
			if (blackBoxAnn || 
			    (options.skipPassed && passed.getPassed(refCC.cell, thisCC.cell))) {
				if (hierInfo==null) continue;
				NccResult r = NccUtils.buildBlackBoxes(refCC, thisCC, hierInfo, 
						                               options, aborter);
				results.add(r);
				return !r.match();
			} else {
				hierInfo.restrictSubcktDetection(refCC, thisCC, compareListCells);

				// release storage from previous Cell pair comparisons
				if (options.operation==NccOptions.FLAT_EACH_CELL) 
					results.abandonPriorResults();
				
				NccResult r = compareAndPrintStatus(refCC.cell, refCC.context,
						                            thisCC.cell, thisCC.context, 
													hierInfo, options, aborter); 
				results.add(r);
				if (r.match())  passed.setPassed(refCC.cell, thisCC.cell);
				
				// Halt after first mismatch if that's what user wants
				if (!r.match() && options.haltAfterFirstMismatch) break;
				
				if (aborter.userWantsToAbort()) break;
			}
		}
		if (!blackBoxAnn && options.operation!=NccOptions.HIER_EACH_CELL) 
			hierInfo.purgeCurrentCompareList();
		return false;
	}
	
	private boolean hasNotSubcircuitAnnotation(List<CellContext> cellContextsInGroup) {
		for (CellContext cc : cellContextsInGroup) {
			Cell c = cc.cell;
			NccCellAnnotations anns = NccCellAnnotations.getAnnotations(c);
			if (anns==null) continue;
			String notSubcktReason = anns.getNotSubcircuitReason();
			if (notSubcktReason!=null) {
				System.out.println("For this hierarchical NCC I'm not treating "+
				                   NccUtils.fullName(c)+
				                   " as a subcircuit because "+notSubcktReason);
				return true;
			}
		}
		return false;
	}
	
	private NccResults processCompareLists(List<CompareList> compareLists,
			                               PassedNcc passed,
	                                       NccOptions options, 
										   Aborter aborter) {
		NccResults results = new NccResults();
		HierarchyInfo hierInfo = new HierarchyInfo();
		for (Iterator<CompareList> it=compareLists.iterator(); it.hasNext();) {
			CompareList compareList = it.next();

			boolean blackBoxAnn = hasBlackBoxAnnotation(compareList);

			// FLAT_TOP_CELL means do all black boxes and root cell
			if (options.operation==NccOptions.FLAT_TOP_CELL && !blackBoxAnn &&
			    it.hasNext()) continue;

			// release storage from previous Cell pair comparisons
			if (options.operation==NccOptions.FLAT_EACH_CELL) 
				results.abandonPriorResults();
			
			boolean blackBoxErr;
			if (!compareList.isSafeToCheckSizes() &&
			    options.operation!=NccOptions.FLAT_TOP_CELL &&
			    !blackBoxAnn) {
				// This cell isn't safe to compare with size 
				// checking because it is parameterized and it is instantiated 
				// more than once. Just compare without size checking but purge
				// any record of the fact that unsized comparison took place.
				// This guarantees that when NCC checks this cell's parent,
				// this cell will get flattened and therefore size checked.
				// Subtle: we purge this cell even if we aren't size checking
				// because want NCC to report the same errors with or without
				// size checking.
				NccOptions tmpOptions = new NccOptions(options);
				tmpOptions.checkSizes = false;

				blackBoxErr = 
					compareCellsInCompareList(results, compareList, hierInfo, 
						                      blackBoxAnn, passed, tmpOptions, 
						                      aborter); 

				hierInfo.purgeCurrentCompareList();
			} else {
				blackBoxErr = 
					compareCellsInCompareList(results, compareList, hierInfo, 
						                      blackBoxAnn, passed, options, 
						                      aborter); 
			}
			
			if (blackBoxErr) {
				prln(
					"Halting multiple cell NCC because of failure to build " +
					"a black box"
				);
				return results;
			}

			if (aborter.userWantsToAbort()) {
				return results;
			} else if ((!results.exportMatch() || ! results.topologyMatch()) 
					   && options.haltAfterFirstMismatch) {
				// Don't stop for size mismatches
				prln("Halting NCC after finding first mismatch");
				return results;
			}
		}
		return results;
	}

	private NccResults compareCells(CellContext cc1, CellContext cc2, 
								    PassedNcc passed, NccOptions options, 
								    Aborter aborter) {
		List<CompareList> compareLists = CompareLists.getCompareLists(cc1, cc2);
		return processCompareLists(compareLists, passed, options, aborter);
	}

	// --------------------------- public methods -----------------------------
	/** Compare the two designs: cc1 and cc2. Typically, cc1 is the schematic
	 * and cc2 is the layout. Note that NCC needs the VarContext 
	 * in order to evaluate variables such as transistor widths.
	 * @param cc1 the root Cell and VarContext of the first design.
	 * @param cc2 the root Cell and VarContext of the second design.
	 * @param passed an object that remembers which Cell pairs passed NCC the 
	 * last time the designer ran NCC.
	 * @param options all of the NCC options specified by the designer
	 * @param aborter an object that NCC queries to tell whether the designer
	 * wants to abort the NCC mid run. 
	 * @return an object that describes all the mismatches that NCC found
	 */
	public static NccResults compare(CellContext cc1, CellContext cc2, 
									 PassedNcc passed, NccOptions options,
									 Aborter aborter) {
		NccBottomUp bo = new NccBottomUp();
		return bo.compareCells(cc1, cc2, passed, options, aborter);
	}
}
