/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Flat.java
 * Asynchronous Logic Simulator network flattening
 * Original C Code written by Brent Serbin and Peter J. Gallant
 * Translated to Java by Steven M. Rubin.
 *
 * Copyright (c) 2005, 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.simulation.als;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.tool.simulation.als.ALS.Stat;
import com.sun.electric.util.TextUtils;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Class to flatten circuitry for the ALS Simulator.
 */
public class Flat
{
	private ALS       als;
	private ALS.Model primPtr2;

	Flat(ALS als)
	{
		this.als = als;
	}

	/**
	 * Method to call a series of routines which convert the hierarchical
	 * network description into a flattened database representation.  The actual
	 * simulation must take place on the flattened network.  Returns true on error.
	 */
	boolean flattenNetwork(Cell cell)
	{
		/*
		 * create a "dummy" level to use as a mixed signal destination for plotting and
		 * screen display.  This level should be bypassed for structure checking and general
		 * simulation, however, so in the following code, references to "als.cellRoot"
		 * have been changed to als.cellRoot.next (pointing to mainCell).
		 * Peter Gallant July 16, 1990
		 */
		als.cellRoot = new ALS.Connect();
		als.cellRoot.instName = "[MIXED_SIGNAL_LEVEL]";
		als.cellRoot.modelName = als.cellRoot.instName;
		als.cellRoot.exList = new ArrayList<ALS.ALSExport>();
		als.cellRoot.parent = null;
		als.cellRoot.child = null;
		als.cellRoot.next = null;
		ALS.Connect tempRoot = als.cellRoot;

		// get upper-case version of main prototype
		String mainName = cell.getName().toUpperCase();

		als.cellRoot = new ALS.Connect();
		als.cellRoot.instName = mainName;
		als.cellRoot.modelName = als.cellRoot.instName;
		als.cellRoot.exList = new ArrayList<ALS.ALSExport>();
		als.cellRoot.parent = null;
		als.cellRoot.child = null;
		als.cellRoot.next = null;

		// these lines link the mixed level as the head followed by mainCell PJG
		tempRoot.next = als.cellRoot;		// shouldn't this be null? ... smr
		tempRoot.child = als.cellRoot;
		als.cellRoot = tempRoot;

		// this code checks to see if model mainCell is present in the netlist PJG
		ALS.Model modHead = findModel(mainName);
		if (modHead == null) return true;
		for(ALS.ALSExport exHead : modHead.exList)
		{
			findXRefEntry(als.cellRoot.next, (String)exHead.nodeName);
		}

		if (flattenModel(als.cellRoot.next)) return true;

		for(ALS.Node nodeHead : als.nodeList)
		{
			if (nodeHead.load < 1) nodeHead.load = 1;
		}
		return false;
	}

	/**
	 * Method to flatten a single model.  If other models are referenced
	 * in connection statements in the netlist, this routine is called recursively
	 * until a totally flat model is obtained.  Returns true on error.
	 * @param cellHead pointer to a data structure containing information about the model that is going to be flattened
	 */
	private boolean flattenModel(ALS.Connect cellHead)
	{
		ALS.Model modHead = findModel(cellHead.modelName);
		if (modHead == null) return true;
		switch (modHead.type)
		{
			case 'F':
				if (processFunction(cellHead, modHead)) return true;
				break;

			case 'G':
				processGate(cellHead, modHead);
				break;

			case 'M':
				if (processConnectList(cellHead, (ALS.Connect)modHead.ptr)) return true;
				for (ALS.Connect subCell = cellHead.child; subCell != null; subCell = subCell.next)
				{
					if (flattenModel(subCell)) return true;
				}
				break;
		}

		if (modHead.setList.size() != 0)
		{
			processSetEntry(cellHead, modHead.setList);
		}
		return false;
	}

	/**
	 * Method to step through the connection list specified by the
	 * connection list pointer (conHead).  Values are entered into the cross
	 * reference table for the present level of hierarchy and new data structures
	 * are created for the lower level of hierarchy to store their cross
	 * reference tables.  Returns true on error.
	 * @param cellHead pointer to the cross reference data structure for the model that is going to be flattened
	 * @param conHead pointer to a list of connection statements for the model that is being flattened by this procedure
	 */
	private boolean processConnectList(ALS.Connect cellHead, ALS.Connect conHead)
	{
		while (conHead != null)
		{
			ALS.Connect cellPtr2 = new ALS.Connect();
			cellPtr2.instName = conHead.instName;
			cellPtr2.modelName = conHead.modelName;
			cellPtr2.exList = new ArrayList<ALS.ALSExport>();
			cellPtr2.parent = cellHead;
			cellPtr2.child = null;
			cellPtr2.next = cellHead.child;
			cellHead.child = cellPtr2;

			ALS.Model modHead = findModel(conHead.modelName);
			if (modHead == null) return true;
			Iterator<ALS.ALSExport> it = modHead.exList.iterator();
			for(ALS.ALSExport exHead : conHead.exList)
			{
				if (!it.hasNext()) break;
				als.exPtr2 = it.next();
				ALS.ALSExport xRefHead = findXRefEntry(cellHead, (String)exHead.nodeName);

				if (als.exPtr2 == null)
				{
					System.out.println("Insufficient parameters declared for model '" + conHead.modelName + "' in netlist");
					return true;
				}

				for(ALS.ALSExport xRefPtr1 : cellPtr2.exList)
				{
					if (xRefPtr1.nodeName.equals(als.exPtr2.nodeName))
					{
						System.out.println("Node '" + als.exPtr2.nodeName + "' in model '" +
							conHead.modelName + "' connected more than once");
						return true;
					}
				}
				ALS.ALSExport xRefPtr2 = new ALS.ALSExport();
				xRefPtr2.nodeName = als.exPtr2.nodeName;
				xRefPtr2.nodePtr = xRefHead.nodePtr;
				cellPtr2.exList.add(xRefPtr2);
			}

			conHead = conHead.next;
		}
		return false;
	}

	/**
	 * Method to return a pointer to the model referenced by the
	 * calling argument character string.  Returns zero on error.
	 * @param modelName pointer to a string which contains the name of the model to be located by the search procedure
	 */
	private ALS.Model findModel(String modelName)
	{
		// convert to proper name
		StringBuffer sb = new StringBuffer();
		for(int i=0; i<modelName.length(); i++)
		{
			char chr = modelName.charAt(i);
			if (!TextUtils.isLetterOrDigit(chr)) chr = '_';
			sb.append(chr);
		}
		String properName = sb.toString();
		for(ALS.Model modHead : als.modelList)
		{
			if (modHead.name.equals(properName)) return modHead;
		}
		System.out.println("ERROR: Model '" + properName + "' not found, simulation aborted");
		return null;
	}

	/**
	 * Method to return the flattened database node number for the
	 * specified model and node name.
	 * @param cellHead pointer to the xref table for the model being processed
	 * @param name pointer to a char string containing the node name
	 */
	private ALS.ALSExport findXRefEntry(ALS.Connect cellHead, String name)
	{
		for(ALS.ALSExport xRefPtr1 : cellHead.exList)
		{
			if (xRefPtr1.nodeName.equals(name)) return xRefPtr1;
		}
		ALS.ALSExport xRefPtr2 = new ALS.ALSExport();
		xRefPtr2.nodeName = name;
		cellHead.exList.add(xRefPtr2);

		ALS.Node nodePtr2 = new ALS.Node();
		nodePtr2.cellPtr = cellHead;
		nodePtr2.statList = new ArrayList<Stat>();
		nodePtr2.pinList = new ArrayList<ALS.Load>();
		nodePtr2.load = -1;
		nodePtr2.visit = 0;
		nodePtr2.traceNode = false;
		als.nodeList.add(nodePtr2);
		xRefPtr2.nodePtr = nodePtr2;
		return xRefPtr2;
	}

	/**
	 * Method to step through the gate truth tables and examines all
	 * node references to insure that they have been included in the cross
	 * reference table for the model.  Returns true on error.
	 * @param cellHead = pointer to the cross reference data structure for the model that is going to be flattened
	 * @param modHead = pointer to the structure containing the hierarchical node references
	 */
	private void processGate(ALS.Connect cellHead, ALS.Model modHead)
	{
		primPtr2 = new ALS.Model(modHead.name, 'G');
		primPtr2.fanOut = modHead.fanOut;
		primPtr2.priority = modHead.priority;
		primPtr2.level = als.computePathName(cellHead);
		als.primList.add(primPtr2);

		ALS.Row rowHead = (ALS.Row)modHead.ptr;
		ALS.Row last = null;
		while (rowHead != null)
		{
			ALS.Row rowPtr2 = new ALS.Row();
			rowPtr2.inList = new ArrayList<Object>();
			rowPtr2.outList = new ArrayList<Object>();
			rowPtr2.delta = rowHead.delta;
			rowPtr2.linear = rowHead.linear;
			rowPtr2.exp = rowHead.exp;
			rowPtr2.abs = rowHead.abs;
			rowPtr2.random = rowHead.random;
			rowPtr2.delay = rowHead.delay;
			if (rowHead.delay == null) rowPtr2.delay = null; else
				rowPtr2.delay = rowHead.delay;

			rowPtr2.next = null;
			if (last == null)
			{
				primPtr2.ptr = rowPtr2;
			} else
			{
				last.next = rowPtr2;
			}
			last = rowPtr2;

			als.ioPtr1 = rowPtr2.inList;
			processIOEntry(modHead, cellHead, rowHead.inList, 'I');

			als.ioPtr1 = rowPtr2.outList;
			processIOEntry(modHead, cellHead, rowHead.outList, 'O');

			rowHead = rowHead.next;
		}
	}

	/**
	 * Method to step through the node references contained within a
	 * row of a transition table and insures that they are included in the cross
	 * reference table in the event they were not previously specified in a
	 * connection statement.  Returns true on error.
	 * @param modHead pointer to model that is being flattened
	 * @param cellHead pointer to the cross reference data structure for the model that is going to be flattened
	 * @param ioHead pointer to a row of node references to be checked for entry into the cross reference table
	 * @param flag character indicating if the node is an input or output
	 */
	private void processIOEntry(ALS.Model modHead, ALS.Connect cellHead, List<Object> ioList, char flag)
	{
		for(Object obj : ioList)
		{
			ALS.IO ioHead = (ALS.IO)obj;
			ALS.ALSExport xRefHead = findXRefEntry(cellHead, (String)ioHead.nodePtr);
			als.ioPtr2 = new ALS.IO();
			als.ioPtr2.nodePtr = xRefHead.nodePtr;
			als.ioPtr2.operatr = ioHead.operatr;

			if (als.ioPtr2.operatr > 127)
			{
				xRefHead = findXRefEntry(cellHead, (String)ioHead.operand);
				als.ioPtr2.operand = xRefHead.nodePtr;
			} else
			{
				als.ioPtr2.operand = ioHead.operand;
			}

			als.ioPtr2.strength = ioHead.strength;
			als.ioPtr1.add(als.ioPtr2);

			switch (flag)
			{
				case 'I':
					createPinEntry(modHead, (String)ioHead.nodePtr, (ALS.Node)als.ioPtr2.nodePtr);
					break;
				case 'O':
					als.ioPtr2.nodePtr = createStatEntry(modHead,
						(String)ioHead.nodePtr, (ALS.Node)als.ioPtr2.nodePtr);
			}

			if (als.ioPtr2.operatr > 127)
			{
				createPinEntry(modHead, (String)ioHead.operand, (ALS.Node)als.ioPtr2.operand);
			}
		}
	}

	/**
	 * Method to make an entry into the primitive input table for the
	 * specified node.  This table keeps track of the primitives which use
	 * this node as an input for event driven simulation.  Returns true on error.
	 * @param modHead pointer to the model structure from which the primitive is being created
	 * @param nodeName pointer to a char string containing the name of the node whose input list is being updated
	 * @param nodeHead pointer to the node data structure allocated for this node
	 */
	private void createPinEntry(ALS.Model modHead, String nodeName, ALS.Node nodeHead)
	{
		for(ALS.Load pinPtr1 : nodeHead.pinList)
		{
			if (pinPtr1.ptr == primPtr2) return;
		}
		ALS.Load pinPtr2 = new ALS.Load();
		pinPtr2.ptr = primPtr2;
		nodeHead.pinList.add(pinPtr2);
		nodeHead.load += findLoadValue(modHead, nodeName);
	}

	/**
	 * Method to make an entry into the database for an output which
	 * is connected to the specified node.  Statistics are maintained for each output
	 * that is connected to a node.  Returns zero on error.
	 * @param modHead pointer to the model structure from which the primitive is being created
	 * @param nodeName pointer to a char string containing the name of the node whose output list is being updated
	 * @param nodeHead pointer to the node data structure allocated for this node
	 */
	private ALS.Stat createStatEntry(ALS.Model modHead, String nodeName, ALS.Node nodeHead)
	{
		for(Stat statPtr1 : nodeHead.statList)
		{
			if (statPtr1.primPtr == primPtr2) return statPtr1;
		}
		ALS.Stat statPtr2 = new ALS.Stat();
		statPtr2.primPtr = primPtr2;
		statPtr2.nodePtr = nodeHead;
		nodeHead.statList.add(statPtr2);
		nodeHead.load += findLoadValue(modHead, nodeName);
		return statPtr2;
	}

	/**
	 * Method to return the loading factor for the specified node.  If
	 * the node can't be found in the load list it is assumed it has a default value
	 * of 1.0.
	 * @param modHead pointer to the model structure from which the primitive is being created
	 * @param nodeName pointer to a char string containing the name of the node whose load value is to be determined
	 */
	private double findLoadValue(ALS.Model modHead, String nodeName)
	{
		for(ALS.Load loadHead : modHead.loadList)
		{
			if (loadHead.ptr.equals(nodeName)) return loadHead.load;
		}

		if (modHead.type == 'F') return 0;
		return 1;
	}

	/**
	 * Method to go through the set node list for the specified cell
	 * and generates vectors for the node.  These vectors are executed at t=0 by
	 * the simulator to initialize the node correctly.  Returns true on error.
	 * @param cellHead pointer to the cross reference table where the node locations are to be found
	 * @param ioHead pointer to the set list containing node names and state info
	 */
	private void processSetEntry(ALS.Connect cellHead, List<ALS.IO> ioList)
	{
		for(ALS.IO ioHead : ioList)
		{
			ALS.ALSExport xRefHead = findXRefEntry(cellHead, (String)ioHead.nodePtr);

			ALS.Link setHead = new ALS.Link();
			setHead.type = 'N';
			setHead.ptr = xRefHead.nodePtr;
			setHead.state = ioHead.operand;
			setHead.strength = ioHead.strength;
			setHead.priority = 2;
			setHead.time = 0.0;
			setHead.right = null;
			als.insertSetList(setHead);
		}
	}

	/**
	 * Method to step through the event driving input list for a function
	 * and enters the function into the primitive input list for the particular node.
	 * In addition to this task the procedure sets up the calling argument node list
	 * for the function when it is called.  Returns true on error.
	 * @param cellHead pointer to the cross reference data structure for the model that is going to be flattened
	 * @param modHead pointer to the data structure containing the hierarchical node references
	 */
	private boolean processFunction(ALS.Connect cellHead, ALS.Model modHead)
	{
		primPtr2 = new ALS.Model(modHead.name, 'F');
		primPtr2.ptr = new ALS.Func();
		primPtr2.priority = modHead.priority;
		primPtr2.level = als.computePathName(cellHead);
		als.primList.add(primPtr2);

		ALS.Func funcHead = (ALS.Func)modHead.ptr;
		ALS.Func funcPtr2 = (ALS.Func)primPtr2.ptr;
		funcPtr2.procPtr = ALS.UserProc.getFunctionAddress(modHead.name);
		if (funcPtr2.procPtr == null) return true;
		funcPtr2.inList = new ArrayList<ALS.ALSExport>();
		funcPtr2.delta = funcHead.delta;
		funcPtr2.linear = funcHead.linear;
		funcPtr2.exp = funcHead.exp;
		funcPtr2.abs = funcHead.abs;
		funcPtr2.random = funcHead.random;
		funcPtr2.userPtr = null;
		for(ALS.ALSExport exHead : modHead.exList)
		{
			ALS.ALSExport xRefHead = findXRefEntry(cellHead, (String)exHead.nodeName);
			als.exPtr2 = new ALS.ALSExport();
			if (exHead.nodePtr != null)
			{
				als.exPtr2.nodeName = createStatEntry(modHead, (String)exHead.nodeName, xRefHead.nodePtr);
			} else
			{
				als.exPtr2.nodeName = null;
			}
			als.exPtr2.nodePtr = xRefHead.nodePtr;
			primPtr2.exList.add(als.exPtr2);
		}

		for(ALS.ALSExport exHead : funcHead.inList)
		{
			ALS.ALSExport xRefHead = findXRefEntry(cellHead, (String)exHead.nodeName);
			als.exPtr2 = new ALS.ALSExport();
			als.exPtr2.nodePtr = xRefHead.nodePtr;
			primPtr2.exList.add(als.exPtr2);
			createPinEntry(modHead, (String)exHead.nodeName, xRefHead.nodePtr);
		}
		return false;
	}
}
