/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Sim.java
 * Input/output tool: ESIM, RSIM, RNL, and COSMOS output
 * Written by Steven M. Rubin.
 *
 * Copyright (c) 2004, 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.io.output;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.HierarchyEnumerator;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.prototype.PortCharacteristic;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.TransistorSize;
import com.sun.electric.tool.io.FileType;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * This is the netlister for Sim.
 */
public class Sim extends Output
{
	private Map<Integer,String> globalNetNames;
	private int globalNetVDD, globalNetGND, globalNetPhi1H, globalNetPhi1L, globalNetPhi2H, globalNetPhi2L;
	/** key of Variable holding COSMOS attributes. */	private static final Variable.Key COSMOS_ATTRIBUTE_KEY = Variable.newKey("SIM_cosmos_attribute");

	private SimPreferences localPrefs;

	public static class SimPreferences extends OutputPreferences
    {
		private FileType type;

		SimPreferences(boolean factory, FileType type) { super(factory); this.type = type; }

        public Output doOutput(Cell cell, VarContext context, String filePath)
        {
    		Sim out = new Sim(this);
    		if (out.openTextOutputStream(filePath)) return out.finishWrite();

    		out.init(cell, filePath, type);
    		HierarchyEnumerator.enumerateCell(cell, context, new Visitor(out, type), Netlist.ShortResistors.ALL);

    		if (out.closeTextOutputStream()) return out.finishWrite();
    		System.out.println(filePath + " written");
            return out.finishWrite();
        }
    }

	/**
	 * Creates a new instance of the Sim netlister.
	 */
	Sim(SimPreferences sp) { localPrefs = sp; }

	private static class Visitor extends HierarchyEnumerator.Visitor
	{
		private Sim generator;
		private FileType type;

		public Visitor(Sim generator, FileType type)
		{
			this.generator = generator;
			this.type = type;
		}

		public boolean enterCell(HierarchyEnumerator.CellInfo info)
		{
 			generator.writeCellContents(info, type);
			return true;
		}

		public void exitCell(HierarchyEnumerator.CellInfo info) {}

		public boolean visitNodeInst(Nodable no, HierarchyEnumerator.CellInfo info) { return true; }
	}

	private void init(Cell cell, String netfile, FileType format)
	{
		globalNetNames = new HashMap<Integer,String>();

		printWriter.println("| Cell " + cell.describe(false));
		emitCopyright("| ", "");
		if (localPrefs.includeDateAndVersionInOutput)
		{
			printWriter.println("| Cell created " + TextUtils.formatDate(cell.getCreationDate()));
			printWriter.println("| Version " + cell.getVersion() + " last revised " + TextUtils.formatDate(cell.getRevisionDate()));
		}

		if (format == FileType.COSMOS)
		{
			printWriter.println("| [e | d | p | n] gate source drain length width xpos ypos {[gsd]=attrs}");
			printWriter.println("| N node D-area D-perim P-area P-perim M-area M-perim");
			printWriter.println("| A node attrs");
			printWriter.println("|  attrs = [Sim:[In | Out | 1 | 2 | 3 | Z | U]]");
		} else
		{
			printWriter.println("| [epd] gate source drain length width r xpos ypos area");
			printWriter.println("| N node xpos ypos M-area P-area D-area D-perim");
		}
	}

	private void writeCellContents(HierarchyEnumerator.CellInfo ci, FileType format)
	{
		Cell cell = ci.getCell();
		Technology tech = cell.getTechnology();
		boolean top = ci.isRootCell();
		Netlist netList = ci.getNetlist();
		if (format == FileType.COSMOS) printWriter.println("| cell " + cell.getName());

		// if top level, cache all export names
		if (top)
		{
			globalNetVDD = globalNetGND = globalNetPhi1H = globalNetPhi1L = globalNetPhi2H = globalNetPhi2L = -1;
			for(Iterator<PortProto> it = cell.getPorts(); it.hasNext(); )
			{
				Export e = (Export)it.next();
				Network net = netList.getNetwork(e, 0);
				int globalNetNum = ci.getNetID(net);
				globalNetNames.put(Integer.valueOf(globalNetNum), e.getName());

				if (e.isPower()) globalNetVDD = globalNetNum;
				if (e.isGround()) globalNetGND = globalNetNum;
				if (e.getCharacteristic() == PortCharacteristic.C1 ||
					e.getName().startsWith("clk1") || e.getName().startsWith("phi1h")) globalNetPhi1H = globalNetNum;
				if (e.getCharacteristic() == PortCharacteristic.C2 ||
					e.getName().startsWith("phi1l")) globalNetPhi1L = globalNetNum;
				if (e.getCharacteristic() == PortCharacteristic.C3 ||
					e.getName().startsWith("clk2") || e.getName().startsWith("phi2h")) globalNetPhi2H = globalNetNum;
				if (e.getCharacteristic() == PortCharacteristic.C4 ||
					e.getName().startsWith("phi2l")) globalNetPhi2L = globalNetNum;
			}

			if (globalNetVDD < 0) reportWarning("Warning: no power export in this cell");
			if (globalNetGND < 0) reportWarning("Warning: no ground export in this cell");
		}

		// reset the arcinst node values
		Set<Network> netsSeen = new HashSet<Network>();

		// set every arcinst to a global node number (from inside or outside)
		for(Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); )
		{
			ArcInst ai = it.next();
			Network net = netList.getNetwork(ai, 0);
			if (netsSeen.contains(net)) continue;
			netsSeen.add(net);
			int globalNetNum = ci.getNetID(net);

			// calculate parasitics for this network
			double darea = 0, dperim = 0, parea = 0, pperim = 0, marea = 0, mperim = 0;
			for(Iterator<ArcInst> oIt = cell.getArcs(); oIt.hasNext(); )
			{
				ArcInst oAi = oIt.next();
				Network oNet = netList.getNetwork(oAi, 0);
				if (oNet != net) continue;

				// calculate true length and width of arc
				double width = oAi.getLambdaBaseWidth();
				double length = oAi.getLambdaLength();
				if (oAi.isHeadExtended()) length += width/2;
				if (oAi.isTailExtended()) length += width/2;
				if (format != FileType.COSMOS)
				{
					width = TextUtils.convertDistance(width, tech, TextUtils.UnitScale.MICRO);
					length = TextUtils.convertDistance(length, tech, TextUtils.UnitScale.MICRO);
				}

				// sum up area and perimeter to total
				ArcProto.Function fun = oAi.getProto().getFunction();
				if (fun.isMetal())
				{
					marea += length * width;
					mperim += 2 * (length + width);
				} else if (fun.isPoly())
				{
					parea += length * width;
					pperim += 2 * (length + width);
				} else if (fun.isDiffusion())
				{
					darea += length * width;
					dperim += 2 * (length + width);
				}
			}
			if (globalNetNum != globalNetVDD && globalNetNum != globalNetGND &&
				(marea != 0 || parea != 0 || darea != 0))
			{
				if (format == FileType.COSMOS)
				{
					printWriter.println("N " + makeNodeName(globalNetNum, format) + " " +
						TextUtils.formatDouble(darea) + " " + TextUtils.formatDouble(dperim) + " " +
						TextUtils.formatDouble(parea) + " " + TextUtils.formatDouble(pperim) + " " +
						TextUtils.formatDouble(marea) + " " + TextUtils.formatDouble(mperim));
				} else
				{
					printWriter.println("N " + makeNodeName(globalNetNum, format) + " 0 0 " +
						TextUtils.formatDouble(marea) + " " + TextUtils.formatDouble(parea) + " " +
						TextUtils.formatDouble(darea) + " " + TextUtils.formatDouble(dperim));
				}
			}
		}

		if (format == FileType.COSMOS)
		{
			// Test each arc for attributes
			for(Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); )
			{
				ArcInst ai = it.next();
				Variable var = ai.getVar(COSMOS_ATTRIBUTE_KEY);
				if (var != null)
				{
					Network net = netList.getNetwork(ai, 0);
					int globalNetNum = ci.getNetID(net);
					printWriter.println("A " + makeNodeName(globalNetNum, format) + " Sim:" + var.getPureValue(-1));
				}
			}
		}

		// look at every nodeinst in the cell
		for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();
			PrimitiveNode.Function fun = ni.getFunction();

			// if it is a transistor, write the information
			if (fun.isFET())
			{
				Network gateNet = netList.getNetwork(ni.getTransistorGatePort());
				int gate = ci.getNetID(gateNet);

				Network sourceNet = netList.getNetwork(ni.getTransistorSourcePort());
				int source = ci.getNetID(sourceNet);

				Network drainNet = netList.getNetwork(ni.getTransistorDrainPort());
				int drain = ci.getNetID(drainNet);

				String tType = "U";
				if (fun.isNTypeTransistor()) tType = "e"; else
					if (fun.isPTypeTransistor()) tType = "p"; else
						tType = "d";

				// determine size of transistor
				TransistorSize size = ni.getTransistorSize(ci.getContext());
				double length = 0, width = 0;
				if (size.getDoubleWidth() > 0) width = size.getDoubleWidth();
				if (size.getDoubleLength() > 0) length = size.getDoubleLength();

				if (format == FileType.COSMOS)
				{
					// see if there is an attribute mentioned on this node
					String extra = "";
					Variable var = ni.getVar(COSMOS_ATTRIBUTE_KEY);
					if (var != null) extra = " g=Sim:" + var.getPureValue(-1);
					extra += " " + TextUtils.formatDouble(ni.getAnchorCenterX()) + " " + TextUtils.formatDouble(ni.getAnchorCenterY());

					printWriter.println(tType + " " + makeNodeName(gate, format) + " " + makeNodeName(source, format) +
						" " + makeNodeName(drain, format) + " " + TextUtils.formatDouble(length) + " " +
						TextUtils.formatDouble(width) + extra);

					// approximate source and drain diffusion capacitances
					printWriter.println("N " + makeNodeName(source, format) + " " +
						TextUtils.formatDouble(length * width) + " " + TextUtils.formatDouble(width) + " 0 0 0 0");

					printWriter.println("N " + makeNodeName(drain, format) + " " +
						TextUtils.formatDouble(length * width) + " " + TextUtils.formatDouble(width) + " 0 0 0 0");
				} else
				{
					width = TextUtils.convertDistance(width, tech, TextUtils.UnitScale.MICRO);
					length = TextUtils.convertDistance(length, tech, TextUtils.UnitScale.MICRO);

					printWriter.println(tType + " " + makeNodeName(gate, format) + " " + makeNodeName(source, format) +
						" " + makeNodeName(drain, format) + " " + TextUtils.formatDouble(length) + " " +
						TextUtils.formatDouble(width) + " r 0 0 " + TextUtils.formatDouble(length * width));

					// approximate source and drain diffusion capacitances
					printWriter.println("N " + makeNodeName(source, format) + " 0 0 0 0 " +
						TextUtils.formatDouble(length * width) + " " + TextUtils.formatDouble(width));

					printWriter.println("N " + makeNodeName(drain, format) + " 0 0 0 0 " +
						TextUtils.formatDouble(length * width) + " " + TextUtils.formatDouble(width));
				}
			}
		}
	}

	/**
	 * Method to generate the name of a simulation node.
	 * Generates a string name (either a numeric node number if internal, an export
	 * name if at the top level, or "power", "ground", etc. if special).  The
	 * "format" is the particular simuator being used.
	 */
	private String makeNodeName(int globalNetNum, FileType format)
	{
		// test for special names
		if (globalNetNum == globalNetVDD) return "vdd";
		if (globalNetNum == globalNetGND) return "gnd";
		if (globalNetNum == globalNetPhi1H)
		{
			if (format == FileType.RSIM) return "phi1h";
			return "clk1";
		}
		if (globalNetNum == globalNetPhi1L) return "phi1l";
		if (globalNetNum == globalNetPhi2H)
		{
			if (format == FileType.RSIM) return "phi2h";
			return "clk2";
		}
		if (globalNetNum == globalNetPhi2L) return "phi2l";

		String name = globalNetNames.get(Integer.valueOf(globalNetNum));
		if (name == null) name = Integer.toString(globalNetNum);
		return(name);
	}

}
