/*
 * Copyright (c) Tony Bybell 2005-2014.
 *
 * This program 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.
 */

#include <config.h>
#include "globals.h"
#include "vcd_saver.h"
#include "helpers/lxt_write.h"
#include "ghw.h"
#include "hierpack.h"
#include <time.h>

static void w32redirect_fprintf(FILE *sfd, const char *format, ...)
{
va_list ap;
va_start(ap, format);
vfprintf(sfd, format, ap);
va_end(ap);
}

/*
 * unconvert trace data back to VCD representation...use strict mode for LXT
 */
static unsigned char analyzer_demang(int strict, unsigned char ch)
{
if(!strict)
	{
	if(ch < AN_COUNT)
		{
		return(AN_STR[ch]);
		}
		else
		{
		return(ch);
		}
	}
else
	{
	if(ch < AN_COUNT)
		{
		return(AN_STR4ST[ch]);
		}
		else
		{
		return(ch);
		}
	}
}


/*
 * generate a vcd identifier for a given facindx
 */
static char *vcdid(unsigned int value, int export_typ)
{
char *pnt = GLOBALS->buf_vcd_saver_c_3;
unsigned int vmod;

if(export_typ != WAVE_EXPORT_TRANS)
	{
	value++;
	for(;;)
	        {
	        if((vmod = (value % 94)))
	                {
	                *(pnt++) = (char)(vmod + 32);
	                }
	                else
	                {
	                *(pnt++) = '~'; value -= 94;
	                }
	        value = value / 94;
	        if(!value) { break; }
	        }

	*pnt = 0;
	}
	else
	{
	sprintf(pnt, "%d", value);
	}

return(GLOBALS->buf_vcd_saver_c_3);
}


static char *vcd_truncate_bitvec(char *s)
{
char l, r;

r=*s;
if(r=='1')
        {
        return s;
        }
        else
        {
        s++;
        }

for(;;s++)
        {
        l=r; r=*s;
        if(!r) return (s-1);

        if(l!=r)
                {
                return(((l=='0')&&(r=='1'))?s:s-1);
                }
        }
}


/************************ splay ************************/

/*
 * integer splay
 */
typedef struct vcdsav_tree_node vcdsav_Tree;
struct vcdsav_tree_node {
    vcdsav_Tree * left, * right;
    nptr item;
    int val;
    hptr hist;
    int len;

    union
	{
	void *p;
	long l;
	int i;
	} handle;	/* future expansion for adding other writers that need pnt/handle info */

    unsigned char flags;
};


static long vcdsav_cmp_l(void *i, void *j)
{
intptr_t il = (intptr_t)i, jl = (intptr_t)j;
return(il - jl);
}

static vcdsav_Tree * vcdsav_splay (void *i, vcdsav_Tree * t) {
/* Simple top down splay, not requiring i to be in the tree t.  */
/* What it does is described above.                             */
    vcdsav_Tree N, *l, *r, *y;
    int dir;

    if (t == NULL) return t;
    N.left = N.right = NULL;
    l = r = &N;

    for (;;) {
	dir = vcdsav_cmp_l(i, t->item);
	if (dir < 0) {
	    if (t->left == NULL) break;
	    if (vcdsav_cmp_l(i, t->left->item)<0) {
		y = t->left;                           /* rotate right */
		t->left = y->right;
		y->right = t;
		t = y;
		if (t->left == NULL) break;
	    }
	    r->left = t;                               /* link right */
	    r = t;
	    t = t->left;
	} else if (dir > 0) {
	    if (t->right == NULL) break;
	    if (vcdsav_cmp_l(i, t->right->item)>0) {
		y = t->right;                          /* rotate left */
		t->right = y->left;
		y->left = t;
		t = y;
		if (t->right == NULL) break;
	    }
	    l->right = t;                              /* link left */
	    l = t;
	    t = t->right;
	} else {
	    break;
	}
    }
    l->right = t->left;                                /* assemble */
    r->left = t->right;
    t->left = N.right;
    t->right = N.left;
    return t;
}


static vcdsav_Tree * vcdsav_insert(void *i, vcdsav_Tree * t, int val, unsigned char flags, hptr h) {
/* Insert i into the tree t, unless it's already there.    */
/* Return a pointer to the resulting tree.                 */
    vcdsav_Tree * n;
    int dir;

    n = (vcdsav_Tree *) calloc_2(1, sizeof (vcdsav_Tree));
    if (n == NULL) {
	fprintf(stderr, "vcdsav_insert: ran out of memory, exiting.\n");
	exit(255);
    }
    n->item = i;
    n->val = val;
    n->flags = flags;
    n->hist = h;
    if (t == NULL) {
	n->left = n->right = NULL;
	return n;
    }
    t = vcdsav_splay(i,t);
    dir = vcdsav_cmp_l(i,t->item);
    if (dir<0) {
	n->left = t->left;
	n->right = t;
	t->left = NULL;
	return n;
    } else if (dir>0) {
	n->right = t->right;
	n->left = t;
	t->right = NULL;
	return n;
    } else { /* We get here if it's already in the tree */
             /* Don't add it again                      */
	free_2(n);
	return t;
    }
}

/************************ heap ************************/

static int hpcmp(vcdsav_Tree *hp1, vcdsav_Tree *hp2)
{
hptr n1 = hp1->hist;
hptr n2 = hp2->hist;
TimeType t1, t2;

if(n1)  t1 = n1->time; else t1 = MAX_HISTENT_TIME;
if(n2)  t2 = n2->time; else t2 = MAX_HISTENT_TIME;

if(t1 == t2)
	{
	return(0);
	}
else
if(t1 > t2)
	{
	return(-1);
	}
else
	{
	return(1);
	}
}


static void recurse_build(vcdsav_Tree *vt, vcdsav_Tree ***hp)
{
if(vt->left) recurse_build(vt->left, hp);

**hp = vt;
*hp = (*hp) + 1;

if(vt->right) recurse_build(vt->right, hp);
}


/*
 * heapify algorithm...used to grab the next value change
 */
static void heapify(int i, int heap_size)
{
int l, r;
int largest;
vcdsav_Tree *t;
int maxele=heap_size/2-1;	/* points to where heapswaps don't matter anymore */

for(;;)
        {
        l=2*i+1;
        r=l+1;

        if((l<heap_size)&&(hpcmp(GLOBALS->hp_vcd_saver_c_1[l],GLOBALS->hp_vcd_saver_c_1[i])>0))
                {
                largest=l;
                }
                else
                {
                largest=i;
                }
        if((r<heap_size)&&(hpcmp(GLOBALS->hp_vcd_saver_c_1[r],GLOBALS->hp_vcd_saver_c_1[largest])>0))
                {
                largest=r;
                }

        if(i!=largest)
                {
                t=GLOBALS->hp_vcd_saver_c_1[i];
                GLOBALS->hp_vcd_saver_c_1[i]=GLOBALS->hp_vcd_saver_c_1[largest];
                GLOBALS->hp_vcd_saver_c_1[largest]=t;

                if(largest<=maxele)
                        {
                        i=largest;
                        }
                        else
                        {
                        break;
                        }
                }
                else
                {
                break;
                }
        }
}


/*
 * mainline
 */
int save_nodes_to_export_generic(FILE *trans_file, Trptr trans_head, const char *fname, int export_typ)
{
Trptr t = trans_head ? trans_head : GLOBALS->traces.first;
int nodecnt = 0;
vcdsav_Tree *vt = NULL;
vcdsav_Tree **hp_clone = GLOBALS->hp_vcd_saver_c_1;
nptr n;
/* ExtNode *e; */
/* int msi, lsi; */
int i;
TimeType prevtime = LLDescriptor(-1);
time_t walltime;
struct strace *st = NULL;
int strace_append = 0;
int max_len = 1;
char *row_data = NULL;
struct lt_trace *lt = NULL;
int lxt = (export_typ == WAVE_EXPORT_LXT);

if(export_typ == WAVE_EXPORT_TIM)
	{
	return(do_timfile_save(fname));
	}

errno = 0;
if(lxt)
	{
	lt = lt_init(fname);
	if(!lt)
		{
		return(VCDSAV_FILE_ERROR);
		}
	}
	else
	{
	if(export_typ != WAVE_EXPORT_TRANS)
		{
		GLOBALS->f_vcd_saver_c_1 = fopen(fname, "wb");
		}
		else
		{
		if(!trans_head) /* scan-build : is programming error to get here */
			{
			return(VCDSAV_FILE_ERROR);
			}
		GLOBALS->f_vcd_saver_c_1 = trans_file;
		}

	if(!GLOBALS->f_vcd_saver_c_1)
		{
		return(VCDSAV_FILE_ERROR);
		}
	}

while(t)
	{
	if(!t->vector)
		{
		if(t->n.nd)
			{
			n = t->n.nd;
			if(n->expansion) n = n->expansion->parent;
			vt = vcdsav_splay(n, vt);
			if(!vt || vt->item != n)
				{
				unsigned char flags = 0;

				if(n->head.next)
				if(n->head.next->next)
					{
					flags = n->head.next->next->flags;
					}

				vt = vcdsav_insert(n, vt, ++nodecnt, flags, &n->head);
				}
			}
		}
		else
		{
		bvptr b = t->n.vec;
		if(b)
			{
			bptr bt = b->bits;
			if(bt)
				{
				for(i=0;i<bt->nnbits;i++)
					{
					if(bt->nodes[i])
						{
						n = bt->nodes[i];

						if(n->expansion) n = n->expansion->parent;
						vt = vcdsav_splay(n, vt);
						if(!vt || vt->item != n)
							{
							unsigned char flags = 0;

							if(n->head.next)
							if(n->head.next->next)
								{
								flags = n->head.next->next->flags;
								}

							vt = vcdsav_insert(n, vt, ++nodecnt, flags, &n->head);
							}
						}
					}
				}
			}
		}


	if(export_typ == WAVE_EXPORT_TRANS)
		{
		break;
		}

	if(!strace_append)
		{
		t=t->t_next;
		if(t) continue;
		}
		else
		{
		st = st->next;
		t = st ? st->trace : NULL;
		if(t)
			{
			continue;
			}
			else
			{
			swap_strace_contexts();
			}
		}

strace_concat:
	GLOBALS->strace_ctx = &GLOBALS->strace_windows[GLOBALS->strace_current_window = strace_append];
	strace_append++;
	if(strace_append == WAVE_NUM_STRACE_WINDOWS) break;

	if(!GLOBALS->strace_ctx->shadow_straces)
		{
		goto strace_concat;
		}

	swap_strace_contexts();
	st = GLOBALS->strace_ctx->straces;
	t = st ? st->trace : NULL;
	if(!t) {swap_strace_contexts(); goto strace_concat; }
	}

if(!nodecnt) return(VCDSAV_EMPTY);


/* header */
if(lxt)
	{
	int dim;

	lt_set_chg_compress(lt);
	lt_set_clock_compress(lt);
	lt_set_initial_value(lt, 'x');
	lt_set_time64(lt, 0);
	lt_symbol_bracket_stripping(lt, 1);

	switch(GLOBALS->time_dimension)
		{
		case 'm':	dim = -3; break;
		case 'u':	dim = -6; break;
		case 'n':	dim = -9; break;
		case 'p':	dim = -12; break;
		case 'f':	dim = -15; break;
		default: 	dim = 0; break;
		}

	lt_set_timescale(lt, dim);
	}
	else
	{
	if(export_typ != WAVE_EXPORT_TRANS)
		{
		time(&walltime);
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$date\n");
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "\t%s",asctime(localtime(&walltime)));
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$end\n");
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$version\n\t"WAVE_VERSION_INFO"\n$end\n");
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$timescale\n\t%d%c%s\n$end\n", (int)GLOBALS->time_scale, GLOBALS->time_dimension, (GLOBALS->time_dimension=='s') ? "" : "s");
		if(GLOBALS->global_time_offset) { w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$timezero\n\t"TTFormat"\n$end\n",GLOBALS->global_time_offset); }
		}
		else
		{
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$comment data_start %p $end\n", (void *)trans_head); /* arbitrary hex identifier */
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$comment name %s $end\n", trans_head->name ? trans_head->name : "UNKNOWN");
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$timescale %d%c%s $end\n", (int)GLOBALS->time_scale, GLOBALS->time_dimension, (GLOBALS->time_dimension=='s') ? "" : "s");
		if(GLOBALS->global_time_offset) { w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$timezero "TTFormat" $end\n",GLOBALS->global_time_offset); }
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$comment min_time "TTFormat" $end\n", GLOBALS->min_time / GLOBALS->time_scale);
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$comment max_time "TTFormat" $end\n", GLOBALS->max_time / GLOBALS->time_scale);
		}
	}

if(export_typ == WAVE_EXPORT_TRANS)
	{
        w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$comment max_seqn %d $end\n", nodecnt);
	if(t && t->transaction_args)
		{
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$comment args \"%s\" $end\n", t->transaction_args);
		}
        }

/* write out netnames here ... */
hp_clone = GLOBALS->hp_vcd_saver_c_1 = calloc_2(nodecnt, sizeof(vcdsav_Tree *));
recurse_build(vt, &hp_clone);

for(i=0;i<nodecnt;i++)
	{
	int was_packed = HIER_DEPACK_STATIC;
	char *hname = hier_decompress_flagged(GLOBALS->hp_vcd_saver_c_1[i]->item->nname, &was_packed);
	char *netname = lxt ? hname : output_hier(hname);

	if(export_typ == WAVE_EXPORT_TRANS)
		{
	        w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$comment seqn %d %s $end\n", GLOBALS->hp_vcd_saver_c_1[i]->val, hname);
	        }

	if(GLOBALS->hp_vcd_saver_c_1[i]->flags & (HIST_REAL|HIST_STRING))
		{
		if(lxt)
			{
			GLOBALS->hp_vcd_saver_c_1[i]->handle.p = lt_symbol_add(lt, netname, 0, 0, 0, GLOBALS->hp_vcd_saver_c_1[i]->flags & HIST_STRING ? LT_SYM_F_STRING : LT_SYM_F_DOUBLE);
			}
			else
			{
			const char *typ = (GLOBALS->hp_vcd_saver_c_1[i]->flags & HIST_STRING) ? "string" : "real";
			int tlen = (GLOBALS->hp_vcd_saver_c_1[i]->flags & HIST_STRING) ? 0 : 1;
			w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$var %s %d %s %s $end\n", typ, tlen, vcdid(GLOBALS->hp_vcd_saver_c_1[i]->val, export_typ), netname);
			}
		}
		else
		{
		int msi = -1, lsi = -1;

		if(GLOBALS->hp_vcd_saver_c_1[i]->item->extvals)
			{
			msi = GLOBALS->hp_vcd_saver_c_1[i]->item->msi;
			lsi = GLOBALS->hp_vcd_saver_c_1[i]->item->lsi;
			}

		if(msi==lsi)
			{
			if(lxt)
				{
				int strand_idx = strand_pnt(netname);
				if(strand_idx >= 0)
					{
					msi = lsi = atoi(netname + strand_idx + 1);
					}
				GLOBALS->hp_vcd_saver_c_1[i]->handle.p = lt_symbol_add(lt, netname, 0, msi, lsi, LT_SYM_F_BITS);
				}
				else
				{
				w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$var wire 1 %s %s $end\n", vcdid(GLOBALS->hp_vcd_saver_c_1[i]->val, export_typ), netname);
				}
			}
			else
			{
			int len = (msi < lsi) ? (lsi - msi + 1) : (msi - lsi + 1);
			if(lxt)
				{
				GLOBALS->hp_vcd_saver_c_1[i]->handle.p = lt_symbol_add(lt, netname, 0, msi, lsi, LT_SYM_F_BITS);
				}
				else
				{
				w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$var wire %d %s %s $end\n", len, vcdid(GLOBALS->hp_vcd_saver_c_1[i]->val, export_typ), netname);
				}
			GLOBALS->hp_vcd_saver_c_1[i]->len = len;
			if(len > max_len) max_len = len;
			}
		}

	/* if(was_packed) { free_2(hname); } ...not needed for HIER_DEPACK_STATIC */
	}

row_data = malloc_2(max_len + 1);

if(!lxt)
	{
	output_hier("");
	free_hier();

	w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$enddefinitions $end\n");
	w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$dumpvars\n");
	}

/* value changes */

for(i=(nodecnt/2-1);i>0;i--)        /* build nodes into having heap property */
        {
        heapify(i,nodecnt);
        }

for(;;)
	{
	heapify(0, nodecnt);

	if(!GLOBALS->hp_vcd_saver_c_1[0]->hist) break;
	if(GLOBALS->hp_vcd_saver_c_1[0]->hist->time > GLOBALS->max_time) break;

	if((GLOBALS->hp_vcd_saver_c_1[0]->hist->time != prevtime) && (GLOBALS->hp_vcd_saver_c_1[0]->hist->time >= LLDescriptor(0)))
		{
		TimeType tnorm = GLOBALS->hp_vcd_saver_c_1[0]->hist->time;
		if(GLOBALS->time_scale != 1)
			{
			tnorm /= GLOBALS->time_scale;
			}

		if(lxt)
			{
			lt_set_time64(lt, tnorm);
			}
			else
			{
			w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "#"TTFormat"\n", tnorm);
			}
		prevtime = GLOBALS->hp_vcd_saver_c_1[0]->hist->time;
		}

	if(GLOBALS->hp_vcd_saver_c_1[0]->hist->time >= LLDescriptor(0))
		{
		if(GLOBALS->hp_vcd_saver_c_1[0]->flags & (HIST_REAL|HIST_STRING))
			{
			if(GLOBALS->hp_vcd_saver_c_1[0]->flags & HIST_STRING)
				{
				char *vec = GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_vector ? GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_vector : "UNDEF";

				if(lxt)
					{
					lt_emit_value_string(lt, GLOBALS->hp_vcd_saver_c_1[0]->handle.p, 0, vec);
					}
					else
					{
					int vec_slen = strlen(vec);
					char *vec_escaped = malloc_2(vec_slen*4 + 1); /* worst case */
					int vlen = fstUtilityBinToEsc((unsigned char *)vec_escaped, (unsigned char *)vec, vec_slen);

					vec_escaped[vlen] = 0;
					if(vlen)
						{
						w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "s%s %s\n", vec_escaped, vcdid(GLOBALS->hp_vcd_saver_c_1[0]->val, export_typ));
						}
						else
						{
						w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "s\\000 %s\n", vcdid(GLOBALS->hp_vcd_saver_c_1[0]->val, export_typ));
						}
					free_2(vec_escaped);
					}
				}
				else
				{
#ifdef WAVE_HAS_H_DOUBLE
				double *d = &GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_double;
#else
				double *d = (double *)GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_vector;
#endif
                                double value;

				if(!d)
					{
	                                sscanf("NaN", "%lg", &value);
					}
					else
					{
					value = *d;
					}

				if(lxt)
					{
					lt_emit_value_double(lt, GLOBALS->hp_vcd_saver_c_1[0]->handle.p, 0, value);
					}
					else
					{
					w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "r%.16g %s\n", value, vcdid(GLOBALS->hp_vcd_saver_c_1[0]->val, export_typ));
					}
				}
			}
		else
		if(GLOBALS->hp_vcd_saver_c_1[0]->len)
			{
			if(GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_vector)
				{
				for(i=0;i<GLOBALS->hp_vcd_saver_c_1[0]->len;i++)
					{
					row_data[i] = analyzer_demang(lxt, GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_vector[i]);
					}
				}
				else
				{
				for(i=0;i<GLOBALS->hp_vcd_saver_c_1[0]->len;i++)
					{
					row_data[i] = 'x';
					}
				}
			row_data[i] = 0;

			if(lxt)
				{
				lt_emit_value_bit_string(lt, GLOBALS->hp_vcd_saver_c_1[0]->handle.p, 0, row_data);
				}
				else
				{
				w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "b%s %s\n", vcd_truncate_bitvec(row_data), vcdid(GLOBALS->hp_vcd_saver_c_1[0]->val, export_typ));
				}
			}
		else
			{
			if(lxt)
				{
				row_data[0] = analyzer_demang(lxt, GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_val);
				row_data[1] = 0;

				lt_emit_value_bit_string(lt, GLOBALS->hp_vcd_saver_c_1[0]->handle.p, 0, row_data);
				}
				else
				{
				w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "%c%s\n", analyzer_demang(lxt, GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_val), vcdid(GLOBALS->hp_vcd_saver_c_1[0]->val, export_typ));
				}
			}
		}

	GLOBALS->hp_vcd_saver_c_1[0]->hist = GLOBALS->hp_vcd_saver_c_1[0]->hist->next;
	}

if(prevtime < GLOBALS->max_time)
	{
	if(lxt)
		{
		lt_set_time64(lt, GLOBALS->max_time / GLOBALS->time_scale);
		}
		else
		{
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "#"TTFormat"\n", GLOBALS->max_time / GLOBALS->time_scale);
		}
	}


for(i=0;i<nodecnt;i++)
	{
	free_2(GLOBALS->hp_vcd_saver_c_1[i]);
	}

free_2(GLOBALS->hp_vcd_saver_c_1); GLOBALS->hp_vcd_saver_c_1 = NULL;
free_2(row_data); row_data = NULL;

if(lxt)
	{
	lt_close(lt); lt = NULL;
	}
	else
	{
	if(export_typ != WAVE_EXPORT_TRANS)
		{
		fclose(GLOBALS->f_vcd_saver_c_1);
		}
		else
		{
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$comment data_end %p $end\n", (void *)trans_head); /* arbitrary hex identifier */
#if !defined _MSC_VER && !defined __MINGW32__
		fflush(GLOBALS->f_vcd_saver_c_1);
#endif
		}

	GLOBALS->f_vcd_saver_c_1 = NULL;
	}

return(VCDSAV_OK);
}



int save_nodes_to_export(const char *fname, int export_typ)
{
return(save_nodes_to_export_generic(NULL, NULL, fname, export_typ));
}

int save_nodes_to_trans(FILE *trans, Trptr t)
{
return(save_nodes_to_export_generic(trans, t, NULL, WAVE_EXPORT_TRANS));
}


/************************ scopenav ************************/

struct namehier
{
struct namehier *next;
char *name;
char not_final;
};



void free_hier(void)
{
struct namehier *nhtemp;

while(GLOBALS->nhold_vcd_saver_c_1)
	{
	nhtemp=GLOBALS->nhold_vcd_saver_c_1->next;
	free_2(GLOBALS->nhold_vcd_saver_c_1->name);
	free_2(GLOBALS->nhold_vcd_saver_c_1);
	GLOBALS->nhold_vcd_saver_c_1=nhtemp;
	}
}

/*
 * navigate up and down the scope hierarchy and
 * emit the appropriate vcd scope primitives
 */
static void diff_hier(struct namehier *nh1, struct namehier *nh2)
{
/* struct namehier *nhtemp; */ /* scan-build */

if(!nh2)
	{
	while((nh1)&&(nh1->not_final))
		{
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$scope module %s $end\n", nh1->name);
		nh1=nh1->next;
		}
	return;
	}

for(;;)
	{
	if((nh1->not_final==0)&&(nh2->not_final==0)) /* both are equal */
		{
		break;
		}

	if(nh2->not_final==0)	/* old hier is shorter */
		{
		/* nhtemp=nh1; */ /* scan-build */
		while((nh1)&&(nh1->not_final))
			{
			w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$scope module %s $end\n", nh1->name);
			nh1=nh1->next;
			}
		break;
		}

	if(nh1->not_final==0)	/* new hier is shorter */
		{
		/* nhtemp=nh2; */ /* scan-build */
		while((nh2)&&(nh2->not_final))
			{
			w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$upscope $end\n");
			nh2=nh2->next;
			}
		break;
		}

	if(strcmp(nh1->name, nh2->name))
		{
		/* nhtemp=nh2; */ /* prune old hier */ /* scan-build */
		while((nh2)&&(nh2->not_final))
			{
			w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$upscope $end\n");
			nh2=nh2->next;
			}

		/* nhtemp=nh1; */ /* add new hier */ /* scan-build */
		while((nh1)&&(nh1->not_final))
			{
			w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "$scope module %s $end\n", nh1->name);
			nh1=nh1->next;
			}
		break;
		}

	nh1=nh1->next;
	nh2=nh2->next;
	}
}


/*
 * output scopedata for a given name if needed, return pointer to name string
 */
char *output_hier(char *name)
{
char *pnt, *pnt2;
char *s;
int len;
struct namehier *nh_head=NULL, *nh_curr=NULL, *nhtemp;

pnt=pnt2=name;

for(;;)
{
if(*pnt2 == '\\')
	{
	while(*pnt2) pnt2++;
	}
	else
	{
	/* while((*pnt2!='.')&&(*pnt2)) pnt2++; ... does not handle dot at end of name */

	while(*pnt2)
		{
		if(*pnt2!='.')
			{
			pnt2++;
			continue;
			}
			else
			{
			if(!*(pnt2+1))	/* if dot is at end of name */
				{
				pnt2++;
				continue;
				}
				else
				{
				break;
				}
			}
		}
	}

s=(char *)calloc_2(1,(len=pnt2-pnt)+1);
memcpy(s, pnt, len);
nhtemp=(struct namehier *)calloc_2(1,sizeof(struct namehier));
nhtemp->name=s;

if(!nh_curr)
	{
	nh_head=nh_curr=nhtemp;
	}
	else
	{
	nh_curr->next=nhtemp;
	nh_curr->not_final=1;
	nh_curr=nhtemp;
	}

if(!*pnt2) break;
pnt=(++pnt2);
}

diff_hier(nh_head, GLOBALS->nhold_vcd_saver_c_1);
free_hier();
GLOBALS->nhold_vcd_saver_c_1=nh_head;

	{
	char *mti_sv_patch = strstr(nh_curr->name, "]["); 	/* case is: #implicit-var###VarElem:ram_di[0.0] [63:0] */
	if(mti_sv_patch)
		{
		char *t = calloc_2(1, strlen(nh_curr->name) + 1 + 1);

		*mti_sv_patch = 0;
		sprintf(t, "%s] %s", nh_curr->name, mti_sv_patch+1);

		free_2(nh_curr->name);
		nh_curr->name = t;
		}

	if((nh_curr->name[0] == '\\') && (nh_curr->name[1] == '#'))
		{
		return(nh_curr->name+1);
		}
	}

return(nh_curr->name);
}


/****************************************/
/***                                  ***/
/*** output in timing analyzer format ***/
/***                                  ***/
/****************************************/

static void write_hptr_trace(Trptr t, int *whichptr, TimeType tmin, TimeType tmax)
{
nptr n = t->n.nd;
hptr *ha = n->harray;
int numhist = n->numhist;
int i;
unsigned char h_val = AN_X;
gboolean first;
gboolean invert = ((t->flags&TR_INVERT) != 0);
int edges = 0;

first = TRUE;
for(i=0;i<numhist;i++)
	{
	if(ha[i]->time < tmin)
		{
		}
	else
	if(ha[i]->time > tmax)
		{
		break;
		}
	else
		{
		if((ha[i]->time != tmin) || (!first))
			{
			edges++;
			}

		first = FALSE;
		}
	}


first = TRUE;
for(i=0;i<numhist;i++)
	{
	if(ha[i]->time < tmin)
		{
		h_val = invert ? AN_USTR_INV[ha[i]->v.h_val] : AN_USTR[ha[i]->v.h_val];
		}
	else
	if(ha[i]->time > tmax)
		{
		break;
		}
	else
		{
		if(first)
			{
			gboolean skip_this = (ha[i]->time == tmin);

			if(skip_this)
				{
				h_val = invert ? AN_USTR_INV[ha[i]->v.h_val] : AN_USTR[ha[i]->v.h_val];
				}

			w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1,
				"Digital_Signal\n"
				"     Position:          %d\n"
				"     Height:            24\n"
				"     Space_Above:       24\n"
				"     Name:              %s\n"
				"     Start_State:       %c\n"
				"     Number_Edges:      %d\n"
				"     Rise_Time:         0.2\n"
				"     Fall_Time:         0.2\n",
					*whichptr,
					t->name,
					h_val,
					edges);
			first = FALSE;

			if(skip_this)
				{
				continue;
				}
			}

		h_val = invert ? AN_USTR_INV[ha[i]->v.h_val] : AN_USTR[ha[i]->v.h_val];
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "          Edge:               "TTFormat".0 %c\n", ha[i]->time, h_val);
		}
	}

if(first)
	{
	/* need to emit blank trace */
	w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1,
		"Digital_Signal\n"
		"     Position:          %d\n"
		"     Height:            24\n"
		"     Space_Above:       24\n"
		"     Name:              %s\n"
		"     Start_State:       %c\n"
		"     Number_Edges:      %d\n"
		"     Rise_Time:         10.0\n"
		"     Fall_Time:         10.0\n",
			*whichptr,
			t->name,
			h_val,
			edges);
	}

(*whichptr)++;
}

/***/

static void format_value_string(char *s)
{
char *s_orig = s;

if((s)&&(*s))
	{
	gboolean is_all_z = TRUE;
	gboolean is_all_x = TRUE;

	while(*s)
		{
		if((*s != 'z') && (*s != 'Z'))
			{
			is_all_z = FALSE;
			}
		if((*s != 'x') && (*s != 'X'))
			{
			is_all_x = FALSE;
			}

		/* if(isspace(*s)) *s='_'; ...not needed */
		s++;
		}

	if(is_all_z)
		{
		*(s_orig++) = 'Z';
		*(s_orig) = 0;
		}
	else
	if(is_all_x)
		{
		*(s_orig++) = 'X';
		*(s_orig) = 0;
		}
	}
}

static char *get_hptr_vector_val(Trptr t, hptr h)
{
char *ascii = NULL;

if(h->time < LLDescriptor(0))
	{
	ascii=strdup_2("X");
	}
else
if(h->flags&HIST_REAL)
	{
        if(!(h->flags&HIST_STRING))
        	{
#ifdef WAVE_HAS_H_DOUBLE
                ascii=convert_ascii_real(t, &h->v.h_double);
#else
                ascii=convert_ascii_real(t, (double *)h->v.h_vector);
#endif
                }
                else
                {
                ascii=convert_ascii_string((char *)h->v.h_vector);
                }
	}
        else
        {
        ascii=convert_ascii_vec(t,h->v.h_vector);
        }

format_value_string(ascii);
return(ascii);
}

static const char *vcdsav_dtypes[] = { "Bin", "Hex", "Text" };

static int determine_trace_data_type(char *s, int curtype)
{
int i;

if((s) && (curtype != VCDSAV_IS_TEXT))
	{
	int len = strlen(s);
	for(i=0;i<len;i++)
		{
		int ch = (int)((unsigned char)s[i]);

		if(strchr("01xXzZhHuUwWlL-?_", ch))
			{
			/* nothing */
			}
		else if(strchr("23456789aAbBcCdDeEfF", ch))
			{
			curtype = VCDSAV_IS_HEX;
			}
		else
			{
			curtype = VCDSAV_IS_TEXT;
			break;
			}
		}
	}

return(curtype);
}


static void write_hptr_trace_vector(Trptr t, int *whichptr, TimeType tmin, TimeType tmax)
{
nptr n = t->n.nd;
hptr *ha = n->harray;
int numhist = n->numhist;
int i;
char *h_val = NULL;
gboolean first;
int edges = 0;
int curtype = VCDSAV_IS_BIN;

first = TRUE;
for(i=0;i<numhist;i++)
	{
	if(ha[i]->time < tmin)
		{
		}
	else
	if(ha[i]->time > tmax)
		{
		break;
		}
	else
		{
		char *s = get_hptr_vector_val(t, ha[i]);
		if(s)
			{
			curtype = determine_trace_data_type(s, curtype);
			free_2(s);
			}

		if((ha[i]->time != tmin) || (!first))
			{
			edges++;
			}

		first = FALSE;
		}
	}


first = TRUE;
for(i=0;i<numhist;i++)
	{
	if(ha[i]->time < tmin)
		{
		if(h_val) free_2(h_val);
		h_val = get_hptr_vector_val(t, ha[i]);
		}
	else
	if(ha[i]->time > tmax)
		{
		break;
		}
	else
		{
		if(first)
			{
			gboolean skip_this = (ha[i]->time == tmin);

			if(skip_this)
				{
				if(h_val) free_2(h_val);
				h_val = get_hptr_vector_val(t, ha[i]);
				}

			w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1,
				"Digital_Bus\n"
				"     Position:          %d\n"
				"     Height:            24\n"
				"     Space_Above:       24\n"
				"     Name:              %s\n"
				"     Start_State:       %s\n"
				"     State_Format:      %s\n"
				"     Number_Edges:      %d\n"
				"     Rise_Time:         0.2\n"
				"     Fall_Time:         0.2\n",
					*whichptr,
					t->name,
					h_val,
					vcdsav_dtypes[curtype],
					edges);
			first = FALSE;

			if(skip_this)
				{
				continue;
				}
			}

		if(h_val) free_2(h_val);
		h_val = get_hptr_vector_val(t, ha[i]);
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "          Edge:               "TTFormat".0 %s\n", ha[i]->time, h_val);
		}
	}

if(first)
	{
	/* need to emit blank trace */
	w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1,
		"Digital_Bus\n"
		"     Position:          %d\n"
		"     Height:            24\n"
		"     Space_Above:       24\n"
		"     Name:              %s\n"
		"     Start_State:       %s\n"
		"     State_Format:      %s\n"
		"     Number_Edges:      %d\n"
		"     Rise_Time:         10.0\n"
		"     Fall_Time:         10.0\n",
			*whichptr,
			t->name,
			h_val,
			vcdsav_dtypes[curtype],
			edges);
	}

if(h_val) free_2(h_val);
(*whichptr)++;
}

/***/

static char *get_vptr_vector_val(Trptr t, vptr v)
{
char *ascii = NULL;

if(v->time < LLDescriptor(0))
	{
	ascii=strdup_2("X");
	}
else
        {
        ascii=convert_ascii(t,v);
        }

if(!ascii)
	{
	ascii=strdup_2("X");
	}

format_value_string(ascii);
return(ascii);
}


static void write_vptr_trace(Trptr t, int *whichptr, TimeType tmin, TimeType tmax)
{
vptr *ha = t->n.vec->vectors;
int numhist = t->n.vec->numregions;
int i;
char *h_val = NULL;
gboolean first;
int edges = 0;
int curtype = VCDSAV_IS_BIN;

first = TRUE;
for(i=0;i<numhist;i++)
	{
	if(ha[i]->time < tmin)
		{
		}
	else
	if(ha[i]->time > tmax)
		{
		break;
		}
	else
		{
		char *s = get_vptr_vector_val(t, ha[i]);
		if(s)
			{
			curtype = determine_trace_data_type(s, curtype);
			free_2(s);
			}

		if((ha[i]->time != tmin) || (!first))
			{
			edges++;
			}

		first = FALSE;
		}
	}


first = TRUE;
for(i=0;i<numhist;i++)
	{
	if(ha[i]->time < tmin)
		{
		if(h_val) free_2(h_val);
		h_val = get_vptr_vector_val(t, ha[i]);
		}
	else
	if(ha[i]->time > tmax)
		{
		break;
		}
	else
		{
		if(first)
			{
			gboolean skip_this = (ha[i]->time == tmin);

			if(skip_this)
				{
				if(h_val) free_2(h_val);
				h_val = get_vptr_vector_val(t, ha[i]);
				}

			w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1,
				"Digital_Bus\n"
				"     Position:          %d\n"
				"     Height:            24\n"
				"     Space_Above:       24\n"
				"     Name:              %s\n"
				"     Start_State:       %s\n"
				"     State_Format:      %s\n"
				"     Number_Edges:      %d\n"
				"     Rise_Time:         0.2\n"
				"     Fall_Time:         0.2\n",
					*whichptr,
					t->name,
					h_val,
					vcdsav_dtypes[curtype],
					edges);
			first = FALSE;

			if(skip_this)
				{
				continue;
				}
			}

		if(h_val) free_2(h_val);
		h_val = get_vptr_vector_val(t, ha[i]);
		w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1, "          Edge:               "TTFormat".0 %s\n", ha[i]->time, h_val);
		}
	}

if(first)
	{
	/* need to emit blank trace */
	w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1,
		"Digital_Bus\n"
		"     Position:          %d\n"
		"     Height:            24\n"
		"     Space_Above:       24\n"
		"     Name:              %s\n"
		"     Start_State:       %s\n"
		"     State_Format:      %s\n"
		"     Number_Edges:      %d\n"
		"     Rise_Time:         10.0\n"
		"     Fall_Time:         10.0\n",
			*whichptr,
			t->name,
			h_val,
			vcdsav_dtypes[curtype],
			edges);
	}

if(h_val) free_2(h_val);
(*whichptr)++;
}


static void write_tim_tracedata(Trptr t, int *whichptr, TimeType tmin, TimeType tmax)
{
if(!(t->flags&(TR_EXCLUDE|TR_BLANK|TR_ANALOG_BLANK_STRETCH)))
	{
        GLOBALS->shift_timebase=t->shift;
        if(!t->vector)
        	{
                if(!t->n.nd->extvals)
                	{
			write_hptr_trace(t, whichptr, tmin, tmax); /* single-bit */
                        }
                        else
                        {
			write_hptr_trace_vector(t, whichptr, tmin, tmax); /* multi-bit */
                        }
		}
                else
                {
		write_vptr_trace(t, whichptr, tmin, tmax); /* synthesized/concatenated vector */
                }
	}
}


int do_timfile_save(const char *fname)
{
const char *time_prefix=WAVE_SI_UNITS;
const double negpow[] = { 1.0, 1.0e-3, 1.0e-6, 1.0e-9, 1.0e-12, 1.0e-15, 1.0e-18, 1.0e-21 };
char *pnt;
int offset;
Trptr t = GLOBALS->traces.first;
int i = 1; /* trace index in the .tim file */
TimeType tmin, tmax;

errno = 0;

if((GLOBALS->tims.marker > LLDescriptor(0)) && (GLOBALS->tims.baseline > LLDescriptor(0)))
	{
	if(GLOBALS->tims.marker < GLOBALS->tims.baseline)
		{
	        tmin = GLOBALS->tims.marker;
	        tmax = GLOBALS->tims.baseline;
		}
		else
		{
	        tmax = GLOBALS->tims.marker;
	        tmin = GLOBALS->tims.baseline;
		}
	}
	else
	{
        tmin = GLOBALS->min_time;
        tmax = GLOBALS->max_time;
	}

GLOBALS->f_vcd_saver_c_1 = fopen(fname, "wb");
if(!GLOBALS->f_vcd_saver_c_1)
	{
        return(VCDSAV_FILE_ERROR);
        }

pnt=strchr(time_prefix, (int)GLOBALS->time_dimension);
if(pnt) { offset=pnt-time_prefix; } else offset=0;

w32redirect_fprintf(GLOBALS->f_vcd_saver_c_1,
	"Timing Analyzer Settings\n"
	"     Time_Scale:        %E\n"
	"     Time_Per_Division: %E\n"
	"     NumberDivisions:   10\n"
	"     Start_Time:        "TTFormat".0\n"
	"     End_Time:          "TTFormat".0\n",

	negpow[offset],
	(tmax-tmin) / 10.0,
	tmin,
	tmax
	);

while(t)
	{
	write_tim_tracedata(t, &i, tmin, tmax);
        t = GiveNextTrace(t);
	}

fclose(GLOBALS->f_vcd_saver_c_1);
GLOBALS->f_vcd_saver_c_1 = NULL;

return(errno ? VCDSAV_FILE_ERROR : VCDSAV_OK);
}

