/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: HSpiceOut.java
 * Input/output tool: reader for HSpice output (tr, pa, ac, sw, mt)
 * 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.input;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.tool.simulation.ComplexSample;
import com.sun.electric.tool.simulation.MutableSignal;
import com.sun.electric.tool.simulation.ScalarSample;
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.tool.simulation.SweptSample;
import com.sun.electric.util.TextUtils;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

/**
 * Class for reading and displaying waveforms from HSpice output.
 * This includes transient information in .tr and .pa files (.pa0/.tr0, .pa1/.tr1, ...)
 * It also includes AC analysis in .ac files; DC analysis in .sw files;
 * and Measurements in .mt files.
 *
 * While trying to debug the condition count handling, these test cases were observed:
 * CASE   VERSION  ANALYSIS   NUMNOI   SWEEPCNT   CNDCNT   CONDITIONS
 *  H01    9007       TR         0         4        1      bma_w
 *  H02    9007       TR        36        19        1      sweepv
 *  H03    9007       TR         2        30        1      MONTE_CARLO
 *                    DC       258        30        1      MONTE_CARLO
 *  H04    9007       TR         2         7        1      TEMPERATURE
 *                    DC         0         0        0
 *                    AC         0         6        1      bigcap
 *  H05    9601       TR         0         0        0
 *  H06    9601       TR         0         0        0
 *  H07    9601       TR         2        25        3      data_tim, inbufstr, outloadstr (sweep header has 2 numbers)
 *  H08    9601       TR         4         3        2      ccdata, cc
 *                    AC         4         2        1      lpvar (***CRASHES***)
 *  H09    9601       TR         0         3        3      rdata, r, c (sweep header has 2 numbers)
 *                    AC         0         3        3      rdata, r, c (sweep header has 2 numbers)
 *  H10    9601       TR         0         4        8      rdata, r0, r1, r2, r3, r4, c0, c1 (sweep header has 7 numbers)
 *                    AC         0         4        8      rdata, r0, r1, r2, r3, r4, c0, c1 (sweep header has 7 numbers)
 */
public class HSpiceOut extends Input<Stimuli>
{
	private static final boolean DEBUGCONDITIONS = false;
	private static final int BINARYBUFFERSIZE = 8192;
	/** true if tr/ac/sw file is binary */						private boolean isTRACDCBinary;
	/** true if binary tr/ac/sw file has bytes swapped */		private boolean isTRACDCBinarySwapped;
	/** the raw file base */									private String fileBase;
	/** the "tr" files (X.tr0, X.tr1, ...): transient */		private List<String> trFiles;
	/** the "sw" file extension (sw0, sw1, ...): DC */			private String swExtension;
	/** the "ic" file extension (ic0, ic1, ...): old DC */		private String icExtension;
	/** the "ac" file extension (ac0, ac1, ...): AC */			private String acExtension;
	/** the "mt" file extension (mt0, mt1, ...): measurement */	private String mtExtension;
	/** the "pa" file extension (pa0, pa1, ...): long names */	private String paExtension;
	/** signal names in file (may be continued in next) */		private String [] signalNames;
	/** signals created (may span multiple files) */			private Signal<?>[][] allSignals;
	/** sweep names (may span multiple files) */				private String [] sweepNames;
	private int binaryTRACDCSize, binaryTRACDCPosition;
	private boolean eofReached;
	private byte [] binaryTRACDCBuffer;

	/**
	 * Class to hold HSpice name associations from the .paX file
	 */
	private static class PALine
	{
		int	number;
		String string;
	}

	HSpiceOut() {
        super(null);
    }

	/**
	 * Method to read HSpice output files.
     * @param sd Stimuli associated to the reading.
	 * @param fileURL the URL to one of the output files.
	 * @param cell the Cell associated with these HSpice output files.
	 */
	protected Stimuli processInput(URL fileURL, Cell cell, Stimuli sd)
		throws IOException
	{
		// find all ".tr" files
		trFiles = new ArrayList<String>();
		String fileNameNoPath = TextUtils.getFileNameWithoutExtension(TextUtils.URLtoString(fileURL)) +
			".tr";
		String topDirName = TextUtils.getFilePath(fileURL);
		File topDir = new File(topDirName);
		String [] fileList = topDir.list();
		for(int i=0; i<fileList.length; i++)
		{
			if (!fileList[i].startsWith(fileNameNoPath)) continue;
			trFiles.add(topDirName + fileList[i]);
		}
		Collections.sort(trFiles, TextUtils.STRING_NUMBER_ORDER);
		if (trFiles.size() > 1)
			System.out.println("Reading " + trFiles.size() + " 'tr' files...");

		// figure out other file names
		swExtension = "sw0";
		icExtension = "ic0";
		acExtension = "ac0";
		mtExtension = "mt0";
		paExtension = "pa0";
		fileBase = fileURL.getFile();
		int dotPos = fileBase.lastIndexOf('.');
		if (dotPos > 0)
		{
			String extension = fileBase.substring(dotPos+1);
			fileBase = fileBase.substring(0, dotPos);
			if (extension.startsWith("sw") || extension.startsWith("ic") ||
				extension.startsWith("ac") || extension.startsWith("mt") || extension.startsWith("pa"))
			{
				swExtension = "sw" + extension.substring(2);
				icExtension = "ic" + extension.substring(2);
				acExtension = "ac" + extension.substring(2);
				mtExtension = "mt" + extension.substring(2);
				paExtension = "pa" + extension.substring(2);
			}
		}



		// the .pa file has name information
		List<PALine> paList = readPAFile(fileURL);

		// read Transient analysis data (.tr file)
		addTRData(sd, paList, fileURL);

		// read DC analysis data (.sw file)
		addDCData(sd, paList, fileURL);

		// read AC analysis data (.ac file)
		addACData(sd, paList, fileURL);

		// read measurement data (.mt file)
		addMeasurementData(sd, fileURL);

		// return the simulation data
		return sd;
	}

	/**
	 * Method to find the ".mt" file and read measurement data.
	 * @param sd the Stimuli to add this measurement data to.
	 * @param fileURL the URL to the ".tr" file.
	 * @throws IOException
	 */
	private void addMeasurementData(Stimuli sd, URL fileURL)
		throws IOException
	{
		// find the associated ".mt" name file
		URL mtURL = null;
		try
		{
			mtURL = new URI(fileURL.getProtocol(), null, fileURL.getHost(), fileURL.getPort(), fileBase + "." + mtExtension, null, null).toURL();
		} catch (Exception e) {}
		if (mtURL == null) return;
		if (!TextUtils.URLExists(mtURL)) return;
		if (openTextInput(mtURL)) return;
		System.out.println("Reading HSpice measurements file: " + mtURL.getFile());

		SignalCollection sc = Stimuli.newSignalCollection(sd, "MEASUREMENTS");
		List<String> measurementNames = new ArrayList<String>();
		HashMap<String,List<Double>> measurementData = new HashMap<String,List<Double>>();
		String lastLine = null;
		for(;;)
		{
			// get line from file
			String nextLine = lastLine;
			if (nextLine == null)
			{
				nextLine = lineReader.readLine();
				if (nextLine == null) break;
			}
			if (nextLine.startsWith("$") || nextLine.startsWith(".")) continue;
			String [] keywords = breakMTLine(nextLine, false);
			if (keywords.length == 0) break;

			// gather measurement names on the first time out
			if (measurementNames.size() == 0)
			{
				for(int i=0; i<keywords.length; i++)
					measurementNames.add(keywords[i]);
				for(;;)
				{
					lastLine = lineReader.readLine();
					if (lastLine == null) break;
					keywords = breakMTLine(lastLine, true);
					if (keywords.length == 0) { lastLine = null;   break; }
					if (TextUtils.isANumber(keywords[0])) break;
					for(int i=0; i<keywords.length; i++)
						if (keywords[i].length() > 0)
							measurementNames.add(keywords[i]);
				}
				for(String mName : measurementNames)
				{
					measurementData.put(mName, new ArrayList<Double>());
				}
				continue;
			}

			// get data values
			int index = 0;
			for(int i=0; i<keywords.length; i++)
			{
				if (keywords[i].length() == 0) continue;
				String mName = measurementNames.get(index++);
				List<Double> mData = measurementData.get(mName);
				mData.add(Double.valueOf(TextUtils.atof(keywords[i])));
			}
			for(;;)
			{
				if (index >= measurementNames.size()) break;
				lastLine = lineReader.readLine();
				if (lastLine == null) break;
				keywords = breakMTLine(lastLine, true);
				if (keywords.length == 0) break;
				for(int i=0; i<keywords.length; i++)
				{
					if (keywords[i].length() == 0) continue;
					String mName = measurementNames.get(index++);
					List<Double> mData = measurementData.get(mName);
					mData.add(Double.valueOf(TextUtils.atof(keywords[i])));
				}
			}
			lastLine = null;
			continue;
		}

		// convert this to a list of Measurements
		List<Double> argMeas = measurementData.get(measurementNames.get(0));
        double[] time = new double[argMeas.size()];
		for (int i = 0; i < argMeas.size(); i++)
            time[i] = argMeas.get(i).doubleValue();
		List<Signal<ScalarSample>> measData = new ArrayList<Signal<ScalarSample>>();
		for(String mName : measurementNames)
		{
			List<Double> mData = measurementData.get(mName);
			double[] values = new double[mData.size()];
			for(int i=0; i<mData.size(); i++) values[i] = mData.get(i).doubleValue();

			// special case with the "alter#" name...remove the "#"
			if (mName.equals("alter#")) mName = "alter";
			Signal<ScalarSample> as = ScalarSample.createSignal(sc, sd, mName, null, time, values);
			measData.add(as);
		}

		closeInput();
	}

	/**
	 * Method to parse a line from a measurement (.mt0) file.
	 * @param line the line from the file.
	 * @param continuation true if the line is supposed to be a continuation
	 * after the first line.
	 * @return an array of strings on the line (zero-length if at end).
	 */
	private String[] breakMTLine(String line, boolean continuation)
	{
		List<String> strings = new ArrayList<String>();
		for(int i=1; ; )
		{
			if (line.length() <= i+1) break;
			int end = i+17;
			if (end > line.length()) end = line.length();
			while (end < line.length() && line.charAt(end-1) != ' ') end++;
			String part = line.substring(i, end).trim();
//			if (i == 1)
//			{
//				// first token: make sure it is blank if a continuation
//				if (continuation && part.length() > 0) return new String[0];
//			}
			if (part.length() > 0) strings.add(part.trim());
			i = end;
		}
		int actualSize = strings.size();
		String [] retVal = new String[actualSize];
		for(int i=0; i<actualSize; i++) retVal[i] = strings.get(i);
		return retVal;
	}

	/**
	 * Method to find the ".tr" file and read transient data.
	 * @param sd the Stimuli to add this transient data to.
	 * @param fileURL the URL to the ".tr" file.
	 * @throws IOException
	 */
	private void addTRData(Stimuli sd, List<PALine> paList, URL fileURL)
		throws IOException
	{
		int numFiles = trFiles.size();
		for(int i=0; i<numFiles; i++)
		{
			// find the ".tr" name file
			String trFile = trFiles.get(i);
			URL trURL = null;
			try
			{
				trURL = new URI(fileURL.getProtocol(), null, fileURL.getHost(), fileURL.getPort(), trFile, null, null).toURL();
			} catch (Exception e) {}
			if (trURL == null) return;
			if (!TextUtils.URLExists(trURL)) return;

			// process the DC data
			readTRDCACFile(sd, trURL, paList, "TRANS SIGNALS", i, numFiles);
		}
	}

	/**
	 * Method to find the ".sw" file and read DC data.
	 * @param sd the Stimuli to add this DC data to.
	 * @param fileURL the URL to the ".tr" file.
	 * @throws IOException
	 */
	private void addDCData(Stimuli sd, List<PALine> paList, URL fileURL)
		throws IOException
	{
		// find the associated ".sw" name file
		URL swURL = null;
		try
		{
			swURL = new URI(fileURL.getProtocol(), null, fileURL.getHost(), fileURL.getPort(), fileBase + "." + swExtension, null, null).toURL();
		} catch (Exception e) {}
		if (swURL != null && TextUtils.URLExists(swURL))
		{
			// process the DC data
			readTRDCACFile(sd, swURL, paList, "DC SIGNALS", 0, 1);
			return;
		}

		// no associated ".sw" file, look for an ".ic" name file
		URL icURL = null;
		try
		{
			icURL = new URI(fileURL.getProtocol(), null, fileURL.getHost(), fileURL.getPort(), fileBase + "." + icExtension, null, null).toURL();
		} catch (Exception e) {}
		if (icURL != null && TextUtils.URLExists(icURL))
		{
			// can't process the DC data
			System.out.println("WARNING: Cannot read old DC format file (." + icExtension +
				")...must provide new format (." + swExtension + "): " + fileBase + "." + icExtension);
			return;
		}
	}

	/**
	 * Method to find the ".ac" file and read AC data.
	 * @param sd the Stimuli to add this AC data to.
	 * @param fileURL the URL to the ".tr" file.
	 * @throws IOException
	 */
	private void addACData(Stimuli sd, List<PALine> paList, URL fileURL)
		throws IOException
	{
		// find the associated ".ac" name file
		URL acURL = null;
		try
		{
			acURL = new URI(fileURL.getProtocol(), null, fileURL.getHost(), fileURL.getPort(), fileBase + "." + acExtension, null, null).toURL();
		} catch (Exception e) {}
		if (acURL == null) return;
		if (!TextUtils.URLExists(acURL)) return;

		// process the AC data
		readTRDCACFile(sd, acURL, paList, "AC SIGNALS", 0, 1);
	}

	/**
	 * Method to read the "pa" file with full symbol names.
	 * These files can end in "0", "1", "2",...
	 * @param fileURL the URL to the simulation output file
	 * @return a list of PALine objects that describe the name mapping file entries.
	 */
	private List<PALine> readPAFile(URL fileURL)
		throws IOException
	{
		// find the associated ".pa" name file
		URL paURL = null;
		try
		{
			paURL = new URI(fileURL.getProtocol(), null, fileURL.getHost(), fileURL.getPort(), fileBase + "." + paExtension, null, null).toURL();
		} catch (Exception e) {}
		if (paURL == null) return null;
		if (!TextUtils.URLExists(paURL)) return null;
		if (openTextInput(paURL)) return null;

		List<PALine> paList = new ArrayList<PALine>();
		for(;;)
		{
			// get line from file
			String nextLine = lineReader.readLine();
			if (nextLine == null) break;

			// break into number and name
			String trimLine = nextLine.trim();
			int spacePos = trimLine.indexOf(' ');
			if (spacePos > 0)
			{
				// save it in a PALine object
				PALine pl = new PALine();
				pl.number = TextUtils.atoi(trimLine, 0, 10);
				pl.string = removeLeadingX(trimLine.substring(spacePos+1).trim());
				paList.add(pl);
			}
		}
		closeInput();
		return paList;
	}

	private void readTRDCACFile(Stimuli sd, URL fileURL, List<PALine> paList, String analysisTitle, int fileNum, int numFiles)
		throws IOException
	{
		if (openBinaryInput(fileURL)) return;
		eofReached = false;
		resetBinaryTRACDCReader();

		SignalCollection sc;
		if (fileNum == 0)
		{
			sc = Stimuli.newSignalCollection(sd, analysisTitle);
		} else
		{
			sc = sd.findSignalCollection(analysisTitle);
			if (sc == null) return;
		}
		startProgressDialog("HSpice " + analysisTitle + " analysis", fileURL.getFile());
		if (fileNum == 0)
		{
			System.out.println("Reading HSpice " + analysisTitle + " file: " + fileURL.getFile());
		} else
		{
			System.out.println("Also reading file: " + fileURL.getFile());
		}

		// get number of nodes
		int nodcnt = getHSpiceInt();

		// switch to text reading if the file is text
		if (!isTRACDCBinary)
		{
			closeInput();
			if (openTextInput(fileURL)) return;
			eofReached = false;
			resetBinaryTRACDCReader();
			int nodcntTxt = getHSpiceInt();
			if (nodcnt != nodcntTxt)
				System.out.println("WARNING: Binary interpretation saw " + nodcnt + " nodes but text sees " + nodcntTxt);
		}

		// get number of special items
		int numnoi = getHSpiceInt();

		// get number of conditions
		int cndcnt = getHSpiceInt();

		/*
		 * Although this isn't documented anywhere, it appears that the 4th
		 * number in the file is a multiplier for the first, which allows
		 * there to be more than 10000 nodes.
		 */
		StringBuffer line = new StringBuffer();
		for(int j=0; j<4; j++) line.append((char)getByteFromFile());
		int multiplier = TextUtils.atoi(line.toString(), 0, 10);
		nodcnt += multiplier * 10000;
		int numSignals = numnoi + nodcnt - 1;

		if (numSignals <= 0)
		{
			System.out.println("Error reading " + fileURL.getFile());
			closeInput();
			stopProgressDialog();
			return;
		}

		// get version number (known to work with 9007, 9601)
		int version = getHSpiceInt();
		if (version != 9007 && version != 9601)
			System.out.println("Warning: may not be able to read HSpice files of type " + version);

		// ignore the unused/title information (4+72 characters over line break)
		line = new StringBuffer();
		for(int j=0; j<76; j++)
		{
			int k = getByteFromFile();
			line.append((char)k);
			if (!isTRACDCBinary && (k == '\n' || k == '\r')) j--;
		}

		// ignore the date/time information (16 characters)
		line = new StringBuffer();
		for(int j=0; j<16; j++) line.append((char)getByteFromFile());

		// ignore the copyright information (72 characters over line break)
		line = new StringBuffer();
		for(int j=0; j<72; j++)
		{
			int k = getByteFromFile();
			line.append((char)k);
			if (!isTRACDCBinary && (k == '\n' || k == '\r')) j--;
		}

		// get number of sweeps
		int sweepcnt = getHSpiceInt();
		if (DEBUGCONDITIONS)
			System.out.println("++++++++++++++++++++ FILE "+(fileNum+1)+"/"+numFiles+": BINARY="+isTRACDCBinary+
				" VERSION="+version+" NODECNT="+nodcnt+" SWEEPCNT="+sweepcnt+" CNDCNT="+cndcnt+" NUMNOI="+numnoi+" MULTIPLIER="+multiplier);
		if (cndcnt == 0) sweepcnt = 0;

		// ignore the Monte Carlo information (76 characters over line break)
		line = new StringBuffer();
		for(int j=0; j<76; j++)
		{
			int k = getByteFromFile();
			line.append((char)k);
			if (!isTRACDCBinary && (k == '\n' || k == '\r')) j--;
		}

		// get the type of each signal
		if (fileNum == 0)
		{
			signalNames = new String[numSignals];
		} else
		{
			if (numSignals != signalNames.length)
			{
				System.out.println("Error: file " + fileURL.getFile() + " has " + numSignals +
					" in it, but previous files have " + signalNames.length);
				closeInput();
				stopProgressDialog();
				return;				
			}
		}
		int [] signalTypes = new int[numSignals];
		for(int k=0; k<=numSignals; k++)
		{
			line = new StringBuffer();
			for(int j=0; j<8; j++)
			{
				int l = getByteFromFile();
				line.append((char)l);
				if (!isTRACDCBinary && (l == '\n' || l == '\r')) j--;
			}
			if (k == 0) continue;
			int l = k - nodcnt;
			if (k < nodcnt) l = k + numnoi - 1;
			String lineStr = line.toString().trim();
			signalTypes[l] = TextUtils.atoi(lineStr, 0, 10);
		}
		boolean paMissingWarned = false;
		for(int k=0; k<=numSignals; k++)
		{
			line = new StringBuffer();
			for(;;)
			{
				int l = getByteFromFile();
				if (l == '\n' || l == '\r') continue;
				if (l == ' ')
				{
					if (line.length() != 0) break;

					// if name starts with blank, skip until non-blank
					for(;;)
					{
						l = getByteFromFile();
						if (l != ' ') break;
					}
				}
				line.append((char)l);
				if (version == 9007 && line.length() >= 16) break;
			}
			int j = line.length();
			int l = (j+16) / 16 * 16 - 1;
			if (version == 9007)
			{
				l = (j+15) / 16 * 16 - 1;
			}
			for(; j<l; j++)
			{
				int i = getByteFromFile();
				if (!isTRACDCBinary && (i == '\n' || i == '\r')) { j--;   continue; }
			}
			if (k == 0) continue;

			// convert name if there is a colon in it
			int startPos = 0;
			int openPos = line.indexOf("(");
			if (openPos >= 0) startPos = openPos+1;
			for(j=startPos; j<line.length(); j++)
			{
				if (line.charAt(j) == ':') break;
				if (!TextUtils.isDigit(line.charAt(j))) break;
			}
			if (j < line.length() && line.charAt(j) == ':')
			{
				l = TextUtils.atoi(line.toString().substring(startPos), 0, 10);
				PALine foundPALine = null;
				if (paList == null)
				{
					if (!paMissingWarned)
						System.out.println("Warning: there should be a ." + paExtension + " file with extra signal names");
					paMissingWarned = true;
				} else
				{
					for(PALine paLine : paList)
					{
						if (paLine.number == l) { foundPALine = paLine;   break; }
					}
				}
				if (foundPALine != null)
				{
					StringBuffer newSB = new StringBuffer();
					newSB.append(line.substring(0, startPos));
					newSB.append(foundPALine.string);
					newSB.append(line.substring(j+1));
					line = newSB;
				}
			} else
			{
				if (line.indexOf(".") >= 0)
				{
					String fixedLine = removeLeadingX(line.toString());
					line = new StringBuffer();
					line.append(fixedLine);
				}
			}

			// move parenthesis from the start to the last name
			openPos = line.indexOf("(");
			if (openPos >= 0)
			{
				String parenPrefix = line.substring(0, openPos+1);
				int lastDot = line.lastIndexOf(".");
				if (lastDot >= 0)
				{
					StringBuffer newSB = new StringBuffer();
					if (parenPrefix.equalsIgnoreCase("v(") && line.charAt(line.length()-1) == ')')
					{
						// just ignore the V()
						newSB.append(line.substring(openPos+1, lastDot+1));
						newSB.append(line.substring(lastDot+1, line.length()-1));
					} else
					{
						// move the parenthetical wrapper to the last dotted piece
						newSB.append(line.substring(openPos+1, lastDot+1));
						newSB.append(parenPrefix);
						newSB.append(line.substring(lastDot+1));
					}
					line = newSB;
				} else if (parenPrefix.equalsIgnoreCase("v(") && line.charAt(line.length()-1) == ')')
				{
					StringBuffer newSB = new StringBuffer();
					// just ignore the V()
					newSB.append(line.substring(openPos+1, line.length()-1));
					line = newSB;
				}
			}

			if (k < nodcnt) l = k + numnoi - 1; else l = k - nodcnt;
			String sigName = line.toString();
			if (fileNum == 0)
			{
				signalNames[l] = sigName;
			} else
			{
				if (!sigName.equals(signalNames[l]))
				{
					System.out.println("Error: file " + fileURL.getFile() + " signal " + (l+1) + " is '" + sigName +
						"', but previous files call that signal '" + signalNames[l] + "'");
					closeInput();
					stopProgressDialog();
					return;				
				}
			}
		}

		// read (and ignore) condition information
		for(int c=0; c<cndcnt; c++)
		{
			int j = 0;
			line = new StringBuffer();
			for(;;)
			{
				int l = getByteFromFile();
				if (l == '\n' || l == '\r') continue;
				if (l == ' ') break;
				line.append((char)l);
				j++;
				if (j >= 16) break;
			}
			int l = (j+15) / 16 * 16 - 1;
			for(; j<l; j++)
			{
				int i = getByteFromFile();
				if (!isTRACDCBinary && (i == '\n' || i == '\r')) { j--;   continue; }
			}
			if (DEBUGCONDITIONS)
				System.out.println("CONDITION "+(c+1)+" IS "+line.toString());
		}

		// read the end-of-header marker
		line = new StringBuffer();
		if (!isTRACDCBinary)
		{
			// finish line, ensure the end-of-header
			for(int j=0; ; j++)
			{
				int l = getByteFromFile();
				if (l == '\n' || l == '\r') break;
				if (j < 4) line.append((char)l);
			}
		} else
		{
			// gather end-of-header string
			for(int j=0; j<4; j++)
				line.append((char)getByteFromFile());
		}
		if (!line.toString().equals("$&%#"))
		{
			System.out.println("HSpice header improperly terminated (got "+line.toString()+")");
			closeInput();
			stopProgressDialog();
			return;
		}
		resetBinaryTRACDCReader();

		// preprocess signal names to remove constant prefix (this code also occurs in VerilogOut.readVerilogFile)
		String constantPrefix = null;
		boolean hasPrefix = true;
		for(int k=0; k<numSignals; k++)
		{
			String name = signalNames[k];
			int dotPos = name.indexOf('.');
			if (dotPos < 0) continue;
			String prefix = name.substring(0, dotPos);
			if (constantPrefix == null) constantPrefix = prefix;
			if (!constantPrefix.equals(prefix)) { hasPrefix = false;   break; }
		}
		if (!hasPrefix) constantPrefix = null; else
		{
			String fileName = fileURL.getFile();
			int pos = fileName.lastIndexOf(File.separatorChar);
			if (pos >= 0) fileName = fileName.substring(pos+1);
			pos = fileName.lastIndexOf('/');
			if (pos >= 0) fileName = fileName.substring(pos+1);
			pos = fileName.indexOf('.');
			if (pos >= 0) fileName = fileName.substring(0, pos);
			if (fileName.equals(constantPrefix)) constantPrefix += "."; else
				constantPrefix = null;
		}

		boolean isComplex = analysisTitle.equals("AC SIGNALS");
		int sweepsThisFile = (sweepcnt > 0) ? sweepcnt : 1;
		if (fileNum == 0)
		{
			int sweepTotal = sweepsThisFile * numFiles;
			allSignals = new Signal[numSignals][sweepTotal];
	        sweepNames = new String[sweepTotal];
		}

		// setup the simulation information
		int sweepCounter = sweepcnt;
        int sweepIndex = 0;
		for(;;)
		{
			// get sweep info
            String sweepName = "";
			if (sweepcnt > 0)
			{
				float sweepValue = getHSpiceFloat(false);
				if (eofReached)  { System.out.println("EOF before sweep data");   break; }
				sweepName = TextUtils.formatDouble(sweepValue);
				if (DEBUGCONDITIONS) System.out.println("READING SWEEP NUMBER: "+sweepValue);
				// if there are more than 2 conditions, read extra sweep values
				for(int i=2; i<cndcnt; i++)
				{
					float anotherSweepValue = getHSpiceFloat(false);
					if (eofReached)  { System.out.println("EOF reading sweep header");   break; }
					sweepName += "," + TextUtils.formatDouble(anotherSweepValue);
					if (DEBUGCONDITIONS) System.out.println("  EXTRA SWEEP NUMBER: "+anotherSweepValue);
				}
                sweepNames[fileNum*sweepsThisFile + sweepIndex] = (sweepsThisFile > 1 ? "TR"+fileNum+"-" : "") + sweepName;
			} else if (numFiles > 1) sweepNames[fileNum] = (fileNum+1) + "";
			for(int k=0; k<numSignals; k++)
			{
				String name = signalNames[k];
				if (constantPrefix != null && name.startsWith(constantPrefix))
					name = name.substring(constantPrefix.length());
				String context = null;
				int lastDotPos = name.lastIndexOf('.');
				if (lastDotPos >= 0)
				{
					context = name.substring(0, lastDotPos);
					name = name.substring(lastDotPos+1);
				}
				if (sweepcnt > 0) name += "[" + sweepName + "]";
				SignalCollection scToUse = sc;
				if (sweepcnt > 0) scToUse = null;
				allSignals[k][fileNum*sweepsThisFile + sweepIndex] = isComplex
	                ? ComplexSample.createComplexSignal(scToUse, sd, name, context)
	                : ScalarSample.createSignal(scToUse, sd, name, context);
			}

			for(;;)
			{
				// get the first number, see if it terminates
				float time = getHSpiceFloat(true);
				if (eofReached) break;

				// get a row of numbers
				for(int k=0; k<numSignals; k++)
				{
					if (isComplex)
					{
                        MutableSignal<ComplexSample> signal = (MutableSignal<ComplexSample>)allSignals[(k + numnoi) % numSignals][fileNum*sweepsThisFile + sweepIndex];
						float realPart = getHSpiceFloat(false);
						float imagPart = getHSpiceFloat(false);
                        if (signal.getSample(time) == null)
                            signal.addSample(time, new ComplexSample(realPart, imagPart));
					} else
					{
                        MutableSignal<ScalarSample> signal = (MutableSignal<ScalarSample>)allSignals[(k + numnoi) % numSignals][fileNum*sweepsThisFile + sweepIndex];
                        double val = getHSpiceFloat(false);
                        if (signal.getSample(time) == null)
                            signal.addSample(time, new ScalarSample(val));
					}
					if (eofReached)
					{
						System.out.println("EOF in the middle of the data (at " + k + " out of " + numSignals +")");
						break;
					}
				}
				if (eofReached) { System.out.println("EOF before the end of the data");   break; }
			}
			sweepCounter--;
			if (sweepCounter <= 0) break;
			sweepIndex++;
			eofReached = false;
		}
		if (sweepcnt > 0 || (numFiles > 1 && fileNum == numFiles-1))
		{
			for(int k=0; k<numSignals; k++)
			{
				String name = signalNames[k];
				if (constantPrefix != null && name.startsWith(constantPrefix))
					name = name.substring(constantPrefix.length());
				String context = null;
				int lastDotPos = name.lastIndexOf('.');
				if (lastDotPos >= 0)
				{
					context = name.substring(0, lastDotPos);
					name = name.substring(lastDotPos+1);
				}

				int total = 0;
		        for(int i=0; i<allSignals[k].length; i++)
		        	if (allSignals[k][i] != null) total++;
		        Signal<?>[] signalCopy = new Signal[total];
		        int j = 0;
		        for(int i=0; i<allSignals[k].length; i++)
		        {
		        	if (allSignals[k][i] == null) continue;
		        	signalCopy[j] = allSignals[k][i];
		        	j++;
		        }
		        
				if (isComplex)
				{
					SweptSample.createSignal(sc, sd, name, context, false, (Signal<ComplexSample>[])signalCopy);
				} else
				{
					SweptSample.createSignal(sc, sd, name, context, false, (Signal<ScalarSample>[])signalCopy);
				}
			}
			sc.setSweepNames(sweepNames);
		}
		closeInput();

		stopProgressDialog();
		if (fileNum == numFiles-1)
			System.out.println("Done reading " + analysisTitle + " analysis");
	}

    /**
	 * Method to reset the binary block pointer (done between the header and
	 * the data).
	 */
	private void resetBinaryTRACDCReader()
	{
		binaryTRACDCSize = 0;
		binaryTRACDCPosition = 0;
	}

	/**
	 * Method to read the next block of tr, sw, or ac data.
	 * @param firstbyteread true to skip the first byte.
	 * @return true on EOF.
	 */
	private boolean readBinaryTRACDCBlock(boolean firstbyteread)
		throws IOException
	{
		// read the first word of a binary block
		if (!firstbyteread)
		{
			if (dataInputStream.read() == -1) return true;
			updateProgressDialog(1);
		}
		for(int i=0; i<3; i++)
			if (dataInputStream.read() == -1) return true;
		updateProgressDialog(3);

		// read the number of 8-byte blocks
		int blocks = 0;
		for(int i=0; i<4; i++)
		{
			int uval = dataInputStream.read();
			if (uval == -1) return true;
			if (isTRACDCBinarySwapped) blocks = ((blocks >> 8) & 0xFFFFFF) | ((uval&0xFF) << 24); else
				blocks = (blocks << 8) | uval;
		}
		updateProgressDialog(4);

		// skip the dummy word
		for(int i=0; i<4; i++)
			if (dataInputStream.read() == -1) return true;
		updateProgressDialog(4);

		// read the number of bytes
		int bytes = 0;
		for(int i=0; i<4; i++)
		{
			int uval = dataInputStream.read();
			if (uval == -1) return true;
			if (isTRACDCBinarySwapped) bytes = ((bytes >> 8) & 0xFFFFFF) | ((uval&0xFF) << 24); else
				bytes = (bytes << 8) | uval;
		}
		updateProgressDialog(4);

		// now read the data
		if (bytes > BINARYBUFFERSIZE)
		{
			System.out.println("ERROR: block is " + bytes + " long, but limit is " + BINARYBUFFERSIZE);
			bytes = BINARYBUFFERSIZE;
		}
		int amtread = dataInputStream.read(binaryTRACDCBuffer, 0, bytes);
		if (amtread != bytes)
		{
			System.out.println("Expected to read " + bytes + " bytes but got only " + amtread);
			return true;
		}
		updateProgressDialog(bytes);

		// read the trailer count
		int trailer = 0;
		for(int i=0; i<4; i++)
		{
			int uval = dataInputStream.read();
			if (uval == -1) return true;
			if (isTRACDCBinarySwapped) trailer = ((trailer >> 8) & 0xFFFFFF) | ((uval&0xFF) << 24); else
				trailer = (trailer << 8) | uval;
		}
		if (trailer != bytes)
		{
			System.out.println("Block trailer claims block had " + trailer + " bytes but block really had " + bytes);
			return true;
		}
		updateProgressDialog(4);

		// set pointers for the buffer
		binaryTRACDCPosition = 0;
		binaryTRACDCSize = bytes;
		return false;
	}

	/**
	 * Method to get the next character from the simulator.
	 * @return the next character (EOF at end of file).
	 */
	private int getByteFromFile()
		throws IOException
	{
		if (byteCount == 0)
		{
			// start of HSpice file: see if it is binary or ascii
			int i = dataInputStream.read();
			if (i == -1) return(i);
			updateProgressDialog(1);
			if (i == 0 || i == 4)
			{
				isTRACDCBinary = true;
				isTRACDCBinarySwapped = false;
				if (i == 4) isTRACDCBinarySwapped = true;
				binaryTRACDCBuffer = new byte[BINARYBUFFERSIZE];
				if (readBinaryTRACDCBlock(true)) return(-1);
			} else
			{
				isTRACDCBinary = false;
				return(i);
			}
		}
		if (isTRACDCBinary)
		{
			if (binaryTRACDCPosition >= binaryTRACDCSize)
			{
				if (readBinaryTRACDCBlock(false))
					return(-1);
			}
			int val = binaryTRACDCBuffer[binaryTRACDCPosition];
			binaryTRACDCPosition++;
			return val&0xFF;
		}
		int i = dataInputStream.read();
		updateProgressDialog(1);
		return i;
	}

	/**
	 * Method to get the next 4-byte integer from the simulator.
	 * @return the next integer.
	 */
	private int getHSpiceInt()
		throws IOException
	{
		StringBuffer line = new StringBuffer();
		for(int j=0; j<4; j++) line.append((char)getByteFromFile());
		return TextUtils.atoi(line.toString().trim(), 0, 10);
	}

	/**
	 * Method to read the next floating point number from the HSpice file.
	 * @return the next number.  Sets the global "eofReached" true on EOF.
	 */
	private float getHSpiceFloat(boolean testEOFValue)
		throws IOException
	{
		if (!isTRACDCBinary)
		{
			StringBuffer line = new StringBuffer();
			for(int j=0; j<11; j++)
			{
				int l = getByteFromFile();
				if (l == -1)
				{
					eofReached = true;   return 0;
				}
				line.append((char)l);
				if (l == '\n' || l == '\r') j--;
			}
			String result = line.toString();
			if (testEOFValue && result.equals("0.10000E+31")) { eofReached = true;   return 0; }
			return (float)TextUtils.atof(result);
		}

		// binary format
		int fi0 = getByteFromFile();
		int fi1 = getByteFromFile();
		int fi2 = getByteFromFile();
		int fi3 = getByteFromFile();
		if (fi0 < 0 || fi1 < 0 || fi2 < 0 || fi3 < 0)
		{
			eofReached = true;
			return 0;
		}
		fi0 &= 0xFF;
		fi1 &= 0xFF;
		fi2 &= 0xFF;
		fi3 &= 0xFF;
		int fi = 0;
		if (isTRACDCBinarySwapped)
		{
			fi = (fi3 << 24) | (fi2 << 16) | (fi1 << 8) | fi0;
		} else
		{
			fi = (fi0 << 24) | (fi1 << 16) | (fi2 << 8) | fi3;
		}
		float f = Float.intBitsToFloat(fi);

		// the termination value (in hex) is 71 49 F2 CA
		if (testEOFValue && f > 1.00000000E30 && f < 1.00000002E30)
		{
			eofReached = true;
			return 0;
		}
		return f;
	}

	/**
	 * Method to remove the leading "x" character in each dotted part of a string.
	 * HSpice decides to add "x" in front of every cell name, so the path "me.you"
	 * appears as "xme.xyou".
	 * @param name the string from HSpice.
	 * @return the string without leading "X"s.
	 */
	static String removeLeadingX(String name)
	{
		// remove all of the "x" characters at the start of every instance name
		int dotPos = -1;
		while (name.indexOf('.', dotPos+1) >= 0)
		{
			int xPos = dotPos + 1;
			if (name.length() > xPos && name.charAt(xPos) == 'x')
			{
				name = name.substring(0, xPos) + name.substring(xPos+1);
			}
			dotPos = name.indexOf('.', xPos);
			if (dotPos < 0) break;
		}
		return name;
	}
}
