/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: LESizer2.java
 * Written by: Jonathan Gainsley, Sun Microsystems.
 *
 * 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/>.
 *
 * Created on November 11, 2003, 4:42 PM
 */

package com.sun.electric.tool.logicaleffort;

import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.user.ErrorLogger;
import com.sun.electric.util.ElapseTimer;
import com.sun.electric.util.TextUtils;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * LESizer sizes an LENetlist. The LENetlist is generated by LENetlister from
 * the Electric database, or perhaps read in from a Spice file(?)
 * 
 * NOTE: the only 'Electric' objects used are in LENetlister, any objects
 * referenced in this file are from the logicaleffort package, although their
 * names may imply otherwise. Their names are as such because their names match
 * PNP's naming scheme.
 * 
 * @author gainsley
 */
public class LESizer2 {

	/** which algorithm to use */
	private LESizer.Alg optimizationAlg;
	/** Where to direct output */
	private PrintStream out;
	/** What job we are part of */
	private Job job;
	/** Netlist */
	private LENetlister2 netlist;
	/** error logger */
	private ErrorLogger errorLogger;

	/** Creates a new instance of LESizer */
	protected LESizer2(LESizer.Alg alg, LENetlister2 netlist, Job job, ErrorLogger errorLogger) {
		optimizationAlg = alg;
		this.netlist = netlist;
		this.job = job;
		this.errorLogger = errorLogger;

		out = new PrintStream(System.out);
	}

	// ============================ Sizing For Equal Gate Delays
	// ==========================

	/**
	 * Optimize using loop algorithm;
	 * 
	 * @param maxDeltaX
	 *            maximum tolerance allowed in X
	 * @param N
	 *            maximum number of loops
	 * @param verbose
	 *            print out size information for each optimization loop
	 * @return true if succeeded, false otherwise
	 * 
	 *         Optimization will stop when the difference in sizes (X) is less
	 *         than maxDeltaX, or when N iterations have occurred.
	 */
	protected boolean optimizeLoops(float maxDeltaX, int N, boolean verbose, float alpha, float keeperRatio) {
		// iterate through all the instances, updating sizes

		float currentLoopDeltaX = maxDeltaX + 1; // force at least one iteration
		float lastLoopDeltaX = currentLoopDeltaX;
		int divergingIters = 0; // count number if iterations sizing is
								// diverging
		ElapseTimer timer = ElapseTimer.createInstance();
		int loopcount = 0;

		while ((currentLoopDeltaX > maxDeltaX) && (loopcount < N)) {

			// check for aborted state of job
			if (((LETool.AnalyzeCell) job).checkAbort(null))
				return false;

			currentLoopDeltaX = 0;
			timer.start();
			System.out.print("  Iteration " + loopcount);
			if (verbose)
				System.out.println(":");

			// iterate through each instance
			for (Iterator<LENodable> lit = netlist.getSizeableNodables(); lit.hasNext();) {
				LENodable leno = lit.next();
				// ignore it if not a sizeable gate
				if (!leno.isLeGate())
					continue;
				LENetwork outputNet = leno.outputNetwork;

				// find all drivers in same group, of same type (LEGATE or
				// LEKEEPER)
				List<LENodable> drivers = new ArrayList<LENodable>();
				List<LENodable> arrayedDrivers = new ArrayList<LENodable>();
				for (LEPin pin : outputNet.getAllPins()) {
					// only interested in drivers
					if (pin.getDir() != LEPin.Dir.OUTPUT)
						continue;
					LENodable loopLeno = pin.getInstance();
					if (leno.getType() == loopLeno.getType()) {
						if (leno.parallelGroup == loopLeno.parallelGroup) {
							// add the instance. Note this adds the current
							// instance at some point as well
							drivers.add(loopLeno);
							// error check
							if (leno.parallelGroup > 0 && loopcount == 0 && leno.su != loopLeno.su) {
								String msg = "\nError: LEGATE \"" + leno.getName() + "\" drives in parallel with \""
										+ loopLeno.getName() + "\" but has a different step-up";
								System.out.println(msg);
								NodeInst ni = leno.getNodable().getNodeInst();
								if (ni != null) {
									errorLogger.logError(msg, ni, ni.getParent(), leno.context, 0);
								}
							}
						}
					}
					if ((loopLeno.getNodable().getNodeInst() == leno.getNodable().getNodeInst())
							&& (loopLeno.context.getInstPath(".").equals(leno.context.getInstPath(".")))) {
						// this must be an arrayed driver: not this also adds
						// current instance at some point as well
						arrayedDrivers.add(loopLeno);
					}
				}

				// this will be the new size.
				float newX = 0;

				// if this is an LEKEEPER, we need to find smallest gate (or
				// group)
				// that also drives this net, it is assumed that will have to
				// overpower this keeper
				if (leno.getType() == LENodable.Type.LEKEEPER) {
					Map<String, List<LENodable>> drivingGroups = new HashMap<String, List<LENodable>>();

					float smallestX = 0;

					// iterate over all drivers on net
					for (LEPin pin : outputNet.getAllPins()) {
						// only interested in drivers
						if (pin.getDir() != LEPin.Dir.OUTPUT)
							continue;
						LENodable loopLeno = pin.getInstance();
						if (loopLeno.getType() == LENodable.Type.LEGATE
								|| loopLeno.getType() == LENodable.Type.STATICGATE) {
							// organize by groups
							int i = loopLeno.parallelGroup;
							Integer integer = new Integer(i);
							if (i <= 0) {
								// this gate drives independently, check size
								if (smallestX == 0)
									smallestX = loopLeno.leX;
								if (loopLeno.leX < smallestX)
									smallestX = loopLeno.leX;
							}
							// add to group to sum up drive strength later
							List<LENodable> groupList = drivingGroups.get(integer.toString());
							if (groupList == null) {
								groupList = new ArrayList<LENodable>();
								drivingGroups.put(integer.toString(), groupList);
							}
							groupList.add(loopLeno);
						}
					}

					// find smallest total size of groups
					Set<String> keys = drivingGroups.keySet();
					for (String str : keys) {
						List<LENodable> groupList = drivingGroups.get(str);
						if (groupList == null)
							continue; // skip empty groups
						// get size
						float sizeX = 0;
						for (LENodable loopLeno : groupList) {
							sizeX += loopLeno.leX;
						}
						// check size of group
						if (smallestX == 0)
							smallestX = sizeX;
						if (sizeX < smallestX)
							smallestX = sizeX;
					}

					// if no drivers found, issue warning
					if (!keys.iterator().hasNext() && loopcount == 0) {
						String msg = "\nError: LEKEEPER \"" + leno.getName() + "\" does not fight against any drivers";
						System.out.println(msg);
						NodeInst ni = leno.getNodable().getNodeInst();
						if (ni != null) {
							errorLogger.logError(msg, ni, ni.getParent(), leno.context, 0);
						}
					}

					// For now, split effort equally amongst all drivers
					if (leno.parallelGroup <= 0) {
						newX = smallestX * netlist.getKeeperRatio() / arrayedDrivers.size();
					} else {
						newX = smallestX * netlist.getKeeperRatio() / drivers.size();
					}
				}

				// If this is an LEGATE, simply sum all capacitances on the Net
				if (leno.getType() == LENodable.Type.LEGATE) {

					// compute total le*X (totalcap)
					float totalcap = 0;
					int numLoads = 0;
					// System.out.println("LENode "+leno.getName()+" drives: ");
					// outputNet.print();
					for (LEPin pin : outputNet.getAllPins()) {
						LENodable loopLeno = pin.getInstance();

						float load = loopLeno.leX * pin.getLE() * loopLeno.getMfactor();
						if (pin.getDir() == LEPin.Dir.OUTPUT)
							load *= alpha;
						totalcap += load;
						// check to see if gate is only driving itself
						if (loopLeno != leno)
							numLoads++;
					}

					// create error if no loads only on first iteration
					if (numLoads == 0 && loopcount == 0) {
						String msg = "\nError: LEGATE \"" + leno.getName() + "\" has no loads: will be ignored";
						System.out.println(msg);
						NodeInst ni = leno.getNodable().getNodeInst();
						if (ni != null) {
							errorLogger.logError(msg, ni, ni.getParent(), leno.context, 1);
						}
					}
					// ignore if no loads, on all iterations
					if (numLoads == 0)
						continue;

					// For now, split effort equally amongst all drivers
					// Group 0 drives individually
					if (leno.parallelGroup <= 0)
						newX = totalcap / leno.su / arrayedDrivers.size();
					else {
						newX = totalcap / leno.su / drivers.size();
					}
					// also take into account mfactor of driver
					newX = newX / (float) leno.getMfactor();
				}

				// determine change in size
				float currentX = leno.leX;
				float deltaX;
				if (currentX == 0 && newX == 0) {
					// if before and after are 0, delta is 0
					deltaX = 0f;
				} else {
					// account for divide by 0
					if (currentX == 0)
						currentX = 0.001f;
					deltaX = Math.abs((newX - currentX) / currentX);
				}
				currentLoopDeltaX = (deltaX > currentLoopDeltaX) ? deltaX : currentLoopDeltaX;

				if (verbose) {
					out.println("Optimized " + leno.getName() + ": size:  " + TextUtils.formatDouble(leno.leX, 3)
							+ "x ==> " + TextUtils.formatDouble(newX, 3) + "x");
				}
				leno.leX = newX;
			}

			// All done, print some statistics about this iteration
			timer.end();
			System.out.println("  ...done (" + timer + "), delta: " + currentLoopDeltaX);
			if (verbose)
				System.out.println("-----------------------------------");
			loopcount++;

			// check to see if we're diverging or not converging
			if (currentLoopDeltaX >= lastLoopDeltaX) {
				if (divergingIters > 2) {
					System.out.println("  Sizing diverging, aborting");
					return false;
				}
				divergingIters++;
			}
			lastLoopDeltaX = currentLoopDeltaX;

		} // while (currentLoopDeltaX ... )

		return true;
	}

	// ========================== Sizing for Path Optimization
	// =====================

	// =============================== Statistics
	// ==================================

	// ============================== Design Printing
	// ===============================

}
