/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: VerilogOut.java
 * Input/output tool: reader for Verilog output (.v)
 * Written by Steven M. Rubin, Sun Microsystems.
 *
 * Copyright (c) 2004, 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/>.
 */
package com.sun.electric.tool.io.input.verilog;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.tool.io.input.Input;
import com.sun.electric.tool.simulation.BusSample;
import com.sun.electric.tool.simulation.DigitalSample;
import com.sun.electric.tool.simulation.DigitalSample.Strength;
import com.sun.electric.tool.simulation.DigitalSample.Value;
import com.sun.electric.tool.simulation.MutableSignal;
import com.sun.electric.tool.simulation.Signal;
import com.sun.electric.tool.simulation.SignalCollection;
import com.sun.electric.tool.simulation.Stimuli;
import com.sun.electric.util.TextUtils;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Class for reading and displaying waveforms from Verilog output.
 * These are contained in .v files.
 */
public class VerilogOut extends Input<Stimuli>
{
	public VerilogOut() {
        super(null);
    }

	/**
	 * Method to read an Verilog output file.
	 */
	protected Stimuli processInput(URL fileURL, Cell cell, Stimuli sd)
		throws IOException
	{
        sd.setNetDelimiter(" ");

		// open the file
		if (openTextInput(fileURL)) return sd;

		// show progress reading .dump file
		startProgressDialog("Verilog output", fileURL.getFile());

		// read the actual signal data from the .dump file
		readVerilogFile(cell, sd);

		// stop progress dialog, close the file
		stopProgressDialog();
		closeInput();

        return sd;
	}

	private void readVerilogFile(Cell cell, Stimuli sd)
		throws IOException
	{
		SignalCollection sc = Stimuli.newSignalCollection(sd, "SIGNALS");
		Map<String,Signal<DigitalSample>[]> busMembers = new HashMap<String,Signal<DigitalSample>[]>();
		double timeScale = 1.0;
		String currentScope = "";
		int curLevel = 0;
		int numSignals = 0;
		Map<String,Object> symbolTable = new HashMap<String,Object>();
		List<Signal<?>> curArray = null;
		for(;;)
		{
			String keyword = getNextKeyword();
			if (keyword == null) break;

			// ignore "$date", "$version" or "$timescale"
			if (keyword.equals("$date") || keyword.equals("$version"))
			{
				parseToEnd();
				continue;
			}
			if (keyword.equals("$timescale"))
			{
				String units = getNextKeyword();
				timeScale = TextUtils.atof(units);
				if (units.endsWith("ms")) timeScale /= 1000.0; else
				if (units.endsWith("us")) timeScale /= 1000000.0; else
				if (units.endsWith("ns")) timeScale /= 1000000000.0; else
				if (units.endsWith("ps")) timeScale /= 1000000000000.0; else
				if (units.endsWith("fs")) timeScale /= 1000000000000000.0;
				parseToEnd();
				continue;
			}
			if (keyword.equals("$scope"))
			{
				String scope = getNextKeyword();
				if (scope == null) break;
				if (scope.equals("module") || scope.equals("task") || scope.equals("function"))
				{
					// scan for arrays
					sd.makeBusSignals(curArray, sc);
					curArray = new ArrayList<Signal<?>>();

					String scopeName = getNextKeyword();
					if (scopeName == null) break;
					if (currentScope.length() > 0) currentScope += ".";
					currentScope += scopeName;
					curLevel++;
				}
				parseToEnd();
				continue;
			}

			if (keyword.equals("$upscope"))
			{
				if (curLevel <= 0 || currentScope.length() == 0)
				{
					System.out.println("Unbalanced $upscope on line " + lineReader.getLineNumber());
					continue;
				}

				// scan for arrays
				sd.makeBusSignals(curArray, sc);
				curArray = new ArrayList<Signal<?>>();

				int dotPos = currentScope.lastIndexOf('.');
				if (dotPos >= 0)
				{
					currentScope = currentScope.substring(0, dotPos);
					curLevel--;
				}
				parseToEnd();
				continue;
			}

			if (keyword.equals("$var"))
			{
				String varName = getNextKeyword();
				if (varName == null) break;
				if (varName.equals("wire") || varName.equals("reg") ||
					varName.equals("supply0") || varName.equals("supply1") ||
					varName.equals("parameter") || varName.equals("trireg") ||
					varName.equals("in") || varName.equals("out") ||
					varName.equals("inout"))
				{
					// get the bus width
					String widthText = getNextKeyword();
					if (widthText == null) break;
					int width = TextUtils.atoi(widthText);

					// get the symbol name for this signal
					String symbol = getNextKeyword();
					if (symbol == null) break;

					// get the signal name
					String signalName = getNextKeyword();
					if (signalName == null) break;

					// see if there is an index
					String index = getNextKeyword();
					if (index == null) break;
					if (index.equals("$end")) index = ""; else
					{
						parseToEnd();
					}
					numSignals++;

                    Signal<?> sig = null;
                    if (width <= 1)
                    {
                        sig = DigitalSample.createSignal(sc, sd, signalName + index, currentScope);
                        if (index.length() > 0) curArray.add(sig);

                        // put it in the symbol table
    					addSignalToHashMap(sig, symbol, symbolTable);
                    } else
                    {
                        Signal<DigitalSample>[] subsigs = new Signal[width];
						for(int i=0; i<width; i++)
						{
                            subsigs[i] = DigitalSample.createSignal(sc, sd, signalName + "[" + i + "]", currentScope);
						}
                        sig = BusSample.createSignal(sc, sd, signalName + "[0:" + (width-1) + "]", currentScope, true, subsigs);

                        // put it in the bus symbol table
                		busMembers.put(symbol, subsigs);
					}
					numSignals++;

				}
				continue;
			}

			if (keyword.equals("$enddefinitions"))
			{
				parseToEnd();
				System.out.println("Found " + numSignals + " signal names");
				continue;
			}
			if (keyword.equals("$dumpvars"))
			{
				double curTime = 0.0;
				for(;;)
				{
					String currentLine = getLineAndUpdateProgress();
					if (currentLine == null) break;
					char chr = currentLine.charAt(0);
					String restOfLine = currentLine.substring(1);
					if (chr == '0' || chr == '1' || chr == 'x' || chr == 'X' ||
						chr == 'z' || chr == 'Z')
					{
						Object entry = symbolTable.get(restOfLine);
						if (entry == null)
						{
							System.out.println("Unknown symbol '" + restOfLine + "' on line " + lineReader.getLineNumber());
							continue;
						}
						if (entry instanceof List<?>) entry = ((List<Signal<?>>)entry).get(0);
						Signal<DigitalSample> sig = (Signal<DigitalSample>)entry;

						// insert the stimuli
						DigitalSample ds = null;
						switch (chr)
						{
							case '0': ds = DigitalSample.getSample(Value.LOW, Strength.LARGE_CAPACITANCE);   break;
							case '1': ds = DigitalSample.getSample(Value.HIGH, Strength.LARGE_CAPACITANCE);  break;
							case 'X':
							case 'x': ds = DigitalSample.getSample(Value.X, Strength.LARGE_CAPACITANCE);     break;
							case 'Z':
							case 'z': ds = DigitalSample.getSample(Value.Z, Strength.HIGH_IMPEDANCE);        break;
						}
			            ((MutableSignal<DigitalSample>)sig).addSample(curTime, ds);
						continue;
					}

					if (chr == '$')
					{
						if (restOfLine.equals("end")) continue;
						if (restOfLine.equals("dumpon")) continue;
						if (restOfLine.equals("dumpoff")) continue;
						if (restOfLine.equals("dumpall")) continue;
						System.out.println("Unknown directive on line " + lineReader.getLineNumber() + ": " + currentLine);
						continue;
					}
					if (chr == '#')
					{
						curTime = TextUtils.atof(restOfLine) * timeScale;
						continue;
					}
					if (chr == 'b')
					{
						int spacePos = restOfLine.indexOf(' ');
						if (spacePos < 0)
						{
							System.out.println("Bus has missing signal name on line " + lineReader.getLineNumber() + ": " + currentLine);
							continue;
						}
						String symName = restOfLine.substring(spacePos+1);
                		Signal<DigitalSample>[] members = busMembers.get(symName);
						for(int i=0; i<members.length; i++)
						{
							Signal<DigitalSample> subSig = members[i];
							char bit = restOfLine.charAt(i++);
							DigitalSample ds = null;
							switch (bit)
							{
								case '0': ds = DigitalSample.getSample(Value.LOW, Strength.LARGE_CAPACITANCE);   break;
								case '1': ds = DigitalSample.getSample(Value.HIGH, Strength.LARGE_CAPACITANCE);  break;
								case 'X':
								case 'x': ds = DigitalSample.getSample(Value.X, Strength.LARGE_CAPACITANCE);     break;
								case 'Z':
								case 'z': ds = DigitalSample.getSample(Value.Z, Strength.HIGH_IMPEDANCE);        break;
							}
				            ((MutableSignal<DigitalSample>)subSig).addSample(curTime, ds);
						}
						continue;
					}
					System.out.println("Unknown stimulus on line " + lineReader.getLineNumber() + ": " + currentLine);
				}
			}
			continue;
		}

		// remove singular top-level signal name (this code also occurs in HSpiceOut.readTRDCACFile)
        /*
		String singularPrefix = null;
		for(Signal sSig : an.getSignals())
		{
			String context = sSig.getSignalContext();
			if (context == null) { singularPrefix = null;   break; }
			int dotPos = context.indexOf('.');
			if (dotPos >= 0) context = context.substring(0, dotPos);
			if (singularPrefix == null) singularPrefix = context; else
			{
				if (!singularPrefix.equals(context)) { singularPrefix = null;   break; }
			}
		}
		if (singularPrefix != null)
		{
			int len = singularPrefix.length();
			for(Signal sSig : an.getSignals())
			{
				String context = sSig.getSignalContext();
				if (context == null || context.length() <= len) sSig.setSignalContext(null); else
				{
					sSig.setSignalContext(context.substring(len+1));
				}
			}
		}
        */
	}

	private void addSignalToHashMap(Signal<?> sig, String symbol, Map<String,Object> symbolTable)
	{
		Object entry = symbolTable.get(symbol);
		if (entry == null)
		{
			symbolTable.put(symbol, sig);
		} else if (entry instanceof Signal<?>)
		{
			List<Signal<?>> manySigs = new ArrayList<Signal<?>>();
			manySigs.add((Signal<?>)entry);
			manySigs.add(sig);
			symbolTable.put(symbol, manySigs);
		} else if (entry instanceof List<?>)
		{
			((List<Signal<?>>)entry).add(sig);
		}
	}

	private void parseToEnd()
		throws IOException
	{
		for(;;)
		{
			String keyword = getNextKeyword();
			if (keyword == null || keyword.equals("$end")) break;
		}
	}

	private String lastLine = null;
	private int linePos;
	private int lineLen;

	private String getNextKeyword()
		throws IOException
	{
		String keyword = null;
		for(;;)
		{
			if (lastLine == null)
			{
				lastLine = getLineAndUpdateProgress();
				if (lastLine == null) break;
				lineLen = lastLine.length();
				linePos = 0;
			}
			if (linePos < lineLen)
			{
				char ch = lastLine.charAt(linePos);
				if (ch == ' ' || ch == '\t')
				{
					linePos++;
				} else
				{
					int startOfKeyword = linePos;
					for(linePos++; linePos < lineLen; linePos++)
					{
						if (lastLine.charAt(linePos) == ' ') break;
					}
					keyword = lastLine.substring(startOfKeyword, linePos);
				}
			}
			if (linePos >= lineLen) lastLine = null;
			if (keyword != null) break;
		}
		return keyword;
	}

}
