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

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

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.tool.ncc.NccGlobals;
import com.sun.electric.tool.ncc.basic.NccCellAnnotations;
import com.sun.electric.tool.ncc.basic.NccUtils;
import com.sun.electric.tool.ncc.netlist.NetObject;
import com.sun.electric.tool.ncc.netlist.Port;
import com.sun.electric.tool.ncc.netlist.Wire;
import com.sun.electric.tool.ncc.result.PortReport.PortReportable;
import com.sun.electric.tool.ncc.trees.Circuit;
import com.sun.electric.tool.ncc.trees.EquivRecord;
import com.sun.electric.tool.user.ncc.ExportMismatch;
import com.sun.electric.tool.Job;

/** The ExportChecker checks the Exports of the two Cells that are
 * supposed to be equivalent. NCC checks exports in two phases.
 *
 * The first phase runs before NCC checks circuit topology.
 * In the first phase NCC checks to
 * see if the two Cells have consistent Exports. For example,
 * if the schematic has a network with Export A then
 * NCC makes sure that the layout has a network with Export A.
 * <p>
 * The second phase runs after NCC has checked the circuit topology.
 * In the second phase NCC makes sure that the Wire attached
 * to the schematic Export
 * A is topologically matched to the Wire attached to layout Export A.
 * <p>
 * Electric allows a network to have multiple exports. For example
 * a schematic network can have three Exports {A, B, C}. NCC
 * can do one of two things for networks with multiple Exports.
 * <p>
 * When I first wrote NCC I implemented a flexible comparison. If the 
 * schematic had Exports {A, B, C} then I just made sure that the
 * layout had a network with any of those Export names. For example,
 * if the layout had a network with Exports {B, C, D} then the
 * flexible comparison would say they match.
 * <p>
 * Years after the initial implementation the design group
 * noticed that commercial tools lacked this flexibility. 
 * Therefore I changed NCC to mimic the behavior of 
 * the commercial tools. If a network has more than one
 * Export attached, then NCC sorts the Export names and 
 * chooses only the first name. In that case if the
 * schematic network has Exports {A, B, C} then it does not match
 * the layout network with Exports {B, C, D}.
 * <p>
 * ExportChecker implements both flexible and strict Export
 * checking in order to maintain compatibility with most
 * of the NCC regression database. When NCC is invoked
 * interactively it uses strict Export checking. However
 * the regression scripts set NccOption.oneNamePerPort to false
 * which enables flexible Export checking.
 */
public class ExportChecker {
	/** a Port that doesn't match by name */
	private static class NoPortNameMatch {
		public final Port port;
		public final int circuit;
		public final int circuitNoMatchingPort;
		public NoPortNameMatch(Port port, int circuit, 
		                       int circuitNoMatchingPort) {
			this.port = port;
			this.circuit = circuit;
			this.circuitNoMatchingPort = circuitNoMatchingPort;
		}
	}
	private static class NoPortNameMatches {
		private List<NoPortNameMatch> unmatched = new LinkedList<NoPortNameMatch>();
		public void add(Port port, int circuit, int circuitNoMatchingPort) {
			unmatched.add(new NoPortNameMatch(port, circuit, 
			                                  circuitNoMatchingPort));
		}
		public NoPortNameMatch removeFirst() {
			Iterator<NoPortNameMatch> it = unmatched.iterator();
			if (!it.hasNext()) return null;
			NoPortNameMatch u = it.next();
			it.remove(); 
			return u;
		}
		/** prune reflexively related mismatches so we don't report
		 * "port1 matches port2" and "port2 matches port1" */
		public void removeMismatches(Port port1, int circuit1, 
		                             Port port2, int circuit2) {
			for (Iterator<NoPortNameMatch> it=unmatched.iterator(); it.hasNext();) {
				NoPortNameMatch u = it.next();
				if ((u.port==port1 && u.circuitNoMatchingPort==circuit2) ||
				    (u.port==port2 && u.circuitNoMatchingPort==circuit1)) { 
					it.remove();
				}
			}
		}
	}
	
	private NccGlobals globals;
	/** The 0th element is null. The ith element maps every port of the 0th
	 * design to the equivalent port of the ith design. */ 
	private HashMap<Port,Port>[] equivPortMaps;
	/** List of ports that can't be matched by name. This list is used to 
	 * suggest possible repairs to the design. */ 
	private NoPortNameMatches noPortNameMatches = new NoPortNameMatches();

	private Port[][] getPortsForEachCell() {
		int numCells = globals.getNumNetlistsBeingCompared();
		Port[][] portsPerCell = new Port[numCells][];

		EquivRecord portRec = globals.getPorts();
		if (portRec==null) {
			// special case: every Cell being compared has zero Ports
			for (int i=0; i<numCells; i++)  portsPerCell[i]=new Port[0];
		} else {
			globals.error(!portRec.isLeaf(),"globals ports must hold circuits");
			int cellNdx = 0;
			for (Iterator<Circuit> it=portRec.getCircuits(); it.hasNext(); cellNdx++) { 
				Circuit ckt = it.next();
				portsPerCell[cellNdx] = new Port[ckt.numNetObjs()];
				int portNdx = 0;
				for (Iterator<NetObject> pit=ckt.getNetObjs(); pit.hasNext(); portNdx++) {
					portsPerCell[cellNdx][portNdx] = (Port)pit.next();
				}
			}
		}
		return portsPerCell;		
	}
	
	
	private HashMap<String,Port> mapFromExportNameToPort(Port[] ports) {
		HashMap<String,Port> map = new HashMap<String,Port>();
		for (int portNdx=0; portNdx<ports.length; portNdx++) {
			Port p = ports[portNdx];
			for (Iterator<String> itN=p.getExportNames(); itN.hasNext();) {
				map.put(itN.next(), p);
			}
		}
		return map;
	}
	private void prln(String s) {System.out.println(s);}
	//private void pr(String s) {System.out.print(s);}

	private void printOneToManyError(String design1, String exports1,
								     String design2, Set<Port> exports2) {
		prln("  A single network in: "+design1+" has Exports with names that "+
			 "match multiple Exports in: "+design2);
		prln("  However, the "+design2+
		     " Exports are attached to more than one network.");
		prln("    The "+design1+" Exports: "+exports1);
		for (Port p2 : exports2) {
			prln("      matches the "+design2+" Exports: "+p2.exportNamesString());
		}
	}
	/** For each port, p1, in Circuit ckt1 make sure there is exactly one port 
	 * circuit ckt2 that shares any of p1's export names. Return a map from 
	 * ckt1 ports to matching ckt2 ports. If p1 has no matching port in ckt2
	 * then record the mismatch in noPortNameMatches. */  
	private boolean matchPortsByName(HashMap<Port,Port> p1ToP2, Port[][] portsPerCell, 
								     String[] designNames, int ckt1, int ckt2) {
		boolean match = true;
		HashMap<String,Port> exportName2ToPort2 = mapFromExportNameToPort(portsPerCell[ckt2]);
		if (p1ToP2==null)  p1ToP2=new HashMap<Port,Port>();
		Port[] ckt1Ports = portsPerCell[ckt1];
        VarContext rootContexts[] = globals.getRootContexts();
        Cell rootCells[] = globals.getRootCells();        
		for (int portNdx=0; portNdx<ckt1Ports.length; portNdx++) {
			Port p1 = ckt1Ports[portNdx];
			Set<Port> p2ports = new HashSet<Port>();
			for (Iterator<String> itN=p1.getExportNames(); itN.hasNext();) {
				String exportNm1 = itN.next();
				Port p = exportName2ToPort2.get(exportNm1);
				if (p!=null)  p2ports.add(p);
		    }

			if (p2ports.size()==0) {
				prln("  In "+designNames[ckt1]+
				     " the network with Exports: "+p1.exportNamesString()+
					 "    matches no Export with the same name in: "+
				     designNames[ckt2]);
				noPortNameMatches.add(p1, ckt1, ckt2);
				match = false;
                ExportMismatch.MultiMatch em = new ExportMismatch.MultiMatch();
                em.setNames(designNames[ckt1], designNames[ckt2]);
                em.setCells(rootCells[ckt1], rootCells[ckt2]);
                em.setContexts(rootContexts[ckt1], rootContexts[ckt2]);
                em.add(0, p1);
                em.setValidOnlyWhenTopologyMismatch(true);
                globals.getNccGuiInfo().addExportMismatch(em);
			} else if (p2ports.size()==1){
				p1ToP2.put(p1, p2ports.iterator().next());
			} else {
				printOneToManyError(designNames[ckt1], p1.exportNamesString(),
						            designNames[ckt2], p2ports);
				match = false;
                ExportMismatch.MultiMatch em = new ExportMismatch.MultiMatch();
                em.setNames(designNames[ckt1], designNames[ckt2]);
                em.setCells(rootCells[ckt1], rootCells[ckt2]);
                em.setContexts(rootContexts[ckt1], rootContexts[ckt2]);
                em.add(0, p1);
                Set<PortReportable> p2portReportables = new HashSet<PortReportable>();
                p2portReportables.addAll(p2ports);
                em.add(1, p2portReportables);
                globals.getNccGuiInfo().addExportMismatch(em);                
			}
		}
		return match;
	}

	private SubcircuitInfo getInfoForReferenceCell(Cell refCell, HierarchyInfo hierInfo) {	
		SubcircuitInfo refInfo = hierInfo.getSubcircuitInfo(refCell);
		
		// If the reference Cell has already been compared to another Cell
		// then return existing SubcircuitInfo
		if (refInfo!=null) return refInfo;
		
		// This is the first time the first Cell is used as a reference. Create
		// a SubcircuitInfo for it. This SubcircuitInfo will be a reference for
		// all the other Cells this one is compared with.
		Port[] refCktPorts = getFirstCktPorts();
		refInfo = new SubcircuitInfo(hierInfo.currentSubcircuitName(),
									 hierInfo.currentSubcircuitID(), 
									 refCktPorts);
		hierInfo.addSubcircuitInfo(refCell, refInfo);

		return refInfo;
	}

	private Port[] getFirstCktPorts() {
		Port[][] portsPerCell = getPortsForEachCell();
		return portsPerCell[0];
	}

	private Map<String,Integer> mapExportNameToPortIndex(SubcircuitInfo refCellInfo, 
										 Map<Port,Port> equivPortMap) {
		Map<String,Integer> exportNameToPortIndex = new HashMap<String,Integer>();
		Port[] firstCktPorts = getFirstCktPorts();
		for (int i=0; i<firstCktPorts.length; i++) {
			Port firstCktPort = firstCktPorts[i];
			String firstCktPortName = 
				firstCktPort.getExportNames().next();
			int portNdx = refCellInfo.getPortIndex(firstCktPortName);
			Port equivPort = equivPortMap.get(firstCktPort);
			// if export names don't match then there may be no equivPort
			if (equivPort==null) continue; 
			for (Iterator<String> it=equivPort.getExportNames(); it.hasNext();) {
				exportNameToPortIndex.put(it.next(), new Integer(portNdx));
			}
		}
		return exportNameToPortIndex;
	}

	private Circuit getNthCircuit(EquivRecord er, int nth) {
		Job.error(!er.isLeaf(), "only leaf EquivRecords have Circuits!");
		Iterator<Circuit> it = er.getCircuits();
		for (int i=0; i<nth; i++) {
			Job.error(!it.hasNext(), "EquivRec has no Circuit at index: "+nth);
			it.next(); 
		}
		Job.error(!it.hasNext(), "EquivRec has no Circuit at index: "+nth);
		return it.next();
	}

	/** Find a possible matching Wire or Port for lonely in the nth 
	 * circuit by examining lonely's Wire's equivalence record.   
	 * @return the matching Port or Wire. Return null if no unique matching 
	 * Port or Wire can be found */ 
	private Object findMatchingPortOrWire(Port lonely, int nth) {
		Wire lonelyWire = lonely.getWire();
		Circuit lonelyCkt = lonelyWire.getParent();
		if (lonelyCkt.numNetObjs()!=1) return null;
		EquivRecord equivRecord = lonelyCkt.getParent();
		Circuit nthCircuit = getNthCircuit(equivRecord, nth);
		if (nthCircuit.numNetObjs()!=1) return null;
		Wire wire = (Wire) nthCircuit.getNetObjs().next();
		Port port = wire.getPort();
		return port!=null ? (Object)port : (Object)wire;
	}

	private String getDescription(Object portOrWire) {
		if (portOrWire instanceof Wire) {
			return "network: "+((Wire)portOrWire).getName();
		} else {
			Job.error(!(portOrWire instanceof Port), "not a Wire?");
			return "network with Exports: "+
				   ((Port)portOrWire).exportNamesString();
		}
	}

	private void markPortForRenaming(Port p, NccCellAnnotations ann) {
		for (Iterator<String> it=p.getExportNames(); it.hasNext();) {
			String name = it.next();
			if (ann.renameExport(name)) {p.setToBeRenamed();  return;}
		}
	}
	
	private void markPortsForRenaming(Cell cell, Port[] ports) {
		NccCellAnnotations ann = NccCellAnnotations.getAnnotations(cell);
		if (ann==null) return;
		for (int portNdx=0; portNdx<ports.length; portNdx++) 
			markPortForRenaming(ports[portNdx], ann);
	}
    private void printNewPortNames(Port[] ports, Cell rootCell, int cktIndex) {
    	boolean printedHeader = false;
    	int otherCktIndex = cktIndex==0 ? 1 : 0;
    	for (int portNdx=0; portNdx<ports.length; portNdx++) {
    		Port p = ports[portNdx];
    		if (p.getToBeRenamed()) {
    			if (!printedHeader) {
    				prln("  Attempting to find better names for Exports in Cell: "+
    				      NccUtils.fullName(rootCell)); 
    				printedHeader = true;
    			}
				Object o = findMatchingPortOrWire(p, otherCktIndex);
    			prln("    "+ p.exportNamesString()+" -> "+
				     getDescription(o));
    		}
    	}
    }
    
    private void printNewNamesForPortsThatTheUserWantsUsToRename() {
    	Cell[] rootCells = globals.getRootCells();
    	Port[][] portsPerCell = getPortsForEachCell();
    	for (int cellNdx=0; cellNdx<portsPerCell.length; cellNdx++)  
    		printNewPortNames(portsPerCell[cellNdx], rootCells[cellNdx], 
    				          cellNdx);
    }

	/** For each port not matched by name, suggest a possible match */
    private void suggestMatchForPortsThatDontMatchByName() {
		String[] rootCellNames = globals.getRootCellNames();
		boolean printedHeader = false;
        VarContext rootContexts[] = globals.getRootContexts();
        Cell rootCells[] = globals.getRootCells();
		for (NoPortNameMatch no=noPortNameMatches.removeFirst(); no!=null; 
		    no=noPortNameMatches.removeFirst()) {
			// skip Ports for which the user has already requested a new name
			if (no.port.getToBeRenamed()) continue;
			Object o = findMatchingPortOrWire(no.port, no.circuitNoMatchingPort);
			if (o==null) {
                ExportMismatch.MultiMatch em = new ExportMismatch.MultiMatch();
                em.setNames(rootCellNames[no.circuit], rootCellNames[no.circuitNoMatchingPort]);
                em.setCells(rootCells[no.circuit], rootCells[no.circuitNoMatchingPort]);
                em.setContexts(rootContexts[no.circuit], rootContexts[no.circuitNoMatchingPort]);
                em.add(0, no.port);
                globals.getNccGuiInfo().addExportMismatch(em);                
                continue;
            }
			if (!printedHeader) {
				printedHeader = true;
				prln("  The following list suggests possible matches for "+
					 "Exports that failed to match by name.");
			}
			prln("    in Cell "+rootCellNames[no.circuit]+
				 " the network with Exports: "+
				 no.port.exportNamesString()+
				 " might match in Cell "+rootCellNames[no.circuitNoMatchingPort]+
				 " the "+getDescription(o));
			if (o instanceof Port) {
				noPortNameMatches.removeMismatches(no.port, no.circuit, (Port)o, 
				                                   no.circuitNoMatchingPort);
            }
            ExportMismatch.NameMismatch esm = new ExportMismatch.NameMismatch();
            esm.setNames(rootCellNames[no.circuit], rootCellNames[no.circuitNoMatchingPort]);
            esm.setCells(rootCells[no.circuit], rootCells[no.circuitNoMatchingPort]);
            esm.setContexts(rootContexts[no.circuit], rootContexts[no.circuitNoMatchingPort]);
            esm.setFirstExport(no.port);
            esm.setSuggestion((NetObject)o);
            globals.getNccGuiInfo().addExportMismatch(esm);                
		}
		if (printedHeader) prln("");
    }

	// -------------------------- public methods ------------------------------    

	public ExportChecker(NccGlobals globals) {this.globals=globals;}
	
	public void markPortsForRenaming() {
		Cell[] rootCells = globals.getRootCells();
		Port[][] portsPerCell = getPortsForEachCell();
		for (int cellNdx=0; cellNdx<portsPerCell.length; cellNdx++) 
			markPortsForRenaming(rootCells[cellNdx], portsPerCell[cellNdx]);
	}
  
	/** match Exports by name. NCC runs this first phase before
	 * topology matching in order to 
	 * give user early warning about Export name inconsistencies. */
	public boolean matchByName() {
		int numCkts = globals.getNumNetlistsBeingCompared();
		String[] rootCellNames = globals.getRootCellNames();
		Port[][] portsPerCell = getPortsForEachCell();
		equivPortMaps = new HashMap[numCkts];
		boolean match=true;
		for (int i=1; i<numCkts; i++) {
			match &= matchPortsByName(null, portsPerCell, rootCellNames, i, 0);
			HashMap<Port,Port> p0ToPi = new HashMap<Port,Port>();
			match &= matchPortsByName(p0ToPi, portsPerCell, rootCellNames, 0, i);
			equivPortMaps[i] = p0ToPi;
		}
		return match;
	}

	/** Gather information that will allow hierarchical netlist comparison 
	 * at higher level to treat me as a subcircuit. */	
	public void saveInfoNeededToMakeMeASubcircuit(HierarchyInfo hierInfo) {
		if (hierInfo==null) return;

		Cell[] rootCells = globals.getRootCells();
		Cell refCell = globals.getRootCells()[0];

		SubcircuitInfo refCellInfo = getInfoForReferenceCell(refCell, hierInfo);		
		
		// It is reasonable for two different designs to share some
		// cells. It's also reasonable to compare the same schematic Cell 
		// with two different VarContexts. When this happens, don't create 
		// two SubcircuitInfo's for the same cell.
		Set<Cell> doneCells = new HashSet<Cell>();
		doneCells.add(refCell);
	
		for (int i=1; i<equivPortMaps.length; i++) {
			if (doneCells.contains(rootCells[i])) continue;
			doneCells.add(rootCells[i]);
			Map<String,Integer> exportNameToPortIndex = 
				mapExportNameToPortIndex(refCellInfo, equivPortMaps[i]);
			SubcircuitInfo subInf = 
				new SubcircuitInfo(refCellInfo, exportNameToPortIndex);
			hierInfo.addSubcircuitInfo(rootCells[i], subInf);
		}
	}

	/** Check that Exports with matching names are on equivalent nets.
	 * NCC runs this second phase after it has performed topology matching 
	 * @return true if equivalent. */ 
    public boolean ensureExportsWithMatchingNamesAreOnEquivalentNets() {
    	boolean match=true;
    	
		String[] rootCellNames = globals.getRootCellNames();
        VarContext rootContexts[] = globals.getRootContexts();
        Cell rootCells[] = globals.getRootCells();        
    	for (int i=1; i<equivPortMaps.length; i++) {
    		HashMap<Port,Port> portToPort = equivPortMaps[i];
    		for (Port p0 : portToPort.keySet()) {
    			Port pn = portToPort.get(p0);
    			// skip Ports that the user wants us to rename
    			if (p0.getToBeRenamed() || pn.getToBeRenamed()) continue;
    			Wire w0 = p0.getWire();
    			Wire wn = pn.getWire();
				EquivRecord er0 = w0.getParent().getParent();
				EquivRecord ern = wn.getParent().getParent();
				if (er0!=ern) {
					prln("  Exports that match by name aren't on equivalent"+
					     " networks");
					prln("    Cell1: "+rootCellNames[0]);
					prln("    Exports1: "+p0.exportNamesString());
					prln("    Cell2: "+rootCellNames[i]);
					prln("    Exports2: "+pn.exportNamesString());
					Object portOrWire = findMatchingPortOrWire(p0, i);
					if (portOrWire!=null) 
						prln("    However the Cell1 network appears to match Cell2's: "+
						     getDescription(portOrWire));
					prln("");
					match = false;
                    ExportMismatch.TopologyMismatch esm = new ExportMismatch.TopologyMismatch();
                    esm.setNames(rootCellNames[0], rootCellNames[i]);
                    esm.setCells(rootCells[0], rootCells[i]);
                    esm.setContexts(rootContexts[0], rootContexts[i]);
                    esm.setFirstExport(p0);
                    esm.setSecondExport(pn);
                    if(portOrWire != null)
                        esm.setSuggestion((NetObject)portOrWire);
                    globals.getNccGuiInfo().addExportMismatch(esm);                    
				}
    		}
    	}
    	return match;
    }
    /** If the topological comparison is successful, then both circuits
     * match. NCC checks to see if the Export names are consistent with
     * the match. If not, then NCC will try to suggest how the 
     * designer should rename Exports. 
     */
    public void suggestPortMatchesBasedOnTopology() {
    	printNewNamesForPortsThatTheUserWantsUsToRename();
    	suggestMatchForPortsThatDontMatchByName();
    }
}
