/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: StratSizes.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.strategy;
import java.util.Iterator;

import com.sun.electric.tool.ncc.NccGlobals;
import com.sun.electric.tool.ncc.lists.LeafList;
import com.sun.electric.tool.ncc.netlist.Mos;
import com.sun.electric.tool.ncc.netlist.NetObject;
import com.sun.electric.tool.ncc.netlist.Part;
import com.sun.electric.tool.ncc.netlist.Resistor;
import com.sun.electric.tool.ncc.trees.Circuit;
import com.sun.electric.tool.ncc.trees.EquivRecord;

/** The OutlierTrans for each Circuit in the EquivRecord */
class OutlierRecord {
	/** The Part in the Circuit that is most different from the others */
	private static class Outlier {
		public final Part part;
		public final double deviation;
		Outlier(Part p, double diff) {part=p; deviation=diff;}
	}

	private final Part[] parts;
	private final double deviation;
	
	private boolean hasSize(Part p) {
		return p instanceof Resistor || p instanceof Mos;
	}
	private double getWidth(Part p) {
		if (p instanceof Resistor) {
			return ((Resistor)p).getWidth();
		} else {
			return ((Mos)p).getWidth();
		}
	}
	private double getLength(Part p) {
		if (p instanceof Resistor) {
			return ((Resistor)p).getLength();
		} else {
			return ((Mos)p).getLength();
		}
	}
	private double[] computeAverageWidthLength(Circuit c) {
		double totLen=0, totWid=0;
		for (Iterator<NetObject> it=c.getNetObjs(); it.hasNext();) {
			Part p = (Part) it.next();
			totLen += getLength(p);
			totWid += getWidth(p);
		}
		int numParts = c.numNetObjs();
		return new double[] {totWid/numParts, totLen/numParts};
	}
	private double computeWidthLengthDeviation(double avgWid, double avgLen, Part p) {
		double deltaW = (getWidth(p)-avgWid) / avgWid;
		double deltaL = (getLength(p)-avgLen) / avgLen;
		return Math.abs(deltaW) + Math.abs(deltaL);
	}
	private double computeWidthLengthMismatch(Part p1, Part p2) {
		double w1 = getWidth(p1);
		double l1 = getLength(p1);
		double w2 = getWidth(p2);
		double l2 = getLength(p2);
		double avgW = (w1+w2)/2;
		double avgL = (l1+l2)/2;
		return Math.abs((w1-w2)/avgW) + Math.abs((l1-l2)/avgL);
	}
	private Outlier findOutlier(Circuit c) {
		if (!hasSize((Part)c.getNetObjs().next())) 
			return new Outlier(null, 0);

		double[] avgWidLen = computeAverageWidthLength(c);
		double avgWid = avgWidLen[0];
		double avgLen = avgWidLen[1];
		
		Part worstPart = null;
		double worstDeviation = 0; 
		for (Iterator<NetObject> it=c.getNetObjs(); it.hasNext();) {
			Part p = (Part) it.next();
			double deviation = computeWidthLengthDeviation(avgWid, avgLen, p);
			if (deviation>worstDeviation) {
				worstPart = p;
				worstDeviation = deviation;
			}
		}
		return new Outlier(worstPart, worstDeviation);
	}
	private Part findClosestSizedPart(Circuit c, Part outlier) {
		Part bestPart = null;
		double bestMismatch = Double.MAX_VALUE; 
		for (Iterator<NetObject> it=c.getNetObjs(); it.hasNext();) {
			Part p = (Part) it.next();
			double mismatch = computeWidthLengthMismatch(outlier, p);
			if (mismatch<bestMismatch) {
				bestPart = p;
				bestMismatch = mismatch;
			}
		}
		return bestPart;
	}
	public OutlierRecord(EquivRecord r) {
		parts = new Part[r.numCircuits()];
		Iterator<Circuit> it = r.getCircuits();

		// find outlier of 0th Circuit
		Outlier outlier = findOutlier((Circuit) it.next());
		deviation = outlier.deviation;
		parts[0] = outlier.part;
		if (deviation==0) return;
		
		// find closest sized Parts from all other Circuits 
		// parts[0] already initialized
		for (int cktNdx=1; it.hasNext(); cktNdx++) {
			Circuit c = (Circuit) it.next();
			parts[cktNdx] = findClosestSizedPart(c, outlier.part);
		}
	}

	public double deviation() {return deviation;}
	public EquivRecord getEquivRecord() {
		return parts[0].getParent().getParent();
	}
	public boolean isOutlier(Part p) {
		for (int i=0; i<parts.length; i++) {
			if (p==parts[i]) return true; 
		}
		return false;
	}
}

/** If Hash code partitioning fails to match all equivalence classes then try to 
 * partition equivalence classes containing Transistors and Resistors based on 
 * widths and lengths. This must be done before random matching or else we'll 
 * get width and length mismatches. */
public class StratSizes extends Strategy {
	private static final Integer CODE_OUTLIER = Integer.valueOf(1);
	private static final Integer CODE_REST = Integer.valueOf(2);
	private OutlierRecord outlierRecord;

	private StratSizes(NccGlobals globals) {super(globals);}

	private OutlierRecord findOutlierRecordWithLargestDeviation() {
		OutlierRecord furthestOut = null;
		Iterator<EquivRecord> frontier = globals.getPartLeafEquivRecs().getNotMatched();
		while (frontier.hasNext()) {
			EquivRecord r = frontier.next();
			if (!r.isBalanced())  continue;
			OutlierRecord farOut = new OutlierRecord(r);
			if (farOut.deviation()==0) continue;
			if (furthestOut==null) {
				furthestOut = farOut;
			} else if (farOut.deviation()>furthestOut.deviation()) {
				furthestOut = farOut;
			}
		}
		return furthestOut;
	}
	
	private LeafList doYourJob() {
		OutlierRecord r = findOutlierRecordWithLargestDeviation();
		if (r==null) return new LeafList();

		outlierRecord = r;
		return doFor(r.getEquivRecord());
	}
	@Override
	public Integer doFor(NetObject n){
		Part p = (Part) n;
		return outlierRecord.isOutlier(p) ? CODE_OUTLIER : CODE_REST;
	}

	//----------------------------- intended interface ------------------------
	/** Find the equivalence class with the largest deviation in Part
	 *  sizes and partition that class based on sizes. 
	 * @return the offspring resulting from the partition. Return an empty list
	 * if no partitioning was possible.	*/ 
	public static LeafList doYourJob(NccGlobals globals){
		StratSizes ss = new StratSizes(globals);
		return ss.doYourJob();
	}
}
