/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: EquivRecord.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/>.
 */
// updated to new view of trees, 16 Jan 2004
// Annotated by Ivan Sutherland, 30 January 2004

package com.sun.electric.tool.ncc.trees;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import com.sun.electric.tool.ncc.NccGlobals;
import com.sun.electric.tool.ncc.lists.LeafList;
import com.sun.electric.tool.ncc.lists.RecordList;
import com.sun.electric.tool.ncc.netlist.NetObject;
import com.sun.electric.tool.ncc.processing.LocalPartitionWires;
import com.sun.electric.tool.ncc.result.EquivRecReport.EquivRecReportable;
import com.sun.electric.tool.ncc.result.NetObjReport.NetObjReportable;
import com.sun.electric.tool.ncc.strategy.Strategy;
import com.sun.electric.tool.Job;

/** Leaf EquivRecords hold Circuits. Internal EquivRecords hold offspring.
 * Every EquivRecord is assigned a pseudo random code at birth which it 
 * retains for life. 
 * <p>
 * A Leaf EquivRecord is "balanced" if all Circuits have the same number of 
 * NetObjects. "Matched" means balanced with each Circuit having one 
 * NetObject. "Mismatched" means unbalanced with some some Circuit having 
 * no NetObject. "Active" means not matched and not mismatched. */
public class EquivRecord implements EquivRecReportable {
	/** Get all NetObjects contained by an EquivRecord subtree. 
	 * The NetObjects found in matched EquivRecords are collected 
	 * separately from NetObjects found in not-matched EquivRecords. */
	private static class GetNetObjs extends Strategy {
		private int numDesigns = -1;
//		private NetObject.Type  type = null;
		private final List<List<NetObject>> matches = 
									new ArrayList<List<NetObject>>();
		private final List<List<NetObject>> notMatches = 
									new ArrayList<List<NetObject>>();
		
//		private void checkType(NetObject n) {
//			NetObject.Type t = n.getNetObjType();
//			if (type==null) type=t;
//			LayoutLib.error(type!=t, "different types in same EquivRecord");
//			LayoutLib.error(type!=NetObject.Type.PART &&
//					        type!=NetObject.Type.WIRE,
//					        "expecting only Parts or Wires");
//		}
		
		private void appendNetObjsFromCircuit(List<List<NetObject>> lists, 
				                              EquivRecord er) {
			int i=0;
			for (Iterator<Circuit> itC=er.getCircuits(); itC.hasNext(); i++) {
				Circuit ckt = (Circuit) itC.next();
				for (Iterator<NetObject> itN=ckt.getNetObjs(); itN.hasNext();) {
					lists.get(i).add(itN.next());
				}
			}
			Job.error(i!=numDesigns, "wrong number of circuits");
		}
		
		@Override public LeafList doFor(EquivRecord er) {
			if (er.isLeaf()) {
				if (numDesigns==-1) {
					// We've encountered the first leaf EquivRec. Now we
					// can initialize the Lists.
					numDesigns = er.numCircuits();
					for (int i=0; i<numDesigns; i++) {
						matches.add(new ArrayList<NetObject>());
						notMatches.add(new ArrayList<NetObject>());
					}
				}
				appendNetObjsFromCircuit(er.isMatched() ? matches : notMatches, er);
				return new LeafList();
			} else {
				return super.doFor(er);
			}
		}
		
		// ------------------------- intended interface -----------------------
		public GetNetObjs(EquivRecord er) {
			super(null);
			doFor(er);
		}
		
		/**  @return one list per Circuit. Each list contains all the matched
		 * NetObjects in that circuit. For all these lists, elements at the
		 * same index matched. */
		public List<List<NetObject>> getMatchedNetObjs() {return matches;}
		
		/** @return one list per Circuit. Each list contains all not-matched
		 * NetObjects in that circuit. */
		public List<List<NetObject>> getNotMatchedNetObjs() {
			return notMatches;
		}
		//public boolean hasParts() {return type==NetObject.Type.PART;}
	}

	// points toward root 	             
	private EquivRecord parent;
	
	// the immutable random code
	private int randCode;
	
	// int that distinguished this Record
	private int value;
	
	// comment describing the metric used to partition my parent into my 
	// siblings and me
	private String partitionReason;
	
	// description of Wire PortInst connections
	private LocalPartitionWires.Signature wireSignature; 
	 
	// At any given time only one of this lists is non-null
	private RecordList offspring;
	private List<Circuit> circuits;

	private static void error(boolean pred, String msg) {
		Job.error(pred, msg);
	}

	/** For a leaf record, apply strategy to build one Map for each Circuit
	 * @param js
	 * @return list of Maps */
	private ArrayList<HashMap<Integer,List<NetObject>>> getOneMapPerCircuit(Strategy js) {
		ArrayList<HashMap<Integer,List<NetObject>>> mapPerCkt = new ArrayList<HashMap<Integer,List<NetObject>>>();
		for (Iterator<Circuit> it=getCircuits(); it.hasNext();) {
			Circuit ckt = it.next();
			HashMap<Integer,List<NetObject>> codeToNetObjs = js.doFor(ckt);
			mapPerCkt.add(codeToNetObjs);
		}
		return mapPerCkt;
	}

	/** Get all the keys of all the maps.
	 * @param mapPerCkt list of maps
	 * @return the set of all keys from all the maps */
	private Set<Integer> getKeysFromAllMaps(ArrayList<HashMap<Integer,List<NetObject>>> mapPerCkt) {
		Set<Integer> keys = new HashSet<Integer>();
		for (HashMap<Integer,List<NetObject>> map : mapPerCkt) {
			keys.addAll(map.keySet());
		}
		return keys;    	
	}

	/** Create a leaf record for all the Circuits corresponding to a
	 * given key. If a map has a list of NetObjects for the given key
	 * then create a Circuit containing those NetObjects.
	 * Otherwise, add an empty Circuit to the EquivRecord.
	 * @param mapPerCkt ArrayList of maps.  Each map maps: 
	 * (Integer -> ArrayList of NetObjects)
	 * @param key check each map for a List of NetObjects at this key 
	 * @return a EquivRecord */
	private EquivRecord makeEquivRecForKey(ArrayList<HashMap<Integer,List<NetObject>>> mapPerCkt, Integer key, NccGlobals globals) {
		List<Circuit> ckts = new ArrayList<Circuit>();
		for (HashMap<Integer,List<NetObject>> map : mapPerCkt) {
			ArrayList<NetObject> netObjs = (ArrayList<NetObject>) map.get(key);
			if (netObjs==null)  netObjs = new ArrayList<NetObject>();
			ckts.add(Circuit.please(netObjs));
		}
		return EquivRecord.newLeafRecord(key.intValue(), ckts, globals);												 	
	}

	/** constructor*/
	private EquivRecord(){}
	
	private void addOffspring(EquivRecord r) {
		offspring.add(r);
		r.setParent(this);
	}

	private LeafList applyToLeaf(Strategy js) {
		ArrayList<HashMap<Integer,List<NetObject>>> mapPerCkt = getOneMapPerCircuit(js);
		
		Set<Integer> keys = getKeysFromAllMaps(mapPerCkt);
		
		error(keys.size()==0, "must have at least one key");
		
		// If everything maps to one hash code then no offspring
		if (keys.size()==1) return new LeafList();
		
		// Change this record from leaf to internal
		circuits = null;
		offspring = new RecordList();
		
		for (Integer key : keys) {
			EquivRecord er = makeEquivRecForKey(mapPerCkt, key, js.globals); 
			addOffspring(er);
		}
		

		LeafList el = new LeafList();
		el.addAll(offspring);
		return el;
	}

	private LeafList applyToInternal(Strategy js) {
		LeafList offspring = new LeafList();
		for (Iterator<EquivRecord> it=getOffspring(); it.hasNext();) {
			EquivRecord jr= it.next();
			offspring.addAll(js.doFor(jr));
		}
		return offspring;
	}

	// --------------------------- public methods -----------------------------
	/** @return internal EquivRecord that contains me */
	public EquivRecord getParent() {return parent;}

	/** getCode returns the fixed hash code for this object.
	 * @return the int fixed hash code for this object. */
	public int getCode(){return isMismatched()? 0 : randCode;}

	/** Sanity check */
    public void checkMe(EquivRecord parent) {
    	error(getParent()!=parent, "wrong parent");
    	error(!(offspring==null ^ circuits==null), "bad lists");
    }
    /** Set my parent to be x */
	public void setParent(EquivRecord x) {parent=x;}
	
	/** get the value that a strategy used to distinguish
	 * this EquivRecord.
	 * @return the int value that distinguished this EquivRecord */
	public int getValue(){return value;}

	/** @return an iterator over all the circuits belonging to EquivRecord */
	public Iterator<Circuit> getCircuits() {return circuits.iterator();}

	/** @return the number of circuits belonging to EquivRecord */
	public int numCircuits() {return circuits.size();}

	/** Add a new Circuit to EquivRecord */
	public void addCircuit(Circuit c) {
		circuits.add(c);
		c.setParent(this);
	}

	/** say whether this leaf record contains Parts, Wires, or Ports.
	 * A leaf record can only hold one kind of NetObject. 
	 * @return PART, WIRE, or PORT */
	public NetObject.Type getNetObjType() {
		for (Iterator<Circuit> ci=getCircuits(); ci.hasNext();) {
			Circuit c = ci.next();
			Iterator<NetObject> ni = c.getNetObjs();
			if (ni.hasNext()) {
				NetObject no = ni.next();
				return no.getNetObjType();
			}
		}
		error(true, "no NetObjects in a leaf EquivRecord?");
		return null; 
	}

	/** Get total number of NetObjects in all Circuits of a leaf record
	 * @return number of NetObjects */
	public int numNetObjs() {
		int sum = 0;
		for (Iterator<Circuit> ci=getCircuits(); ci.hasNext();) {
			Circuit c = ci.next();
			sum += c.numNetObjs();
		}
		return sum;
	}

	/** generates a String indicating the size of the
	 * Circuits in this leaf record
	 * @return the String */
	public String sizeString() {
	    if(numCircuits() == 0) return "0";
	    String s= "";
	    for (Iterator<Circuit> it=getCircuits(); it.hasNext();) {
	        Circuit jc= it.next();
	        s= s + " " + jc.numNetObjs() ;
	    }
	    return s;
	}

	/** maxSizeDiff computes the difference in the number of
	 * NetObjects in the Circuits of this leaf record.
	 * @return an int with the difference, zero is good */
	public int maxSizeDiff() {
	    int out= 0;
	    int max= maxSize();
	    for (Iterator<Circuit> it=getCircuits(); it.hasNext();) {
	        Circuit j= it.next();
	        int diff= max-j.numNetObjs();
	        if(diff > out)out= diff;
	    }
	    return out;
	}

	/** maxSize returns the number of NetObjects in the most populous
	 * Circuit.
	 * @return an int with the maximum size of any Circuit in this
	 * leaf record */
	public int maxSize() {
	    int out= 0;
	    for (Iterator<Circuit> it=getCircuits(); it.hasNext();) {
	        Circuit j= it.next();;
	        out = Math.max(out, j.numNetObjs());
	    }
	    return out;
	}

	/** isActive indicates that this leaf record is neither matched
	 * nor mismatched.
	 * @return true if this leaf record is still in play, false otherwise */
	public boolean isActive() {
	    error(numCircuits()==0, "leaf record with no circuits?");
	    for (Iterator<Circuit> it=getCircuits(); it.hasNext();) {
	        Circuit c = it.next();
	        if (c.numNetObjs()==0) return false; // mismatched
	        if (c.numNetObjs()>1)  return true;  // live
	    }
	    return false;
	}

	/** @return true if all Circuits have same number of NetObjects. */
	public boolean isBalanced() {
		boolean first = true;
		int sz = 0;
	    for (Iterator<Circuit> it=getCircuits(); it.hasNext();) {
	        Circuit c = it.next();
	        if (first) {
	        	sz = c.numNetObjs();
	        	first = false;
	        } else {
				if (c.numNetObjs()!=sz) return false;
	        }
	    }
	    return true;
	}

	/** isMatched is a special case of balanced.
	 * @return true if every Circuit has one NetObject */
	public boolean isMatched() {
		for (Iterator<Circuit> it=getCircuits(); it.hasNext();) {
			Circuit c = it.next();
			if (c.numNetObjs()!=1) return false;
		}
		return true;
	}

	/** isMismatched is a special case of unbalanced. 
	 * @return true if some Circuit has no NetObject */
	public boolean isMismatched() {
		// It's impossible for all Circuits to be zero sized so we only
		// need to find the first zero sized.
	    for (Iterator<Circuit> it=getCircuits(); it.hasNext();) {
	        Circuit c = it.next();
			if (c.numNetObjs()==0) return true;
	    }
	    return false;
	}

	/** get offspring of internal record */
	public Iterator<EquivRecord> getOffspring() {return offspring.iterator();}

	public int numOffspring() {return offspring.size();}

	/** The apply method applies a Strategy to this leaf EquivRecord.  If the 
	 * divides this Record then this leaf record becomes an internal record.
	 * @param js the Strategy to apply
	 * @return a LeafList of the resulting offspring */
	public LeafList apply(Strategy js) {
		return isLeaf() ? applyToLeaf(js) : applyToInternal(js);
	}

	/** nameString returns a String of type and name for this parent.
 	 * @return a String identifying this EquivRecord. */
	public String nameString() {
		String name = "";
		if (isLeaf()) {
			name = isMatched() ? "Matched" : isMismatched() ? "Mismatched" : "Active";
			name += " leaf";
		} else {
			name = "Internal";
		}
		name += " Record randCode="+randCode+" value="+value;
		name += isLeaf() ? (
			" maxSize="+maxSize()
		) : (
			" #offspring="+numOffspring()
		);
		return name;
	}
	
	/** @return true if EquivRecord is a leaf of the EquivRec tree */
	public boolean isLeaf() {return offspring==null;} 

	/** The fixed strategies annotate EquivRecords with comments
	 * describing what characteristic made this EquivRecord unique.
	 * This information is useful for providing pre-analysis information
	 * to the user. */
	public void setPartitionReason(String s) {partitionReason=s;}
	public String getPartitionReason() {return partitionReason;}
	public List<String> getPartitionReasonsFromRootToMe() {
		if (wireSignature!=null) {
			return wireSignature.getReasons();
		} else {
			LinkedList<String> reasons = new LinkedList<String>();
			for (EquivRecord r=this; r!=null; r=r.getParent()) {
				String reason = r.getPartitionReason();
				if (reason!=null) reasons.addFirst(reason);
			}
			return reasons;
		}
	}
	public void setWireSignature(LocalPartitionWires.Signature sig) {
		wireSignature = sig;
	}

	/** Construct a leaf EquivRecord that holds circuits
	 * @param ckts Circuits belonging to Equivalence Record
	 * @param globals used for generating random numbers
	 * @return the new EquivRecord */
	public static EquivRecord newLeafRecord(int key, List<Circuit> ckts, NccGlobals globals) {
		EquivRecord r = new EquivRecord();
		r.circuits = new ArrayList<Circuit>();
		r.value = key;
		r.randCode = globals.getRandom();
		for (Circuit ckt : ckts) {
			r.addCircuit(ckt);
		}
		error(r.maxSize()==0, 
			  "invalid leaf EquivRecord: all Circuits are empty");
		return r;
	}
	/** Construct an internal EquivRecord that will serve as the root of the 
	 * EquivRecord tree
	 * @param offspring 
	 * @return the new EquivRecord or null if there are no offspring */
	public static EquivRecord newRootRecord(List<EquivRecord> offspring) {
		if (offspring.size()==0) return null;
		EquivRecord r = new EquivRecord();
		r.offspring = new RecordList();
		for (EquivRecord er : offspring) {
			r.addOffspring(er);
		}
		return r;		
	}
	/** Get all NetObjects contained by an EquivRecord subtree.
	 * @param matched list of list of NetObjects from matched EquivRecords
	 * indexed as: [circuitIndex][netObjectIndex]. NetObjects at the same
	 * index in each list match.
	 * @param notMatched list of list of NetObjects from not matched
	 * EquivRecords indexed as [circuitIndex][netObjectIndex] */
	public void getNetObjsFromEntireTree(List<List<NetObject>> matched,
			                             List<List<NetObject>> notMatched) {
		GetNetObjs gno = new GetNetObjs(this);
		matched.clear();
		matched.addAll(gno.getMatchedNetObjs());
		notMatched.clear();
		notMatched.addAll(gno.getNotMatchedNetObjs());
	}
	private List<List<NetObjReportable>> coerceToReportable(List<List<NetObject>> no) {
		List<List<NetObjReportable>> nor = new ArrayList<List<NetObjReportable>>();
		for (List<NetObject> i : no) {
			List<NetObjReportable> j = new ArrayList<NetObjReportable>();
			j.addAll(i);
			nor.add(j);
		}
		return nor;
	}
	public void getNetObjReportablesFromEntireTree(
									List<List<NetObjReportable>> matched,
						            List<List<NetObjReportable>> notMatched) {
		List<List<NetObject>> m = new ArrayList<List<NetObject>>();
		List<List<NetObject>> nm = new ArrayList<List<NetObject>>();
		getNetObjsFromEntireTree(m, nm);

		matched.clear();
		matched.addAll(coerceToReportable(m));
		
		notMatched.clear();
		notMatched.addAll(coerceToReportable(nm));
	}
}
