/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: LESizer.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.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
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 LESizer {

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

	/** Alg is a typesafe enum class that describes the algorithm to be used */
	public static enum Alg {

		/** Sizes all gates for user specified equal gate delay */
		EQUALGATEDELAYS("Equal Gate Delays"),
		/** Sizes for optimal path delay */
		PATHDELAY("Path Delay");

		private final String name;

		private Alg(String name) {
			this.name = name;
		}

		public String toString() {
			return name;
		}
	}

	/** Creates a new instance of LESizer */
	protected LESizer(Alg alg, LENetlister1 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
			Iterator<Instance> instancesIter = netlist.getAllInstances().values().iterator();

			while (instancesIter.hasNext()) {
				Instance instance = instancesIter.next();
				String instanceName = instance.getName();

				// make sure it is a sizeable gate
				if (instance.isLeGate()) {

					// get output pin (do not iterate over all output pins, does
					// not make sense)
					ArrayList<Pin> outputPins = instance.getOutputPins();
					if (outputPins.size() != 1) {
						// error
						continue;
					}
					Pin outputPin = outputPins.get(0);
					Net net = outputPin.getNet();

					// now find all pins connected to this net
					ArrayList<Pin> netpins = net.getAllPins();

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

					// 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 (instance.getType() == Instance.Type.LEKEEPER) {
						Map<String, List<Instance>> drivingGroups = new HashMap<String, List<Instance>>();

						float smallestX = 0;

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

						// find smallest total size of groups
						Set<String> keys = drivingGroups.keySet();
						for (String str : keys) {
							List<Instance> groupList = drivingGroups.get(str);
							if (groupList == null)
								continue; // skip empty groups
							// get size
							float sizeX = 0;
							for (Instance inst : groupList) {
								sizeX += inst.getLeX();
							}
							// 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 \"" + instance.getName()
									+ "\" does not fight against any drivers";
							System.out.println(msg);
							NodeInst ni = instance.getNodable().getNodeInst();
							if (ni != null) {
								errorLogger.logError(msg, ni, ni.getParent(), instance.getContext(), 0);
							}
						}

						// For now, split effort equally amongst all drivers
						if (instance.getParallelGroup() <= 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 (instance.getType() == Instance.Type.LEGATE) {

						// compute total le*X (totalcap)
						float totalcap = 0;
						Iterator<Pin> netpinsIter = netpins.iterator();
						int numLoads = 0;
						while (netpinsIter.hasNext()) {
							Pin netpin = netpinsIter.next();
							Instance netpinInstance = netpin.getInstance();
							float load = netpinInstance.getLeX() * netpin.getLE() * (float) netpinInstance.getMfactor();
							if (netpin.getDir() == Pin.Dir.OUTPUT)
								load *= alpha;
							totalcap += load;
							// check to see if gate is only driving itself
							if (netpinInstance != instance)
								numLoads++;
						}

						// create error if no loads only on first iteration
						if (numLoads == 0 && loopcount == 0) {
							String msg = "\nError: LEGATE \"" + instance.getName() + "\" has no loads: will be ignored";
							System.out.println(msg);
							NodeInst ni = instance.getNodable().getNodeInst();
							if (ni != null) {
								errorLogger.logError(msg, ni, ni.getParent(), instance.getContext(), 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 (instance.getParallelGroup() <= 0) {
							newX = totalcap / instance.getLeSU() / arrayedDrivers.size();
						} else {
							newX = totalcap / instance.getLeSU() / drivers.size();
						}
						// also take into account mfactor of driver
						newX = newX / (float) instance.getMfactor();
					}

					// determine change in size
					float currentX = instance.getLeX();
					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 " + instanceName + ": size:  "
								+ TextUtils.formatDouble(instance.getLeX(), 3) + "x ==> "
								+ TextUtils.formatDouble(newX, 3) + "x");
					}
					instance.setLeX(newX);

				} // if (leGate)

			} // while (instancesIter)

			// 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
	// =====================

	protected List getEndNets() {

		List endNets = new ArrayList();

		Iterator<Net> netIter = netlist.getAllNets().values().iterator();
		while (netIter.hasNext()) {
			Net net = netIter.next();

		}
		return null;
	}

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

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

	/**
	 * Dump the design information for debugging purposes
	 */
	protected void printDesign() {
		out.println("Instances in design are:");

		Iterator<Instance> instancesIter = netlist.getAllInstances().values().iterator();
		while (instancesIter.hasNext()) {
			Instance instance = instancesIter.next();
			String instanceName = instance.getName();
			StringBuffer buf = new StringBuffer();
			out.println("\t" + instanceName + " ==> " + TextUtils.formatDouble(instance.getLeX(), 3) + "x");
			ArrayList<Pin> pins = instance.getAllPins();

			// now print out pinname ==> netname
			Iterator<Pin> pinsIter = pins.iterator();
			while (pinsIter.hasNext()) {
				Pin pin = pinsIter.next();
				out.println("\t\t" + pin.getName() + " ==> " + pin.getNetName());
			}
		}
	}

	/**
	 * Generate simple size file (for regression purposes)
	 * 
	 * @param filename
	 *            output filename
	 */
	protected int printDesignSizes(String filename) {
		// open output file
		try {
			FileWriter fileWriter = new FileWriter(filename); // throws
																// IOException

			// iterate through all instances
			Iterator<Instance> instancesIter = netlist.getAllInstances().values().iterator();
			while (instancesIter.hasNext()) {
				Instance instance = instancesIter.next();
				String instanceName = instance.getName();
				float leX = instance.getLeX();
				fileWriter.write(instanceName + " " + leX + "\n"); // throws
																	// IOException
				fileWriter.flush(); // throws IOException
			}
			fileWriter.close(); // throws IOException

		} catch (IOException e) {
			out.println("Writing to file " + filename + ": " + e.getMessage());
			return 1;
		}
		return 0;
	}

	/**
	 * Generate SKILL backannotation file
	 * 
	 * @param filename
	 *            output filename
	 * @param libname
	 *            The Opus library name to be annotated
	 * @param cellname
	 *            The Opus cell to be annotated
	 */
	protected int printDesignSkill(String filename, String libname, String cellname) {
		// nothing here
		return 0;
	}

	/**
	 * Dummy method to improve test coverage
	 */
	protected void testcoverage() {
		// nothing here
	}

	// ---------------------------------------TEST---------------------------------------
	// ---------------------------------------TEST---------------------------------------

	/** run a contrived test */
	public static void test1() {
		System.out.println("Running GASP test circuit");
		System.out.println("=========================");

		float su = (float) 4.0;
		LENetlister1 netlist = new LENetlister1(null, null);

		{
			// inv1
			Pin pin_a = new Pin("A", Pin.Dir.INPUT, (float) 1.0, "nand1_out");
			Pin pin_y = new Pin("Y", Pin.Dir.OUTPUT, (float) 1.0, "inv1_out");
			ArrayList<Pin> pins = new ArrayList<Pin>();
			pins.add(pin_a);
			pins.add(pin_y);
			netlist.addInstance("inv1", Instance.Type.LEGATE, su, (float) 1.0, pins, null);
		}

		{
			// inv2
			Pin pin_a = new Pin("A", Pin.Dir.INPUT, (float) 1.0, "pu_out");
			Pin pin_y = new Pin("Y", Pin.Dir.OUTPUT, (float) 1.0, "inv2_out");
			ArrayList<Pin> pins = new ArrayList<Pin>();
			pins.add(pin_a);
			pins.add(pin_y);
			netlist.addInstance("inv2", Instance.Type.LEGATE, su, (float) 1.0, pins, null);
		}

		{
			// inv3
			Pin pin_a = new Pin("A", Pin.Dir.INPUT, (float) 1.0, "nand1_out");
			Pin pin_y = new Pin("Y", Pin.Dir.OUTPUT, (float) 1.0, "inv3_out");
			ArrayList<Pin> pins = new ArrayList<Pin>();
			pins.add(pin_a);
			pins.add(pin_y);
			netlist.addInstance("inv3", Instance.Type.LEGATE, su, (float) 1.0, pins, null);
		}

		{
			// nand1
			Pin pin_a = new Pin("A", Pin.Dir.INPUT, (float) 1.333, "inv2_out");
			Pin pin_b = new Pin("B", Pin.Dir.INPUT, (float) 1.333, "pd_out");
			Pin pin_y = new Pin("Y", Pin.Dir.OUTPUT, (float) 2.0, "nand1_out");
			ArrayList<Pin> pins = new ArrayList<Pin>();
			pins.add(pin_a);
			pins.add(pin_b);
			pins.add(pin_y);
			netlist.addInstance("nand1", Instance.Type.LEGATE, su, (float) 1.0, pins, null);
		}

		{
			// pu
			Pin pin_g = new Pin("G", Pin.Dir.INPUT, (float) 0.667, "nand1_out");
			Pin pin_d = new Pin("D", Pin.Dir.OUTPUT, (float) 0.667, "pu_out");
			ArrayList<Pin> pins = new ArrayList<Pin>();
			pins.add(pin_g);
			pins.add(pin_d);
			netlist.addInstance("pu", Instance.Type.LEGATE, su, (float) 1.0, pins, null);
		}

		{
			// pd
			Pin pin_g = new Pin("G", Pin.Dir.INPUT, (float) 0.333, "inv3_out");
			Pin pin_d = new Pin("D", Pin.Dir.OUTPUT, (float) 0.333, "pd_out");
			ArrayList<Pin> pins = new ArrayList<Pin>();
			pins.add(pin_g);
			pins.add(pin_d);
			netlist.addInstance("pd", Instance.Type.LEGATE, su, (float) 1.0, pins, null);
		}

		{
			// cap1
			Pin pin_c = new Pin("C", Pin.Dir.INPUT, (float) 1.0, "pd_out");
			ArrayList<Pin> pins = new ArrayList<Pin>();
			pins.add(pin_c);
			netlist.addInstance("cap1", Instance.Type.LOAD, su, (float) 0.0, pins, null);
		}

		{
			// cap2
			Pin pin_c = new Pin("C", Pin.Dir.INPUT, (float) 1.0, "pu_out");
			ArrayList<Pin> pins = new ArrayList<Pin>();
			pins.add(pin_c);
			netlist.addInstance("cap2", Instance.Type.LOAD, su, (float) 0.0, pins, null);
		}

		{
			// cap3
			Pin pin_c = new Pin("C", Pin.Dir.INPUT, (float) 1.0, "inv1_out");
			ArrayList<Pin> pins = new ArrayList<Pin>();
			pins.add(pin_c);
			netlist.addInstance("cap3", Instance.Type.LOAD, su, (float) 100.0, pins, null);
		}

		netlist.getSizer().printDesign();
		netlist.getSizer().optimizeLoops((float) 0.01, 30, true, (float) 0.7, (float) 0.1);
		System.out.println("After optimization:");
		netlist.getSizer().printDesign();
	}
}
