/*
 * Electric(tm) VLSI Design System
 *
 * File: simspicerun.c
 * Companion file to simspice.c
 * Written by: Steven M. Rubin, Static Free Software
 *
 * Copyright (c) 2000 Static Free Software.
 *
 * 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 2 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 Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

#include "config.h"
#if SIMAID

#include "global.h"
#include "sim.h"
#include "efunction.h"
#include "usr.h"
#include "edialogs.h"

#include <math.h>
#include <errno.h>

/******************** SPICE NUMBER OUTPUT QUEUE ********************/

#define NONUMBERS  ((NUMBERS *)-1)

typedef struct Inumbers
{
	float  time;
	float *list;
	INTSML count;
	struct Inumbers *nextnumbers;
} NUMBERS;

NUMBERS *sim_numbersfree = NONUMBERS;

extern INTSML   spice_state;			/* value of "SIM_spice_state" */
extern char     sim_deck_facetname[];	/* name extracted from .SPO/TR0 file */
static FILE    *sim_streamfromsim;

/* the raw data from the SPICE simulator */
static NUMBERS *sim_window_numbers = NONUMBERS;		/* current list */
static char   **sim_window_signames;
static INTSML  *sim_window_sigtypes;
static float   *sim_window_time, *sim_window_val;
static INTSML   sim_window_signals = 0;				/* entries in "signames/sigtypes" */
static INTSML   sim_window_iter;
static INTSML   sim_window_limit = 0;				/* entries in "time/val" */
static float    sim_window_lowy, sim_window_highy;	/* range of an entry */
static INTBIG   sim_window_filepos;
static INTBIG   sim_window_filelen;

/* prototypes for local routines */
NUMBERS *sim_allocnumbers(INTSML);
void sim_freenumbers(NUMBERS*);
void sim_spice_terminate(FILE*, char*, char*);
INTSML sim_getfromsimulator(void);
INTSML sim_window_topofsignals(char**);
char *sim_window_nextsignals(void);
INTSML sim_spice_plotvalues(INTSML);
INTSML sim_window_ensurespace(INTSML);
INTBIG sim_spiceparseoutput(INTBIG running, FILE *outputfile, INTBIG parsemode);
INTBIG sim_window_findsignalname(char *name);
INTSML sim_spice_charhandlerschem(WINDOWPART *w, INTSML chr);
INTSML sim_spice_charhandlerwave(WINDOWPART *w, INTSML chr);

/*
 * routine to allocate a new numbers module from the pool (if any) or memory
 */
NUMBERS *sim_allocnumbers(INTSML total)
{
	REGISTER NUMBERS *d;

	if (sim_numbersfree == NONUMBERS)
	{
		d = (NUMBERS *)emalloc(sizeof (NUMBERS), sim_aid->cluster);
		if (d == 0) return(NONUMBERS);
	} else
	{
		d = sim_numbersfree;
		sim_numbersfree = (NUMBERS *)d->nextnumbers;
	}

	d->list = (float *)emalloc(((sizeof (float)) * total), sim_aid->cluster);
	if (d->list == 0) return(NONUMBERS);
	d->count = total;
	return(d);
}

/*
 * routine to return numbers module "d" to the pool of free modules
 */
void sim_freenumbers(NUMBERS *d)
{
	efree((char *)d->list);
	d->nextnumbers = sim_numbersfree;
	sim_numbersfree = d;
}

/******************** SPICE EXECUTION AND PLOTTING ********************/

#define MAXTRACES   7
#define MAXLINE   200

/*
 * routine to execute SPICE with "infile" as the input deck file, "outfile"
 * as the output listing file.  If "infile" is null, do not run the simulator,
 * but just read "outfile" as the output listing.  If "infile" is not null,
 * run SPICE on that file.
 */
void sim_spice_execute(char *infile, char *outfile)
{
	char line[MAXLINE+5], *ptr;
	REGISTER INTBIG i, important, parsemode;
	NODEPROTO *np;
	NUMBERS *num;
	REGISTER FILE *outputfile;
	VARIABLE *var;
	extern DIALOG us_progressdialog;
	char *host, *path, *name, *execpars[8], *truename;

	/* determine execution/parsing mode */
	var = getvalkey((INTBIG)sim_aid, VAID, VINTEGER, sim_dontrun);
	if (var == NOVARIABLE) parsemode = SIMRUNNO; else
		parsemode = var->addr;

	outputfile = NULL;
	sim_window_filelen = -1;
	if (*infile == 0)
	{
		/* parsing output into simulation window: make sure none is active */
		if (sim_window_isactive(&np) != 0)
		{
			ttyputerr(_("Only one simulation window can run at a time"));
			ttyputmsg(_("Terminate the current one before reading SPICE output"));
			return;
		}

		/* no input deck, simply use output listing */
		sim_streamfromsim = xopen(truepath(outfile), sim_filetypespiceout, "", &truename);
		if (sim_streamfromsim == NULL)
		{
			ttyputerr(_("Cannot read %s"), truename);
			return;
		}
		sim_window_filelen = filesize(sim_streamfromsim);
	} else
	{
		/* input deck exists, run SPICE on it */
		sim_streamfromsim = 0;

		/* for communication with simulator, read fromsim[0] */
		(void)epipe(sim_fromsim);

		sim_process = efork();
		if (sim_process == 1)
		{
			ttyputmsg(_("Run SPICE directly please"));
			return;
		}
		if (sim_process == 0)
		{
			REGISTER INTBIG save0, save1, save2;

			save0 = channelreplacewithfile(0, infile);
			if ((parsemode == SIMRUNYESQPARSE || parsemode == SIMRUNYESQ) &&
				(spice_state & SPICETYPE) == SPICEHSPICE)
			{
				save1 = channelreplacewithfile(1, "/dev/null");
				save2 = channelreplacewithchannel(2, sim_fromsim[1]);
			} else
			{
				save1 = channelreplacewithchannel(1, sim_fromsim[1]);
				save2 = channelreplacewithfile(2, "/dev/null");
			}
			switch (spice_state & SPICETYPE)
			{
				case SPICE2:      name = "spice";    break;
				case SPICE3:      name = "spice3";   break;
				case SPICEHSPICE: name = "hspice";   break;
			}
			path = egetenv("ELECTRIC_SPICELOC");
			if (path == NULL) path = SPICELOC;
			host = egetenv("ELECTRIC_REMOTESPICEHOST");
			if (host != NULL)
			{
				/* spice on a remote machine */
				execpars[0] = "rsh";
				execpars[1] = host;
				execpars[2] = path;
				execpars[3] = infile;
				execpars[4] = 0;
				eexec("/usr/ucb/rsh", execpars);
			} else
			{
				/* spice on the local machine */
				execpars[0] = name;
				execpars[1] = infile;
				execpars[2] = 0;
				eexec(path, execpars);
			}

			channelrestore(0, save0);
			channelrestore(1, save1);
			channelrestore(2, save2);
			ttyputerr(_("Cannot run %s"), path);
			exit(1);
		}

		/* close the file descriptor so the pipe will terminate when done */
		eclose(sim_fromsim[1]);
		if (parsemode == SIMRUNYESQ && (spice_state & SPICETYPE) == SPICEHSPICE)
			parsemode = SIMRUNYES;

		/* prepare output file if requested */
		if (*outfile != 0)
		{
			char *truename;

			outputfile = xcreate(outfile, sim_filetypespiceout, _("SPICE Output File"), &truename);
			if (outputfile == NULL)
			{
				if (truename != 0) ttyputerr(_("Cannot write SPICE output file %s"), truename);
			}
		}
	}

	/*
	 * Determine the "spice_state".  If we're doing a write-execute-parse,
	 * then we've already done this.  If we're just doing a parse, we have not.
	 */
	var = getvalkey((INTBIG)sim_aid, VAID, VINTEGER, sim_spice_state);
	if (var != NOVARIABLE) spice_state = (INTSML)var->addr; else spice_state = 0;

	/* free any former data */
	if (sim_window_signals > 0)
	{
		for(i=0; i<sim_window_signals; i++) efree(sim_window_signames[i]);
		efree((char *)sim_window_signames);
		efree((char *)sim_window_sigtypes);
		sim_window_signals = 0;
	}
	while (sim_window_numbers != NONUMBERS)
	{
		num = sim_window_numbers;
		sim_window_numbers = sim_window_numbers->nextnumbers;
		sim_freenumbers(num);
	}

	/* show progress if possible */
	if (sim_window_filelen > 0)
	{
		if (DiaInitDialog(&us_progressdialog) != 0) return;
		DiaPercent(1, 0);
	}
	sim_window_filepos = 0;

	/* if executing and not parsing, simply echo execution progress */
	if ((parsemode == SIMRUNYES || parsemode == SIMRUNYESQ) && *infile != 0)
	{
		ptr = line;
		for(;;)
		{
			if (stopping(STOPREASONSPICE)) break;
			i = sim_getfromsimulator();
			if (i == EOF) break;
			if (parsemode == SIMRUNYESQ) continue;
			*ptr = (char)i;
			if (*ptr == '\n' || ptr - line >= MAXLINE)
			{
				*ptr = 0;
				ttyputmsg("%s", line);
				ptr = line;
			} else ptr++;
		}
		important = -1;
	} else
	{
		important = sim_spiceparseoutput(*infile != 0, outputfile, parsemode);
	}

	/* clean up */
	if (sim_window_filelen > 0) DiaDoneDialog();
	(void)sim_spice_terminate(outputfile, outfile, infile);
	if (stopping(STOPREASONSPICE)) return;

	/* plot the list of numbers */
	if (important < 0) return;
	if (sim_spice_plotvalues((INTSML)important) != 0) ttyputerr(_("Problem making the plot"));
}

/*
 * Parse the SPICE output file.  If "running" is nonzero, echo commands (send them
 * to "outputfile" if it is valid).
 * Returns the number of important traces in the data (-1 on error).
 */
INTBIG sim_spiceparseoutput(INTBIG running, FILE *outputfile, INTBIG parsemode)
{
	char line[MAXLINE+5], *ptr;
	REGISTER INTSML j, k, l, datamode, knows, past_end, important,
		nodcnt, numnoi, first;
	REGISTER INTBIG i;
	float numbers[20], lastt;
	NUMBERS *num, *numend;

	/* read from the simulator and list on the status terminal */
	if ((spice_state & SPICETYPE) == SPICE2)
	{
		/* parse SPICE 2 format output */
		past_end = datamode = 0;
		first = 1;
		numend = NONUMBERS;
		ptr = line;
		for(;;)
		{
			if (stopping(STOPREASONSPICE)) break;
			i = sim_getfromsimulator();
			if (i == EOF) break;
			*ptr = (char)i;
			if (*ptr == '\n' || ptr - line >= MAXLINE)
			{
				*ptr++ = 0;   *ptr = 0;
				if (sim_window_filelen > 0) DiaPercent(1, sim_window_filepos*100/sim_window_filelen);
				if (first != 0)
				{
					/* check the first line for HSPICE format possibility */
					first = 0;
					if (strlen(line) >= 20 && line[16] == '9' && line[17] == '0' &&
						line[18] == '0' && line[19] == '7')
					{
						ttyputerr(_("This is an HSPICE file, not a SPICE2 file"));
						ttyputerr(_("Change the SPICE format and reread"));
						return(-1);
					}
				}
				if (running != 0)
				{
					if (outputfile == NULL)
					{
						if (parsemode == SIMRUNYESPARSE)
							ttyputmsg("%s", (isprint(*line) ? line : &line[1]));
					} else sim_spice_xprintf(outputfile, "%s\n", line);
				}

				/* look for a facet name */
				if ((sim_deck_facetname[0] == '\0') && (namesamen(line, "*** FACET",9) == 0))
					if ((i = sscanf(line+9,"%s",sim_deck_facetname)) != 1)
						sim_deck_facetname[0] = '\0';
				if (namesamen(line, ".END", 4) == 0 && namesamen(line, ".ENDS", 5) != 0)
				{
					past_end = 1;
					ptr = line;
					continue;
				}
				if (past_end && datamode == 0)
				{
					if ((isspace(line[0]) || line[0] == '-') && isdigit(line[1]))
						datamode = 1;
				}
				if (past_end && datamode == 1)
				{
					if (!((isspace(line[0]) || line[0] == '-') && isdigit(line[1])))
					{
						datamode = 0;
						past_end = 0;
					}
				}
				if (datamode != 0)
				{
					ptr = line;
					sim_window_signals = 0;
					for(;;)
					{
						while (isspace(*ptr)) ptr++;
						if (*ptr == 0) break;
						numbers[sim_window_signals++] = (float)atof(ptr);
						while (*ptr != ' ' && *ptr != 0) ptr++;
					}
					if (sim_window_signals > 1)
					{
						sim_window_signals--;
						num = sim_allocnumbers(sim_window_signals);
						if (num == NONUMBERS) break;
						num->time = numbers[0];
						for(i=0; i<sim_window_signals; i++) num->list[i] = numbers[i+1];
						if (numend == NONUMBERS) sim_window_numbers = num; else
							numend->nextnumbers = num;
						num->nextnumbers = NONUMBERS;
						numend = num;
					}
				}
				ptr = line;
				continue;
			}
			ptr++;
		}

		/* generate dummy names */
		important = sim_window_signals;
		sim_window_signames = (char **)emalloc(sim_window_signals * (sizeof (char *)), sim_aid->cluster);
		sim_window_sigtypes = (INTSML *)emalloc(sim_window_signals * SIZEOFINTSML, sim_aid->cluster);
		if (sim_window_signames == 0 || sim_window_sigtypes == 0)
		{
			/* terminate execution so we can restart simulator */
			return(-1);
		}
		for(i=0; i<sim_window_signals; i++)
		{
			(void)sprintf(line, "Signal %ld", i+1);
			(void)allocstring(&sim_window_signames[i], line, sim_aid->cluster);
		}
	} else if ((spice_state & SPICETYPE) == SPICE3)
	{
		/* parse SPICE 3 format output */
		numend = NONUMBERS;
		ptr = line;
		first = 1;
		for(;;)
		{
			if (stopping(STOPREASONSPICE)) break;
			i = sim_getfromsimulator();
			if (i == EOF) break;
			*ptr = (char)i;
			if (*ptr == '\n' || ptr - line >= MAXLINE)
			{
				*ptr = 0;
				if (sim_window_filelen > 0) DiaPercent(1, sim_window_filepos*100/sim_window_filelen);

				if (first != 0)
				{
					/* check the first line for HSPICE format possibility */
					first = 0;
					if (strlen(line) >= 20 && line[16] == '9' && line[17] == '0' &&
						line[18] == '0' && line[19] == '7')
					{
						ttyputerr(_("This is an HSPICE file, not a SPICE3 file"));
						ttyputerr(_("Change the SPICE format and reread"));
						return(-1);
					}
				}

				/* skip first word if there is an "=" in the line */
				for(ptr = line; *ptr != 0; ptr++) if (*ptr == '=') break;
				if (*ptr == 0) ptr = line; else ptr += 3;

				/* read the data values */
				knows = 0;
				lastt = 0.0;
				for(;;)
				{
					while (*ptr == ' ' || *ptr == '\t') ptr++;
					if (*ptr == 0 || *ptr == ')') break;
					if (sim_window_signals == 0)
					{
						num = sim_allocnumbers(MAXTRACES);
						num->time = (float)atof(ptr);
						if (numend == NONUMBERS) sim_window_numbers = num; else
						{
							numend->nextnumbers = num;
							if (num->time <= lastt && knows == 0)
							{
								ttyputerr(_("First trace not increasing values"));
								ttyputmsg(_("   this should be 'time'"));
								knows++;
							}
						}
						lastt = num->time;
						num->nextnumbers = NONUMBERS;
						numend = num;
					} else
					{
						if (num == NONUMBERS) ttyputmsg(_("Line %ld of data has too many values"),
							sim_window_signals); else
						{
							if (sim_window_signals <= MAXTRACES)
								num->list[sim_window_signals-1] = (float)atof(ptr);
							num = num->nextnumbers;
						}
					}
					while (*ptr != ' ' && *ptr != '\t' && *ptr != 0) ptr++;
				}

				/* see if there is an ")" at the end of the line */
				if (line[strlen(line)-1] == ')')
				{
					/* advance to the next value for subsequent reads */
					if (sim_window_signals != 0 && num != NONUMBERS)
						ttyputmsg(_("Line %ld of data has too few values"), sim_window_signals);
					sim_window_signals++;
					num = sim_window_numbers;
				}

				if (running != 0)
				{
					if (outputfile == NULL)
					{
						if (parsemode == SIMRUNYESPARSE)
							ttyputmsg("%s", (isprint(*line) ? line : &line[1]));
					} else sim_spice_xprintf(outputfile, "%s\n", line);
				}
				ptr = line;
				continue;
			}
			ptr++;
		}

		/* generate dummy names */
		important = sim_window_signals;
		sim_window_signames = (char **)emalloc(sim_window_signals * (sizeof (char *)), sim_aid->cluster);
		sim_window_sigtypes = (INTSML *)emalloc(sim_window_signals * SIZEOFINTSML, sim_aid->cluster);
		if (sim_window_signames == 0 || sim_window_sigtypes == 0)
		{
			/* terminate execution so we can restart simulator */
			return(-1);
		}
		for(i=0; i<sim_window_signals; i++)
		{
			(void)sprintf(line, "Signal %ld", i+1);
			(void)allocstring(&sim_window_signames[i], line, sim_aid->cluster);
		}
	} else if ((spice_state & SPICETYPE) == SPICEHSPICE)
	{
		/* parse HSPICE format output */
		/* get number of nodes, special items, and conditions */
		line[4] = 0;
		for(j=0; j<4; j++) line[j] = (char)sim_getfromsimulator();
		nodcnt = atoi(line);
		for(j=0; j<4; j++) line[j] = (char)sim_getfromsimulator();
		numnoi = atoi(line);
		for(j=0; j<4; j++) line[j] = (char)sim_getfromsimulator();
		/* cndcnt = atoi(line); */
		for(j=0; j<4; j++) line[j] = (char)sim_getfromsimulator();
		sim_window_signals = numnoi + nodcnt - 1;

		/* get version number (known to work with 9007, 9601) */
		for(j=0; j<4; j++) line[j] = (char)sim_getfromsimulator();
		j = atoi(line);

		/* ignore the unused/title information (4+72 characters over line break) */
		for(j=0; j<76; j++)
		{
			k = sim_getfromsimulator();
			if (k == '\n') j--;	/* was 10 at end of line */
		}

		/* ignore the date/time information (16 characters) */
		for(j=0; j<16; j++) (void)sim_getfromsimulator();

		/* ignore the copywrite information (72 characters over line break) */
		for(j=0; j<72; j++)
		{
			k = sim_getfromsimulator();
			if (k == '\n') j--;
		}

		/* get number of sweeps */
		line[4] = 0;
		for(j=0; j<4; j++) line[j] = (char)sim_getfromsimulator();
		/* sweepcnt = atoi(line); */

		/* ignore the Monticarlo information (76 characters over line break) */
		for(j=0; j<76; j++)
		{
			k = sim_getfromsimulator();
			if (k == '\n') j--;
		}
		if (sim_window_filelen > 0) DiaPercent(1, sim_window_filepos*100/sim_window_filelen);

		/* get the type of each signal */
		important = numnoi;
		sim_window_signames = (char **)emalloc(sim_window_signals * (sizeof (char *)), sim_aid->cluster);
		sim_window_sigtypes = (INTSML *)emalloc(sim_window_signals * SIZEOFINTSML, sim_aid->cluster);
		if (sim_window_signames == 0 || sim_window_sigtypes == 0)
		{
			/* terminate execution so we can restart simulator */
			return(-1);
		}
		line[8] = 0;
		for(k=0; k<=sim_window_signals; k++)
		{
			for(j=0; j<8; j++)
			{
				l = sim_getfromsimulator();
				line[j] = (char)l;
				if (l == '\n') j--;
			}
			if (k == 0) continue;
			if (k < nodcnt) l = k + numnoi - 1; else l = k - nodcnt;
			sim_window_sigtypes[l] = atoi(line);
		}
		line[16] = 0;
		for(k=0; k<=sim_window_signals; k++)
		{
			j = 0;
			for(;;)
			{
				l = sim_getfromsimulator();
				if (l == '\n') continue;
				if (l == ' ') break;
				line[j++] = (char)l;
			}
			line[j] = 0;
			l = (j+15) / 16 * 16 - 1;
			for(; j<l; j++)
			{
				i = sim_getfromsimulator();
				if (i == '\n') { j--;   continue; }
			}
			if (k == 0) continue;
			if (k < nodcnt) l = k + numnoi - 1; else l = k - nodcnt;
			(void)allocstring(&sim_window_signames[l], line, sim_aid->cluster);
		}

		/* finish line, ensure the end-of-header */
		for(j=0; ; j++)
		{
			l = sim_getfromsimulator();
			if (l == '\n') break;
			if (j < 4) line[j] = (char)l;
		}
		line[4] = 0;
		if (strcmp(line, "$&%#") != 0)
		{
			ttyputerr(_("HSPICE header improperly terminated"));
			return(-1);
		}

		/* now read the data */
		sim_window_numbers = numend = NONUMBERS;
		for(;;)
		{
			if (stopping(STOPREASONSPICE)) break;
			if (sim_window_filelen > 0) DiaPercent(1, sim_window_filepos*100/sim_window_filelen);

			/* get the first number, see if it terminates */
			line[11] = 0;
			for(j=0; j<11; j++)
			{
				l = sim_getfromsimulator();
				if (l == EOF)
				{
					ttyputerr(_("EOF too soon"));
					return(0);
				}
				line[j] = (char)l;
				if (l == '\n') j--;
			}
			if (strcmp(line, "0.10000E+31") == 0) break;

			/* save a row of numbers */
			num = sim_allocnumbers(sim_window_signals);
			if (numend == NONUMBERS) sim_window_numbers = num; else
				numend->nextnumbers = num;
			num->nextnumbers = NONUMBERS;
			numend = num;

			/* load the numbers */
			num->time = (float)atof(line);
			for(k=1; k<=sim_window_signals; k++)
			{
				for(j=0; j<11; j++)
				{
					l = sim_getfromsimulator();
					if (l == EOF)
					{
						ttyputerr(_("EOF too soon"));
						return(0);
					}
					line[j] = (char)l;
					if (l == '\n') j--;
				}
				if (k < nodcnt) l = k + numnoi - 1; else l = k - nodcnt;
				num->list[l] = (float)atof(line);
			}
		}
	}
	return(important);
}

/* terminate SPICE execution */
void sim_spice_terminate(FILE *outputfile, char *outfile, char *infile)
{
	if (sim_streamfromsim != 0) xclose(sim_streamfromsim); else
		eclose(sim_fromsim[0]);

	if (outputfile != NULL)
	{
		xclose(outputfile);
		ttyputverbose(_("%s written"), outfile);
	}
	if (*infile != 0)
	{
		if (ekill(sim_process) == 0)
		{
			ewait(sim_process);
			if (errno >= 0)
				ttyputmsg(_("SPICE execution reports error %d"), errno);
					else ttyputmsg(_("SPICE execution terminated"));
		} else ttyputmsg(_("SPICE execution complete"));
		sim_process = -1;
	}
}

INTSML sim_getfromsimulator(void)
{
	INTBIG i;
	char val;

	if (sim_streamfromsim != 0)
	{
		i = xfread(&val, 1, 1, sim_streamfromsim);
	} else
	{
		i = eread(sim_fromsim[0], &val, 1);
	}
	if (i != 1) return(EOF);
	sim_window_filepos++;
	return(val);
}

INTSML sim_window_topofsignals(char **c)
{
	sim_window_iter = 0;
	return(1);
}

char *sim_window_nextsignals(void)
{
	char *nextname;

	if (sim_window_iter >= sim_window_signals) return(0);
	nextname = sim_window_signames[sim_window_iter];
	sim_window_iter++;
	return(nextname);
}

static COMCOMP sim_window_picktrace = {NOKEYWORD, sim_window_topofsignals,
	sim_window_nextsignals, NOPARAMS, NOBACKUP, 0, " \t", N_("pick a signal to display"), ""};

/*
 * routine to display the numbers in "sim_window_numbers".  For each entry of the
 * linked list is another time sample, with "sim_window_signals" trace points.  The
 * names of these signals is in "sim_window_signames".  Only the first "important"
 * entries should be displayed initially.  Returns nonzero on error.
 */
INTSML sim_spice_plotvalues(INTSML important)
{
	float lowy, highy, r, mul, tl,th, min, max;
	REGISTER NUMBERS *num;
	REGISTER INTSML i, numtotal;
	char *pars[3];
	REGISTER INTBIG ysteps, j, tr;
	REGISTER NODEPROTO *plotnp;
	extern COMCOMP us_yesnop, us_showdp;

	/* count the number of values */
	for(num = sim_window_numbers, numtotal=0; num != NONUMBERS; num = num->nextnumbers)
		numtotal++;
	if (numtotal == 0) return(0);

	/* We have to establish a link for the plot facet */
	plotnp = NONODEPROTO;

	/* if we're already simulating a facet, use that one */
	if (sim_simnt != NONODEPROTO) plotnp = sim_simnt; else
		if (sim_deck_facetname[0] != '\0') plotnp = getnodeproto(sim_deck_facetname);
	if (plotnp != NONODEPROTO && plotnp->primindex != 0) plotnp = NONODEPROTO;
	if (plotnp != NONODEPROTO)
	{
		(void)initinfstr();
		(void)formatinfstr(_("Is this a simulation of facet %s?"),
			describenodeproto(plotnp));
		i = ttygetparam(returninfstr(), &us_yesnop, 3, pars);
		if (i == 1)
		{
			if (pars[0][0] == 'n') plotnp = NONODEPROTO;
		}
	}

	/* one final chance to pick a facet */
	if (plotnp == NONODEPROTO)
	{
		i = ttygetparam(_("Please select a facet to associate with this plot: "),
			&us_showdp, 3, pars);
		if (i > 0) plotnp = getnodeproto(pars[0]);
	}
	if (plotnp == NONODEPROTO) return(1);

	/* get memory for the values */
	if (sim_window_ensurespace(numtotal) != 0) return(1);

	/* get Y range of values */
	lowy = highy = sim_window_numbers->time;
	j = 0;
	for(num = sim_window_numbers; num != NONUMBERS; num = num->nextnumbers)
	{
		sim_window_time[j++] = num->time;
		for(i=0; i<sim_window_signals; i++)
		{
			if (num->list[i] < lowy) lowy = num->list[i];
			if (num->list[i] > highy) highy = num->list[i];
		}
	}

	/* spread the Y values if they are all equal */
	if (lowy == highy)
	{
		highy = lowy * 2.0f;   lowy = 0.0;
		if (highy == 0.0) highy = 1.0;
	}

	/* determine range of values, multiply units by "mul" to get magnitude */
	r = highy - lowy;
	mul = 1.0;
	while (r > 10.0) { r /= 10.0;   mul *= 10.0; }
	while (r < 1.0)  { r *= 10.0;   mul /= 10.0; }

	/* range selected, step lower bound to a half-step below */
	ysteps = (INTBIG)(r-1);
	j = (INTBIG)((lowy+highy) / 2.0 / mul);
	do
	{
		ysteps++;
		tl = j*mul - ysteps * mul / 2.0f;
	} while (tl > lowy);

	/* adjust upper bound to a half step above */
	th = tl + ysteps*mul;
	while (th < highy)
	{
		ysteps++;
		th = tl + ysteps*mul;
	}

	/* set the globals with the range of the simulation window */
	sim_window_lowy = tl;   sim_window_highy = th;

	/* make the simulation window */
	if (sim_window_create(1, plotnp, sim_spice_charhandlerwave,
		sim_spice_charhandlerschem) != 0) return(1);

	/* create the trace nodes */
	sim_window_setanaextents(sim_window_lowy, sim_window_highy);
	sim_window_setanarange(sim_window_lowy, sim_window_highy);
	for(i=0; i<important; i++)
	{
		tr = sim_window_newtrace(0, sim_window_signames[i], 0);
		j = 0;
		for(num = sim_window_numbers; num != NONUMBERS; num = num->nextnumbers)
			sim_window_val[j++] = (num->list[i] - sim_window_lowy) /
				(sim_window_highy - sim_window_lowy);
		sim_window_loadanatrace(tr, numtotal, sim_window_time, sim_window_val);
	}

	/* clean up */
	min = sim_window_time[0];   max = sim_window_time[numtotal-1];
	sim_window_settimerange(min, max);
	sim_window_setmaincursor((max-min)*0.25f + min);
	sim_window_setextensioncursor((max-min)*0.75f + min);
	sim_window_redraw();
	return(0);
}

/*
 * routine to ensure that there is space for "count" time samples in the global arrays
 * "sim_window_time" and "sim_window_val".  Returns nonzero on error.
 */
INTSML sim_window_ensurespace(INTSML count)
{
	REGISTER INTSML take;

	if (count <= sim_window_limit) return(0);
	if (sim_window_limit > 0)
	{
		efree((char *)sim_window_time);
		efree((char *)sim_window_val);
		sim_window_limit = 0;
	}
	take = count + 10;
	sim_window_time = (float *)emalloc(take * (sizeof (float)), sim_aid->cluster);
	if (sim_window_time == 0) return(1);
	sim_window_val = (float *)emalloc(take * (sizeof (float)), sim_aid->cluster);
	if (sim_window_val == 0) return(1);
	sim_window_limit = take;
	return(0);
}

INTSML sim_spice_charhandlerschem(WINDOWPART *w, INTSML chr)
{
	char *par[3];

	switch (chr)
	{
		case '?':		/* help */
			ttyputmsg(_("These keys may be typed in the SPICE Schematic window:"));
			ttyputinstruction(" d", 6, _("move down the hierarchy"));
			ttyputinstruction(" u", 6, _("move up the hierarchy"));
			ttyputinstruction(" o", 6, _("overlay selected network on top of current signal line"));
			ttyputinstruction(" a", 6, _("add selected network to simulation window"));
			return(0);
		case 'a':		/* add trace */
			sim_window_addhighlightednet(0);
			return(0);
		case 'o':		/* overlap trace */
			sim_window_addhighlightednet(1);
			return(0);
		case 'd':		/* move down the hierarchy */
			us_editfacet(0, par);
			return(0);
		case 'u':		/* move up the hierarchy */
			us_outhier(0, par);
			return(0);
	}
	return(us_charhandler(w, chr));
}

INTSML sim_spice_charhandlerwave(WINDOWPART *w, INTSML chr)
{
	char *par[3], s2[20];
	float v, time, low, high;
	REGISTER INTBIG tr, trl;
	REGISTER INTSML i, j, thispos, pos, lines, traces;
	REGISTER NUMBERS *num;

	/* see if there are special functions for SPICE waveform simulation */
	switch (chr)
	{
		case '?':		/* help */
			ttyputmsg(_("These keys may be typed in the SPICE Waveform window:"));
			ttyputinstruction(" 9", 6, _("show entire vertical range"));
			ttyputinstruction(" 7", 6, _("zoom vertical range in"));
			ttyputinstruction(" 0", 6, _("zoom vertical range out"));
			ttyputinstruction(" 8", 6, _("shift vertical range up"));
			ttyputinstruction(" 2", 6, _("shift vertical range down"));
			ttyputinstruction(" i", 6, _("show value of selected signal at main cursor"));
			ttyputinstruction(" a", 6, _("add signal to simulation window"));
			ttyputinstruction(" o", 6, _("overlay signal on top of current signal line"));
			ttyputinstruction(" r", 6, _("remove selected signal from simulation window"));
			ttyputinstruction(" s", 6, _("save snapshot of simulation window in database"));
			return(0);
		case '9':		/* show entire vertical range */
			sim_window_getanaextents(&low, &high);
			sim_window_setanarange(low, high);
			sim_window_redraw();
			return(0);
		case '7':		/* zoom vertical range in */
			sim_window_getanarange(&low, &high);
			time = (high-low) / 4;
			low = low + time;
			high = high - time;
			sim_window_setanarange(low, high);
			sim_window_redraw();
			return(0);
		case '0':		/* zoom vertical range out */
			sim_window_getanarange(&low, &high);
			time = (high-low) / 2;
			low = low - time;
			high = high + time;
			sim_window_setanarange(low, high);
			sim_window_redraw();
			return(0);
		case '8':		/* shift vertical range up */
			sim_window_getanarange(&low, &high);
			time = (high-low) / 4;
			low = low - time;
			high = high - time;
			sim_window_setanarange(low, high);
			sim_window_redraw();
			return(0);
		case '2':		/* shift vertical range down */
			sim_window_getanarange(&low, &high);
			time = (high-low) / 4;
			low = low + time;
			high = high + time;
			sim_window_setanarange(low, high);
			sim_window_redraw();
			return(0);
		case 'i':		/* show trace value */
			tr = sim_window_gethighlighttrace();
			if (tr == 0) return(0);
			time = sim_window_getmaincursor();
			v = sim_window_getanatracevalue(tr, time);
			sim_windowconvertengineeringnotation(time, s2);
			ttyputmsg(_("Signal %s has value %g at time %s"), sim_window_gettracename(tr), v, s2);
			return(0);
		case 's':		/* save image in {simulation_snapshot} view */
			sim_window_savegraph();
			return(0);
		case 'a':		/* add trace */
			i = ttygetparam(_("Signal to add"), &sim_window_picktrace, 3, par);
			if (i == 0) return(0);
			i = (INTSML)sim_window_findsignalname(par[0]);
			if (i < 0) return(0);

			/* count the number of traces */
			sim_window_inittraceloop();
			for(traces=0; ; traces++) if (sim_window_nexttraceloop() == 0) break;

			/* if there are no traces, put new trace on line zero */
			if (traces == 0) j = 0; else
			{
				/* other traces exist, make a new line in the plot */
				j = sim_window_getlines();
				sim_window_setlines((INTSML)(j+1));
			}

			/* create a new trace in the last slot */
			tr = sim_window_newtrace(j, sim_window_signames[i], 0);
			j = 0;
			for(num = sim_window_numbers; num != NONUMBERS; num = num->nextnumbers)
				sim_window_val[j++] = (num->list[i] - sim_window_lowy) /
					(sim_window_highy - sim_window_lowy);
			sim_window_loadanatrace(tr, j, sim_window_time, sim_window_val);
			sim_window_sethighlighttrace(tr);
			sim_window_redraw();
			return(0);
		case 'o':		/* overlap trace */
			tr = sim_window_gethighlighttrace();
			if (tr == 0) return(0);
			thispos = sim_window_gettraceline(tr);
			i = ttygetparam(_("Signal to overlay"), &sim_window_picktrace, 3, par);
			if (i == 0) return(0);
			i = (INTSML)sim_window_findsignalname(par[0]);
			if (i < 0) return(0);

			/* create a new trace in this slot */
			tr = sim_window_newtrace(thispos, sim_window_signames[i], 0);
			j = 0;
			for(num = sim_window_numbers; num != NONUMBERS; num = num->nextnumbers)
				sim_window_val[j++] = (num->list[i] - sim_window_lowy) /
					(sim_window_highy - sim_window_lowy);
			sim_window_loadanatrace(tr, j, sim_window_time, sim_window_val);
			sim_window_sethighlighttrace(tr);
			sim_window_redraw();
			return(0);
		case 'r':		/* remove trace */
			tr = sim_window_gethighlighttrace();
			if (tr == 0) return(0);
			sim_window_sethighlighttrace(0);

			/* see if any other traces use this line */
			thispos = sim_window_gettraceline(tr);
			sim_window_inittraceloop();
			for(;;)
			{
				trl = sim_window_nexttraceloop();
				if (trl == tr) continue;
				if (trl == 0) break;
				pos = sim_window_gettraceline(trl);
				if (pos == thispos) break;
			}
			lines = sim_window_getlines();
			if (trl == 0 && lines > 1)
			{
				/* no other traces on this line, delete it */
				sim_window_inittraceloop();
				for(;;)
				{
					trl = sim_window_nexttraceloop();
					if (trl == 0) break;
					pos = sim_window_gettraceline(trl);
					if (pos > thispos) sim_window_settraceline(trl, (INTSML)(pos-1));
				}
				sim_window_setlines((INTSML)(lines-1));
			}

			/* kill trace, redraw */
			sim_window_killtrace(tr);
			sim_window_redraw();
			return(0);
	}
	return(us_charhandler(w, chr));
}

void sim_window_addhighlightednet(INTSML overlay)
{
	REGISTER ARCINST *ai;
	NODEPROTO *np;
	REGISTER INTBIG i, tr, traceline, j;
	REGISTER char *pt;
	REGISTER NUMBERS *num;

	if ((sim_window_isactive(&np) & SIMWINDOWWAVEFORM) == 0)
	{
		ttyputerr(_("Not displaying a waveform"));
		return;
	}

	ai = (ARCINST *)askaid(us_aid, "get-arc");
	if (ai == NOARCINST)
	{
		ttyputerr(_("Select an arc first"));
		return;
	}
	if (ai->network == NONETWORK)
	{
		ttyputerr(_("This arc has no network information"));
		return;
	}
	pt = sim_spice_signalname(ai->network);
	if (pt == 0)
	{
		ttyputerr(_("Cannot get SPICE signal for network %s"), pt);
		return;
	}
	i = sim_window_findsignalname(pt);
	if (i < 0)
	{
		ttyputerr(_("Cannot find network %s in the simulation data"), pt);
		return;
	}

	if (sim_window_findtrace(sim_window_signames[i]) != 0)
	{
		ttyputerr(_("Signal %s is already being displayed"), sim_window_signames[i]);
		return;
	}

	/* figure out where to show the new signal */
	if (overlay == 0)
	{
		/* count the number of traces */
		sim_window_inittraceloop();
		for(j=0; ; j++) if (sim_window_nexttraceloop() == 0) break;

		/* if there are no traces, put new trace on line zero */
		if (j == 0) traceline = 0; else
		{
			/* other traces exist, make a new line in the plot */
			traceline = sim_window_getlines();
			sim_window_setlines((INTSML)(traceline+1));
		}
	} else
	{
		tr = sim_window_gethighlighttrace();
		sim_window_sethighlighttrace(0);
		if (tr != 0)
		{
			traceline = sim_window_gettraceline(tr);
		} else
		{
			/* just use the first trace */
			sim_window_inittraceloop();
			tr = sim_window_nexttraceloop();
			if (tr != 0)
			{
				traceline = sim_window_gettraceline(tr);
			} else
			{
				/* no traces: create a new line */
				traceline = 0;
				sim_window_setlines((INTSML)(traceline+1));
			}
		}
	}

	/* create a new trace in this slot */
	tr = sim_window_newtrace((INTSML)traceline, sim_window_signames[i], 0);
	j = 0;
	for(num = sim_window_numbers; num != NONUMBERS; num = num->nextnumbers)
		sim_window_val[j++] = (num->list[i] - sim_window_lowy) /
			(sim_window_highy - sim_window_lowy);
	sim_window_loadanatrace(tr, (INTSML)j, sim_window_time, sim_window_val);
	sim_window_redraw();
	sim_window_sethighlighttrace(tr);
}

/*
 * Routine to return the NETWORK associated with HSPICE signal name "name".
 */
NETWORK *sim_spice_networkfromname(char *name)
{
	NODEPROTO *np;
	REGISTER NODEPROTO *cnp;
	REGISTER VARIABLE *var;
	REGISTER char *pt, *start;
	REGISTER NODEINST *ni;
	REGISTER NETWORK *net;

	/* get simulation facet, quit if not simulating */
	if (sim_window_isactive(&np) == 0) return(NONETWORK);

	/* parse the names, separated by dots */
	start = name;
	for(;;)
	{
		for(pt = start; *pt != 0 && *pt != '.'; pt++) ;
		if (*pt == 0) break;

		/* get node name that is pushed into */
		*pt = 0;
		if (*start == 'x') start++;
		for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		{
			var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, el_node_name);
			if (var == NOVARIABLE) continue;
			if (namesame(start, (char *)var->addr) == 0) break;
		}
		*pt++ = '.';
		if (ni == NONODEINST) return(NONETWORK);
		start = pt;
		np = ni->proto;
		cnp = contentsview(np);
		if (cnp != NONODEPROTO) np = cnp;
	}

	/* find network "start" in facet "np" */
	net = getnetwork(start, np);
	return(net);
}

/*
 * Routine to return the SPICE network name of network "net".
 */
char *sim_spice_signalname(NETWORK *net)
{
	NODEPROTO *np;
	REGISTER char *prevstr;
	REGISTER NODEPROTO *netpar;
	REGISTER VARIABLE *varname;
	REGISTER PORTPROTO *pp;
	REGISTER PORTARCINST *pi;
	REGISTER PORTEXPINST *pe;
	REGISTER NODEINST *ni;

	/* get simulation facet, quit if not simulating */
	if (sim_window_isactive(&np) == 0) return(0);

	/* shift this network up the hierarchy as far as it is exported */
	for(;;)
	{
		/* if this network is at the current level of hierarchy, stop going up the hierarchy */
		if (net->parent == np) break;

		/* find the instance that is the proper parent of this facet */
		ni = descentparent(net->parent);
		if (ni == NONODEINST) break;

		/* see if the network is exported from this level */
		for(pp = net->parent->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
			if (pp->network == net) break;
		if (pp != NOPORTPROTO)
			pp = equivalentport(net->parent, pp, ni->proto);
		if (pp == NOPORTPROTO) break;

		/* exported: find the network in the higher level */
		for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
			if (pi->proto == pp)
		{
			net = pi->conarcinst->network;
			break;
		}
		if (pi == NOPORTARCINST)
		{
			for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
				if (pe->proto == pp)
			{
				net = pe->exportproto->network;
				break;
			}
			if (pe == NOPORTEXPINST) break;
		}
	}

	/* construct a path to the top-level of simulation from the net's current level */
	netpar = net->parent;
	prevstr = "";
	for(;;)
	{
		if (netpar->cell == np->cell) break;

		/* find the instance that is the proper parent of this facet */
		ni = descentparent(netpar);
		if (ni == NONODEINST) break;

		varname = getvalkey((INTBIG)ni, VNODEINST, VSTRING, el_node_name);
		if (varname == 0)
		{
			ttyputerr(_("Back annotation is missing"));
			return(0);
		}
		(void)initinfstr();
		(void)addtoinfstr('x');
		(void)addstringtoinfstr((char *)varname->addr);
		if (*prevstr != 0)
		{
			(void)addtoinfstr('.');
			(void)addstringtoinfstr(prevstr);
		}
		prevstr = returninfstr();
		netpar = ni->parent;
	}

	(void)initinfstr();
	(void)addstringtoinfstr(prevstr);
	if (*prevstr != 0) (void)addtoinfstr('.');
	if (net->namecount <= 0) (void)addstringtoinfstr(describenetwork(net)); else
		(void)addstringtoinfstr(net->netname);
	return(returninfstr());
}

INTBIG sim_window_findsignalname(char *name)
{
	REGISTER INTBIG i;

	for(i=0; i<sim_window_signals; i++)
		if (namesame(name, sim_window_signames[i]) == 0) return(i);
	return(-1);
}

#endif  /* SIMAID - at top */
