/*
 * Electric(tm) VLSI Design System
 *
 * File: simalsgraph.c
 * Asynchronous Logic Simulator graphics interface
 * From algorithms by: Brent Serbin and Peter J. Gallant
 * Last maintained by: Steven M. Rubin
 *
 * 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 "simals.h"
#include "usr.h"
#include "network.h"

static INTSML sim_window_iter;
static INTSML sim_als_wantweak = 0, sim_als_wantstrong = 0;

/* prototypes for local routines */
INTSML sim_window_topofdsignals(char**);
char *sim_window_nextdsignals(void);
INTBIG simals_count_channels(CONPTR);
void simals_helpwindow(INTSML waveform);

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

char *sim_window_nextdsignals(void)
{
	char *nextname;

	for(; ; sim_window_iter++)
	{
		if (sim_window_iter >= simals_levelptr->num_chn) return(0);
		if (simals_levelptr->display_page[sim_window_iter+1].nodeptr == 0) continue;
		if (simals_levelptr->display_page[sim_window_iter+1].displayptr == 0) break;
	}
	nextname = simals_levelptr->display_page[sim_window_iter+1].name;
	sim_window_iter++;
	return(nextname);
}

static COMCOMP sim_window_pickdtrace = {NOKEYWORD, sim_window_topofdsignals,
	sim_window_nextdsignals, NOPARAMS, NOBACKUP, 0, " \t", N_("pick a signal to display"), ""};

/*
 * The character handler for the waveform window of ALS simulation
 */
INTSML simals_charhandlerwave(WINDOWPART *w, INTSML chr)
{
	REGISTER INTSML i, j, traces, pos, thispos, lines, strength;
	REGISTER INTBIG tr, trl, nexttr, prevtr, state, highsig, windowstate;
	REGISTER VARIABLE *var;
	char *par[30], *name;
	NODEPTR node, nodehead;
	NODEPROTO *np;
	float endtime;
	LINKPTR sethead;

	ttynewcommand();

	/* can always preserve snapshot */
	if (chr == 'p')
	{
		sim_window_savegraph();
		sim_als_wantweak = sim_als_wantstrong = 0;
		return(0);
	}

	/* can always do help */
	if (chr == '?')
	{
		simals_helpwindow(1);
		sim_als_wantweak = sim_als_wantstrong = 0;
		return(0);
	}

	/* if not simulating, don't handle any simulation commands */
	if (sim_window_isactive(&np) == 0)
		return(us_charhandler(w, chr));

	switch (chr)
	{
		/* update display */
		case 'u':
			endtime = simals_initialize_simulator(1);
			var = getvalkey((INTBIG)sim_aid, VAID, VINTEGER, sim_window_state);
			if (var == NOVARIABLE) windowstate = 0; else windowstate = var->addr;
			if ((windowstate&ADVANCETIME) != 0) sim_window_setmaincursor(endtime);
			return(0);

		/* add trace */
		case 'a':
			i = ttygetparam(_("Signal to add"), &sim_window_pickdtrace, 3, par);
			if (i == 0) return(0);

			/* find the signal */
			for(i=0; i<simals_levelptr->num_chn; i++)
			{
				node = simals_levelptr->display_page[i+1].nodeptr;
				if (node == 0) continue;
				if (simals_levelptr->display_page[i+1].displayptr != 0) continue;
				name = simals_levelptr->display_page[i+1].name;
				if (namesame(name, par[0]) == 0) break;
			}
			if (i >= simals_levelptr->num_chn) return(0);

			/* ready to add: remove highlighting */
			sim_window_sethighlighttrace(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, simals_levelptr->display_page[i+1].name,
				(INTBIG)simals_levelptr->display_page[i+1].nodeptr);
			simals_levelptr->display_page[i+1].displayptr = tr;
			simals_fill_display_arrays();
			sim_window_redraw();
			sim_window_sethighlighttrace(tr);
			sim_als_wantweak = sim_als_wantstrong = 0;
			return(0);

		/* handle weak and strong prefixes */
		case 'w':
			if (sim_als_wantweak != 0 || sim_als_wantstrong != 0) ttybeep();
			sim_als_wantweak = 1;
			return(0);
		case 's':
			if (sim_als_wantweak != 0 || sim_als_wantstrong != 0) ttybeep();
			sim_als_wantstrong = 1;
			return(0);

		/* different flavors of low values */
		case '0':
		case 'l':
			state = LOGIC_LOW;    strength = GATE_STRENGTH;
			if (sim_als_wantweak != 0) strength = NODE_STRENGTH; else
				if (sim_als_wantstrong != 0) strength = VDD_STRENGTH;
			break;

		/* different flavors of high values */
		case '1':
		case 'h':
			state = LOGIC_HIGH;   strength = GATE_STRENGTH;
			if (sim_als_wantweak != 0) strength = NODE_STRENGTH; else
				if (sim_als_wantstrong != 0) strength = VDD_STRENGTH;
			break;

		/* different flavors of undefined values */
		case 'x':
			state = LOGIC_X;      strength = GATE_STRENGTH;
			if (sim_als_wantweak != 0) strength = NODE_STRENGTH; else
				if (sim_als_wantstrong != 0) strength = VDD_STRENGTH;
			break;

		/* signal clock setting, info, erasing, removing (all handled later) */
		case 'c':
		case 'i':
		case 'e':
		case 'r':
			break;

		default:
			sim_als_wantweak = sim_als_wantstrong = 0;
			return(us_charhandler(w, chr));
	}
	sim_als_wantweak = sim_als_wantstrong = 0;

	/* the following commands demand a current trace...get it */
	highsig = sim_window_gethighlighttrace();
	if (highsig == 0)
	{
		ttyputerr(_("Select a signal name first"));
		return(0);
	}

	if (chr == 'c')		/* set clock waveform */
	{
		par[0] = sim_window_gettracename(highsig);
		simals_clock_command(1, par);
		return(0);
	}
	if (chr == 'i')		/* print signal info */
	{
		par[0] = "state";
		par[1] = sim_window_gettracename(highsig);
		simals_print_command(2, par);
		return(0);
	}
	if (chr == 'e')		/* clear signal vectors */
	{
		par[0] = "delete";
		par[1] = sim_window_gettracename(highsig);
		par[2] = "all";
		simals_vector_command(3, par);
		return(0);
	}
	if (chr == 'r')		/* remove trace */
	{
		sim_window_sethighlighttrace(0);

		/* delete it */
		thispos = sim_window_gettraceline(highsig);
		sim_window_inittraceloop();
		nexttr = prevtr = 0;
		for(;;)
		{
			trl = sim_window_nexttraceloop();
			if (trl == 0) break;
			pos = sim_window_gettraceline(trl);
			if (pos > thispos)
			{
				pos--;
				if (pos == thispos) nexttr = trl;
				sim_window_settraceline(trl, (INTSML)pos);
			} else if (pos == thispos-1) prevtr = trl;
		}
		lines = sim_window_getlines();
		if (lines > 1) sim_window_setlines((INTSML)(lines-1));

		/* remove from the simulator's list */
		for(i=0; i<simals_levelptr->num_chn; i++)
		{
			node = simals_levelptr->display_page[i+1].nodeptr;
			if (node == 0) continue;
			if (simals_levelptr->display_page[i+1].displayptr == highsig)
			{
				simals_levelptr->display_page[i+1].displayptr = 0;
				break;
			}
		}

		/* kill trace, redraw */
		sim_window_killtrace(highsig);
		if (nexttr != 0) sim_window_sethighlighttrace(nexttr); else
			if (prevtr != 0) sim_window_sethighlighttrace(prevtr);
		sim_window_redraw();
		return(0);
	}

	/* handle setting of values on signals */
	nodehead = simals_find_node(sim_window_gettracename(highsig));
	if (! nodehead) return(0);
	sethead = simals_alloc_link_mem();
	if (sethead == 0) return(0);
	sethead->type = 'N';
	sethead->ptr = (char *)nodehead;
	sethead->state = state;
	sethead->strength = strength;
	sethead->priority = 2;
	sethead->time = sim_window_getmaincursor();
	sethead->right = 0;
	simals_insert_set_list(sethead);

	endtime = simals_initialize_simulator(0);
	var = getvalkey((INTBIG)sim_aid, VAID, VINTEGER, sim_window_state);
	if (var == NOVARIABLE) windowstate = 0; else windowstate = var->addr;
	if ((windowstate&ADVANCETIME) != 0) sim_window_setmaincursor(endtime);
	return(0);
}

/*
 * The character handler for the schematic/layout window of ALS simulation
 */
INTSML simals_charhandlerschem(WINDOWPART *w, INTSML chr)
{
	INTBIG state, highsig, windowstate;
	REGISTER VARIABLE *var;
	REGISTER INTSML strength;
	REGISTER NETWORK *net;
	char *par[30], *pt;
	float endtime;
	NODEPTR nodehead;
	LINKPTR sethead;

	ttynewcommand();
	switch (chr)
	{
		case '?':
			simals_helpwindow(0);
			sim_als_wantweak = sim_als_wantstrong = 0;
			return(0);
		case 'u':
			endtime = simals_initialize_simulator(1);
			var = getvalkey((INTBIG)sim_aid, VAID, VINTEGER, sim_window_state);
			if (var == NOVARIABLE) windowstate = 0; else windowstate = var->addr;
			if ((windowstate&ADVANCETIME) != 0) sim_window_setmaincursor(endtime);
			return(0);
		case 'w':
			if (sim_als_wantweak != 0 || sim_als_wantstrong != 0) ttybeep();
			sim_als_wantweak = 1;
			return(0);
		case 's':
			if (sim_als_wantweak != 0 || sim_als_wantstrong != 0) ttybeep();
			sim_als_wantstrong = 1;
			return(0);
		case '0':
		case 'l':
			state = LOGIC_LOW;    strength = GATE_STRENGTH;
			if (sim_als_wantweak != 0) strength = NODE_STRENGTH; else
				if (sim_als_wantstrong != 0) strength = VDD_STRENGTH;
			break;
		case '1':
		case 'h':
			state = LOGIC_HIGH;   strength = GATE_STRENGTH;
			if (sim_als_wantweak != 0) strength = NODE_STRENGTH; else
				if (sim_als_wantstrong != 0) strength = VDD_STRENGTH;
			break;
		case 'x':
			state = LOGIC_X;      strength = GATE_STRENGTH;
			if (sim_als_wantweak != 0) strength = NODE_STRENGTH; else
				if (sim_als_wantstrong != 0) strength = VDD_STRENGTH;
			break;
		case 'c':
		case 'i':
		case 'e':
			break;
		default:
			sim_als_wantweak = sim_als_wantstrong = 0;
			return(us_charhandler(w, chr));
	}
	sim_als_wantweak = sim_als_wantstrong = 0;

	/* find the net in the waveform window */
	net = net_gethighlightednet(0, 0);
	if (net == NONETWORK) return(0);
	if (net->namecount > 0) pt = net->netname; else
		pt = describenetwork(net);
	sim_window_sethighlighttrace(sim_window_findtrace(pt));

	/* get waveform trace and make changes */
	highsig = sim_window_gethighlighttrace();
	if (highsig == 0)
	{
		ttyputerr(_("Select a signal name first"));
		return(0);
	}

	if (chr == 'c')
	{
		par[0] = sim_window_gettracename(highsig);
		simals_clock_command(1, par);
		return(0);
	}
	if (chr == 'i')
	{
		par[0] = "state";
		par[1] = sim_window_gettracename(highsig);
		simals_print_command(2, par);
		return(0);
	}
	if (chr == 'e')
	{
		par[0] = "delete";
		par[1] = sim_window_gettracename(highsig);
		par[2] = "all";
		simals_vector_command(3, par);
		simals_fill_display_arrays();
		sim_window_redraw();
		return(0);
	}

	nodehead = simals_find_node(sim_window_gettracename(highsig));
	if (nodehead == 0) return(0);

	sethead = simals_alloc_link_mem();
	if (sethead == 0) return(0);
	sethead->type = 'N';
	sethead->ptr = (char *)nodehead;
	sethead->state = state;
	sethead->strength = strength;
	sethead->priority = 2;
	sethead->time = sim_window_getmaincursor();
	sethead->right = 0;
	simals_insert_set_list(sethead);
	endtime = simals_initialize_simulator(0);

	var = getvalkey((INTBIG)sim_aid, VAID, VINTEGER, sim_window_state);
	if (var == NOVARIABLE) windowstate = 0; else windowstate = var->addr;
	if ((windowstate&ADVANCETIME) != 0) sim_window_setmaincursor(endtime);
	return(0);
}

void simals_helpwindow(INTSML waveform)
{
	REGISTER INTSML active;
	NODEPROTO *np;

	active = sim_window_isactive(&np);

	if (waveform != 0)
		ttyputmsg(_("These keys may be typed in the ALS Waveform window:")); else
			ttyputmsg(_("These keys may be typed in the ALS Schematic window:"));
	if (active != 0)
	{
		ttyputinstruction("L,0", 4, _("Set signal low (gate strength)"));
		ttyputinstruction("WL",  4, _("Set signal weak low (node strength)"));
		ttyputinstruction("SL",  4, _("Set signal strong low (power strength)"));
		ttyputinstruction("H,1", 4, _("Set signal high (gate strength)"));
		ttyputinstruction("WH",  4, _("Set signal weak high (node strength)"));
		ttyputinstruction("SH",  4, _("Set signal strong high (power strength)"));
		ttyputinstruction(" X",  4, _("Set signal undefined (gate strength)"));
		ttyputinstruction("WX",  4, _("Set signal weak undefined (node strength)"));
		ttyputinstruction("SX",  4, _("Set signal strong undefined (power strength)"));
		ttyputinstruction(" C",  4, _("Set signal to be a clock"));
		ttyputinstruction(" E",  4, _("Delete all vectors on signal"));
		ttyputinstruction(" I",  4, _("Print information about signal"));
		ttyputinstruction(" U",  4, _("Update display (resimulate)"));
	}

	if (waveform != 0)
	{
		if (active != 0)
		{
			ttyputinstruction(" A", 4, _("Add signal to simulation window"));
			ttyputinstruction(" R", 4, _("Remove signal from the window"));
		}
		ttyputinstruction(" P", 4, _("Preserve snapshot of simulation window in database"));
	}
}

/*
 * Name: simals_count_channels
 *
 * Description:
 *	This function returns the number of exported channels in a given level.
 *
 * Calling Argument:
 *	level_pointer : pointer to a level
 */
INTBIG simals_count_channels(CONPTR level_pointer)
{
	INTBIG count=0;
	EXPTR exhead;

	for (exhead = level_pointer->exptr; exhead; exhead = exhead->next) count++;
	return(count);
}

/*
 * Name: simals_set_current_level
 *
 * Description:
 *	This procedure is used to initialize the display channels to the current
 * level of the database hierarchy.  Returns nonzero on error.
 */
INTSML simals_set_current_level(void)
{
	INTBIG i, chn, windowstate;
	REGISTER VARIABLE *var;
	NODEPTR nodehead;
	NODE *node;
	EXPTR exhead;
	char *name;
	NODEPROTO *np;

	for (nodehead = simals_noderoot; nodehead; nodehead = nodehead->next)
		nodehead->plot_node = 0;

	if (simals_levelptr->display_page)
	{
		exhead = simals_levelptr->exptr;
		for (i = 1; i <= simals_levelptr->num_chn; i++)
		{
			if (exhead)
			{
				exhead->nodeptr->plot_node = 1;
				exhead = exhead->next;
			}
		}
	} else
	{
		simals_levelptr->num_chn = (INTSML)simals_count_channels(simals_levelptr);
		chn = simals_levelptr->num_chn + 1;
		simals_levelptr->display_page = (CHNPTR)simals_alloc_mem((INTBIG)(chn * sizeof(CHANNEL)));
		if (simals_levelptr->display_page == 0) return(1);

		exhead = simals_levelptr->exptr;
		for (i = 1; i < chn; i++)
		{
			if (exhead)
			{
				name = exhead->node_name;
				simals_levelptr->display_page[i].nodeptr = exhead->nodeptr;
				exhead->nodeptr->plot_node = 1;
				exhead = exhead->next;
			} else
			{
				name = "";
				simals_levelptr->display_page[i].nodeptr = 0;
			}
			(void)allocstring(&simals_levelptr->display_page[i].name, name, sim_aid->cluster);
		}
	}

	/* prepare the simulation window */
	if (sim_window_isactive(&np) == 0)
	{
		/* no simulation running: start it up */
		if (simals_levelptr->num_chn <= 0) return(1);

		var = getvalkey((INTBIG)sim_aid, VAID, VINTEGER, sim_window_state);
		if (var == NOVARIABLE) windowstate = 0; else windowstate = var->addr;
		if (sim_window_create(simals_levelptr->num_chn, simals_mainproto,
			((windowstate&SHOWWAVEFORM) != 0 ? simals_charhandlerwave : 0),
				simals_charhandlerschem) != 0) return(1);
	}

	/* load the traces */
	sim_window_killalltraces();
	for(i=0; i<simals_levelptr->num_chn; i++)
	{
		node = simals_levelptr->display_page[i+1].nodeptr;
		if (node == 0) continue;
		name = simals_levelptr->display_page[i+1].name;
		simals_levelptr->display_page[i+1].displayptr = sim_window_newtrace((INTSML)i, name,
			(INTBIG)node);
	}
	sim_window_sethighlighttrace(0);
	sim_window_settimerange(0.0, 0.0000005f);
	sim_window_setmaincursor(0.0000002f);
	sim_window_setextensioncursor(0.0000003f);
	return(0);
}

void simals_fill_display_arrays(void)
{
	INTSML i, j, pos, num_chan, *numsteps, **statearrays;
	INTBIG *nodelist, displayobj;
	TRAKPTR trakhead;
	float min, max, **timearrays;

	/* determine size needed for waveform arrays */
	num_chan = simals_levelptr->num_chn;
	numsteps = (INTSML *)emalloc(num_chan * SIZEOFINTSML, el_tempcluster);
	if (numsteps == 0) return;
	nodelist = (INTBIG *)emalloc(num_chan * SIZEOFINTBIG, el_tempcluster);
	if (nodelist == 0) return;
	timearrays = (float **)emalloc(num_chan * (sizeof (float *)), el_tempcluster);
	if (timearrays == 0) return;
	statearrays = (INTSML **)emalloc(num_chan * (sizeof (INTSML*)), el_tempcluster);
	if (statearrays == 0) return;
	for(i=0; i<num_chan; i++)
	{
		numsteps[i] = 1;
		displayobj = simals_levelptr->display_page[i+1].displayptr;
		if (displayobj == 0) nodelist[i] = 0; else
			nodelist[i] = sim_window_gettracedata(displayobj);
	}

	sim_window_gettimerange(&min, &max);
	if (simals_trakfull == 0) i = 0; else i = (INTSML)simals_trakptr;
	for( ; i < simals_trakptr && i < simals_trace_size; i++)
	{
		trakhead = &(simals_trakroot[i]);
		if (trakhead->time > max) break;
		for (j=0; j<num_chan; j++)
		{
			if (nodelist[j] == 0) continue;
			if (nodelist[j] != (INTBIG)trakhead->ptr) continue;
			numsteps[j]++;
		}
	}

	/* allocate space for the waveform arrays */
	for (j=0; j<num_chan; j++)
	{
		if (nodelist[j] == 0) continue;
		timearrays[j] = (float *)emalloc(numsteps[j] * (sizeof (float)), sim_aid->cluster);
		statearrays[j] = (INTSML *)emalloc(numsteps[j] * SIZEOFINTSML, sim_aid->cluster);
		if (timearrays[j] == 0 || statearrays[j] == 0) return;
	}

	/* fill the arrays */
	for (i=0; i<num_chan; i++)
	{
		if (nodelist[i] == 0) continue;
		numsteps[i] = 1;
		timearrays[i][0] = min;
		statearrays[i][0] = (LOGIC_LOW << 8) | 0;
	}
	if (simals_trakfull == 0) i = 0; else i = (INTSML)simals_trakptr;
	for( ; i < simals_trakptr && i < simals_trace_size; i++)
	{
		trakhead = &(simals_trakroot[i]);
		if (trakhead->time > max) break;
		for (j=0; j<num_chan; j++)
		{
			if (nodelist[j] == 0) continue;
			if (nodelist[j] != (INTBIG)trakhead->ptr) continue;
			pos = numsteps[j]++;
			timearrays[j][pos] = trakhead->time;
			statearrays[j][pos] = (trakhead->state << 8) | trakhead->strength;
		}
	}

	/* give the data to the simulation window system */
	for (j=0; j<num_chan; j++)
	{
		if (nodelist[j] == 0) continue;
		displayobj = simals_levelptr->display_page[j+1].displayptr;
		sim_window_loaddigtrace(displayobj, numsteps[j], timearrays[j], statearrays[j]);
		efree((char *)timearrays[j]);
		efree((char *)statearrays[j]);
	}
	efree((char *)nodelist);
	efree((char *)numsteps);
	efree((char *)timearrays);
	efree((char *)statearrays);
}

#endif  /* SIMAID - at top */
