/* $Id$
 *
 * Utilities for writing to a VCD file.
 *
 * Copyright (C) 2008-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include "trace.h"
#include "mangle_names.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include "glue-log.h"
#include <string.h>
#include <inttypes.h>

#define INTERPRETER_VERSION "fauhdli 0.0alpha1"
#define RESOLUTION_UNIT "fs"
#define RESOLUTION_SCALE (1000000000000000ull /  (1ull << 32))

enum trace_type_kind {
	TRACE_TYPE_INT 		= TYPE_INT,
	TRACE_TYPE_FLOAT 	= TYPE_FLOAT,
	TRACE_TYPE_STDLOGIC
};

struct trace_sig_wrapper {
	/** pointer to the signal in question */
	const union fauhdli_value *sigptr;
	char id_code;
	enum trace_type_kind type;
	int array_size;
};

static const char *
vcd_get_hex(universal_integer num)
{
	static char ret[65] = { '\0' };
	int bit;
	int dst = 0;
	bool seen = false;

	for (bit = 63; bit >= 0; bit--) {
		if ((num & (1ULL << bit)) == 0) {
			ret[dst] = '0';
		} else {
			ret[dst] = '1';
			seen = true;
		}

		if (seen) {
			dst++;
		}
	}

	ret[dst] = '\0';
	if (dst == 0) {
		ret[0] = '0';
		ret[1] = '\0';
	}

	return ret;
}

static const char *
vcd_get_var_type(enum trace_type_kind k) 
{
	switch (k) {
	case TRACE_TYPE_INT:
		return "integer";

	case TRACE_TYPE_STDLOGIC:
		return "tri";
	
	case TRACE_TYPE_FLOAT:
		return "real";

	default:
		assert(false);
	}
}

static void
vcd_add_sig(
	FILE *f, 
	const char *name, 
	char id_code, 
	enum trace_type_kind type, 
	int display_bits
)
{
	size_t sz;
	char buf[4096];
	char *c;
	int ret;

	ret = demangle_name(name, buf, sizeof(buf));
	assert(ret >= 0);
	assert((size_t)ret < sizeof(buf));

	/* replace ":" with ".", seems like gtkwave matches hierarchy */
	for (c = strchr(buf, ':'); c != NULL; c = strchr(buf, ':')) {
		*c = '.';
	}

	switch (type) {
	case TYPE_INT:
		sz = sizeof(universal_integer) * 8;
		break;
	
	case TRACE_TYPE_FLOAT:
		sz = sizeof(universal_real) * 8;
		break;

	case TRACE_TYPE_STDLOGIC:
		break;

	default:
		assert(0);
	}

	if (display_bits != -1) {
		sz = (size_t)display_bits;
	}

	fprintf(f, "$var %s %zd %c %s $end\n",
		vcd_get_var_type(type),
		sz,
		id_code,
		buf
	);
}

static void
vcd_write_header(FILE *f)
{
	fprintf(f, "$version %s\n", INTERPRETER_VERSION);
	fprintf(f, "$end\n");
	fprintf(f, "$timescale %lld %s\n", RESOLUTION_SCALE, RESOLUTION_UNIT);
	fprintf(f, "$end\n");
}

static void
vcd_end_header(FILE *f)
{
	fprintf(f, "$end\n");
}

static void
vcd_dump_stdlogic(FILE *f, const struct trace_sig_wrapper *tw)
{
	static const char std_logic_vals[] = "ux01zwlh-";
	int i;

	if (1 < tw->array_size) {
		fprintf(f, "b");
	}

	for (i = 0; i < tw->array_size; i++) {
		const struct signal * const sig = 
			(const struct signal *)tw->sigptr[i].pointer;

		assert(sig->value.univ_int < strlen(std_logic_vals));
		fprintf(f, "%c", std_logic_vals[sig->value.univ_int]);
	}

	if (1 < tw->array_size) {
		fprintf(f, " ");
	}

	fprintf(f, "%c\n", tw->id_code);
}

static void
vcd_dump_var(FILE *f, const struct trace_sig_wrapper *tw)
{
	switch (tw->type) {
	case TRACE_TYPE_INT: {
		const struct signal * const sig = 
			(const struct signal *)tw->sigptr[0].pointer;

		fprintf(f, "b%s %c\n",
			vcd_get_hex(sig->value.univ_int),
			tw->id_code);
		break;
	    }
	case TRACE_TYPE_FLOAT:
		/* TODO */
		assert(0);
		break;

	case TRACE_TYPE_STDLOGIC:
		vcd_dump_stdlogic(f, tw);
		break;

	default:
		assert(0);
	}

}

static enum trace_type_kind
trace_get_type(
	enum type_kind type,
	const char *override
)
{
	if (override && strcmp(override, "std_ulogic") == 0) {
		return TRACE_TYPE_STDLOGIC;
	}

	switch (type) {
	case TYPE_INT:
		return TRACE_TYPE_INT;
		break;

	case TYPE_FLOAT:
		return TRACE_TYPE_FLOAT;
		break;
	
	default:
		/* not supported */
		assert(0);
	}
	
	/* unreached */
	return TYPE_INT;
}

static void
trace_end_header(struct trace_t *s)
{
	const struct slist_entry *i;

	vcd_end_header(s->output);
	s->header_written = true;

	/* write initial values */
	fprintf(s->output, "$dumpvars\n");

	for (i = s->traced_sigs->first; i != NULL; i = i->next) {
		const struct trace_sig_wrapper *tw = 
			(const struct trace_sig_wrapper *)i->data;

		vcd_dump_var(s->output, tw);
	}
	fprintf(s->output, "$end\n");
}

static bool
trace_can_trace(const struct trace_sig_wrapper *tw)
{
	switch (tw->type) {
	case TRACE_TYPE_STDLOGIC:	
		/* arrays supported */
		return true;

	case TRACE_TYPE_FLOAT:
		/* not supported at all yet */
		return false;
	
	default: 
		break;
	}

	/* only non-arrays supported */
	return tw->array_size == 1;
}

struct trace_t *
trace_create(const char *trace_file, const struct glue_vhdl_cb *callbacks)
{
	struct trace_t *ret = callbacks->malloc(sizeof(struct trace_t));
	assert(ret != NULL);

	ret->traced_sigs = slist_create(callbacks->malloc);
	ret->output = fopen(trace_file, "w");
	if (ret->output == NULL) {
		callbacks->log(FAUHDLI_LOG_ERROR, "fauhdli", "tracer",
			"cannot created output file %s: %s\n", trace_file, 
			strerror(errno));
		assert(0);
	}
	ret->ident_code = 33; /* ident code is in range 33-126 */
	ret->header_written = false;

	vcd_write_header(ret->output);
	return ret;
}

void
trace_destroy(struct trace_t *s, const struct glue_vhdl_cb *callbacks)
{
	int ret;
	struct slist_entry *i;

	assert(s != NULL);
	for (i = s->traced_sigs->first; i != NULL; i = i->next) {
		callbacks->free(i->data);
	}

	slist_destroy(s->traced_sigs, callbacks->free);

	ret = fclose(s->output);
	assert(ret == 0);

	callbacks->free(s);
}

void
trace_add_signal(
	struct trace_t *s,
	const union fauhdli_value *sigptr,
	const char *name,
	enum type_kind type,
	int display_bits,
	const char *display_type,
	const struct glue_vhdl_cb *callbacks,
	int array_size
)
{
	struct trace_sig_wrapper *tw = 
		callbacks->malloc(sizeof(struct trace_sig_wrapper));
	assert(tw != NULL);
	assert(! s->header_written);

	tw->id_code = s->ident_code;
	if (tw->id_code > 126) {
		callbacks->log(FAUHDLI_LOG_WARNING, "fauhdli", "tracer", 
			"Not tracing signal %s, out of ident codes.\n", name);
		callbacks->free(tw);
		return;
	}

	

	tw->type = trace_get_type(type, display_type);
	tw->sigptr = sigptr;
	tw->array_size = array_size;

	if (! trace_can_trace(tw)) {
		callbacks->log(FAUHDLI_LOG_WARNING, "fauhdli", "tracer",
			"Not tracing singal %s: not yet supported.\n", name);
		callbacks->free(tw);
		return;
	}

	s->ident_code++;

	if (tw->type == TRACE_TYPE_STDLOGIC) {
		display_bits = array_size;
	}

	vcd_add_sig(s->output, name, tw->id_code, tw->type, display_bits);
	slist_add(s->traced_sigs, tw, callbacks->malloc);
}

void
trace_time_advance(struct trace_t *s, universal_integer sim_time)
{
	struct slist_entry *i;

	if (! s->header_written) {
		trace_end_header(s);
	}

	for (i = s->traced_sigs->first; i != NULL; i = i->next) {
		const struct trace_sig_wrapper * const tw = 
			(const struct trace_sig_wrapper * const)i->data;

		vcd_dump_var(s->output, tw);
	}

	fprintf(s->output, "#%" PRIi64 "\n", sim_time);
}
