/* $Id$
 * Interpreter library.
 *
 * 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 "fauhdli_private.h"
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include "lookup_symbols.h"
#include "kernel.h"
#include "glue-log.h"
#include "glue-main.h"
#include "signals.h"
#include "trace.h"

#define ARRAY_SIZE(t)	(sizeof t / sizeof(t[0]))

/* definition of scanner */
extern FILE *yyin;
/* definition of parser */
extern int yyparse(struct fauhdli *instance, const char *filename);


static void
fauhdli_destroy_operand(
	struct operand *op, 
	const struct glue_vhdl_cb *callbacks
)
{
	if (op == NULL) {
		return;
	}

	switch (op->kind) {
	case OPERAND_TARGET:
		callbacks->free(op->bytype.target.name);
		break;

	case OPERAND_REFERENCE:
		callbacks->free(op->bytype.data.name);
		break;

	case OPERAND_IMMEDIATE:
	case OPERAND_INDIRECT:
	case OPERAND_REGISTER:
		/* nothing to do */
		break;
	}

	callbacks->free(op);
}

static void
fauhdli_destroy_annotations(
	struct slist *annotations, 
	const struct glue_vhdl_cb *callbacks
)
{
	struct slist_entry *i;

	if (annotations == NULL) {
		return;
	}

	for (i = annotations->first; i != NULL; i = i->next) {
		struct annotation_spec *a = (struct annotation_spec*)i->data;

		if (a->string_value != NULL) {
			callbacks->free(a->string_value);
		}
		callbacks->free(a->name);
		callbacks->free(a);
	}

	slist_destroy(annotations, callbacks->free);
}

static void
fauhdli_destroy_type_element(
	struct type_element *elem,
	const struct glue_vhdl_cb *callbacks
)
{
	if (elem == NULL) {
		return;
	}
	
	callbacks->free(elem->name);

	if (elem->initial_list != NULL) {
		struct slist_entry *i;

		for (i = elem->initial_list->first; i != NULL; i = i->next) {
			fauhdli_destroy_operand(
				(struct operand *)i->data,
				callbacks);
		}
		slist_destroy(elem->initial_list, callbacks->free);
	}
	fauhdli_destroy_annotations(elem->annotations, callbacks);
	callbacks->free(elem);
}


/** destroy one data definition.
 *  @param d data definition to destroy.
 */
static void
fauhdli_destroy_data_definition(
	struct data_definition *d,
	const struct glue_vhdl_cb *callbacks
)
{
	if (d == NULL) {
		return;
	}

	callbacks->free(d->name);
	fauhdli_destroy_type_element(d->type, callbacks);
	fauhdli_destroy_annotations(d->annotations, callbacks);

	callbacks->free(d);
}

/** destroy one type declaration.
 *  @param t type declaration to destroy.
 */
static void
fauhdli_destroy_type_declaration(
	struct type_declaration *t,
	const struct glue_vhdl_cb *callbacks
)
{
	struct slist_entry *i;

	if (t == NULL) {
		return;
	}

	assert(t->elements != NULL);

	for (i = t->elements->first; i != NULL; i = i->next) {
		fauhdli_destroy_type_element(
			(struct type_element*)i->data,
			callbacks);
	}
	slist_destroy(t->elements, callbacks->free);

	callbacks->free(t);
}


/** destroy a type list.
 *  @param seg type list to destroy.
 *  FIXME: cleanup wrt. fauhdli_destroy_data_seg.
 */
static void
fauhdli_destroy_type_seg(
	struct slist *seg,
	const struct glue_vhdl_cb *callbacks
)
{
	struct slist_entry *i;

	if (seg == NULL) {
		return;
	}

	for (i = seg->first; i != NULL; i = i->next) {
		fauhdli_destroy_type_declaration(
			(struct type_declaration *)i->data,
			callbacks);
	}

	slist_destroy(seg, callbacks->free);
}

/** destroy a transfer or stack segment.
 *  @param seg transfer or stack segment to destroy.
 */
static void
fauhdli_destroy_data_seg(
	struct slist *seg, 
	const struct glue_vhdl_cb *callbacks
)
{
	struct slist_entry *i;

	if (seg == NULL) {
		return;
	}
	
	for (i = seg->first; i != NULL; i = i->next) {
		fauhdli_destroy_data_definition(
			(struct data_definition*)i->data,
			callbacks);
	}

	slist_destroy(seg, callbacks->free);
}

static void
fauhdli_destroy_opcode(
	struct opcode *opcode, 
	const struct glue_vhdl_cb *callbacks
)
{
	if (opcode == NULL) {
		return;
	}

	fauhdli_destroy_operand(opcode->op1, callbacks);
	fauhdli_destroy_operand(opcode->op2, callbacks);
	fauhdli_destroy_operand(opcode->op3, callbacks);
	fauhdli_destroy_type_element(opcode->indexed_type, callbacks);
	fauhdli_destroy_annotations(opcode->annotations, callbacks);

	switch (opcode->kind) {
	case OPCODE_LABEL:
		callbacks->free(opcode->label);
		break;

	default:
		break;
	}

	callbacks->free(opcode);
}

/** destroy a text segment.
 *  @param seg text segment to destroy.
 */
static void
fauhdli_destroy_text_seg(
	struct slist *seg, 
	const struct glue_vhdl_cb *callbacks
)
{
	struct slist_entry *i;

	if (seg == NULL) {
		return;
	}

	for (i = seg->first; i != NULL; i = i->next) {
		fauhdli_destroy_opcode((struct opcode *)i->data, callbacks);
	}
	slist_destroy(seg, callbacks->free);
}

/** destroy the code_container hierarchy again recursively.
 *  @param instance fauhdli instance.
 */
static void
fauhdli_destroy_container(
	struct code_container *container, 
	const struct glue_vhdl_cb *callbacks
)
{
	struct slist_entry *i;
	assert(container != NULL);
	callbacks->free(container->name);

	fauhdli_destroy_type_seg(container->type_definitions, callbacks);
	fauhdli_destroy_data_seg(container->transfer_segment, callbacks);
	fauhdli_destroy_data_seg(container->stack_segment, callbacks);
	fauhdli_destroy_text_seg(container->text_segment, callbacks);

	if (container->sub_containers != NULL) {
		for (i = container->sub_containers->first; i != NULL; 
			i = i->next) {

			fauhdli_destroy_container(
				(struct code_container *)i->data,
				callbacks);
		}
		slist_destroy(container->sub_containers, callbacks->free);
	}

	callbacks->free(container);
}

static void
fauhdli_destroy_signals(
	struct slset *sigs, 
	const struct glue_vhdl_cb *callbacks
)
{
	struct slset_entry *i;
	for (i = sigs->first; i != NULL; i = i->next) {
		signal_destroy((struct signal *)i->data, callbacks);
	}
	slset_destroy(sigs, callbacks->free);
}

static bool
fauhdli_parse(struct fauhdli *instance, const char *file_name)
{
	int ret;
	assert(file_name != NULL);

	yyin = fopen(file_name, "r");
	if (yyin == NULL) {
		instance->callbacks.log(
			FAUHDLI_LOG_ERROR, "fauhdli", __func__,
			"Failed to open file \"%s\": %s\n",
			file_name, strerror(errno));
		return false;
	}

	ret = yyparse(instance, file_name);
	if (ret != 0) {
		instance->callbacks.log(
			FAUHDLI_LOG_ERROR, "fauhdli", __func__,
			"Failed to parse file \"%s\"\n", file_name);
		ret = fclose(yyin);
		assert(ret >= 0);
		return false;
	}

	ret = fclose(yyin);
	assert(ret >= 0);
	return true;
}

static void
fauhdli_setup_callbacks(
	struct fauhdli *instance, 
	const struct glue_vhdl_cb *callbacks
)
{
	if (callbacks == NULL) {
		memset(&instance->callbacks, 0, sizeof(instance->callbacks));
	} else {
		memcpy(&instance->callbacks, 
			callbacks, 
			sizeof(instance->callbacks));
	}

	if (   instance->callbacks.malloc == NULL 
	    || instance->callbacks.free == NULL) {
		instance->callbacks.malloc = malloc;
		instance->callbacks.free = free;
	}

	if (instance->callbacks.log == NULL) {
		instance->callbacks.log = 
			(void (*)(int, 
				const char *, 
				const char *, 
				const char *,
				...))fauhdli_log;
	}

	if (instance->callbacks.time_virt == NULL 
		|| instance->callbacks.time_call_at == NULL
		|| instance->callbacks.time_call_delete == NULL
		|| instance->callbacks.quit == NULL) {

		/* any scheduling callback set, but not all? -> error */
		if (instance->callbacks.time_virt != NULL
			|| instance->callbacks.time_call_at != NULL
			|| instance->callbacks.time_call_delete != NULL
			|| instance->callbacks.quit != NULL) {

			instance->callbacks.log(FAUHDLI_LOG_ERROR,
				"fauhdli",
				"setup",
				"Some, but not all callbacks for "
				"scheduling set. Overriding.\n");
		}

		instance->callbacks.time_virt = fauhdli_time_virt;
		instance->callbacks.time_call_at = fauhdli_time_call_at;
		instance->callbacks.time_call_delete = 
					fauhdli_time_call_delete;
		instance->callbacks.quit = fauhdli_simulation_quit;
	}
}

struct opcode *
fauhdli_alloc_opcode(
	enum opcode_kind kind, 
	const struct glue_vhdl_cb *callbacks
)
{
	/* FIXME calllbacks */
	struct opcode *ret = callbacks->malloc(sizeof(struct opcode));
	assert(ret != NULL);

	ret->kind = kind;
	ret->label = NULL;
	ret->op1 = NULL;
	ret->op2 = NULL;
	ret->op3 = NULL;
	ret->indexed_type = NULL;
	ret->annotations = NULL;

	return ret;
}

struct fauhdli *
fauhdli_create(
	const char *parse_file, 
	const char *trace_file, 
	bool debug,
	const struct glue_vhdl_cb *callbacks,
	void *_cpssp
)
{
	struct fauhdli *ret;
	bool r;

	if (callbacks->malloc != NULL) {
		ret = callbacks->malloc(sizeof(struct fauhdli));
	} else {
		ret = malloc(sizeof(struct fauhdli));
	}
	assert(ret != NULL);

	fauhdli_setup_callbacks(ret, callbacks);

	ret->container = NULL;
	ret->cleanup_ptrs = slset_create(NULL, ret->callbacks.malloc);
	ret->scheduler = vhdl_sched_create(&ret->callbacks);
	ret->signals = slset_create(NULL, ret->callbacks.malloc);
	ret->pending_stack_frames = slset_create(NULL, ret->callbacks.malloc);
	ret->processes = slset_create(NULL, ret->callbacks.malloc);
	ret->sim_time = 0;
	ret->debug = debug;
	ret->drivers = slset_create(NULL, ret->callbacks.malloc);
	ret->foreign_drivers = slset_create(NULL, ret->callbacks.malloc);

	ret->foreign_g_array_params.base.pointer = NULL;
	ret->foreign_g_array_params.left = 0;
	ret->foreign_g_array_params.left_set = false;
	ret->foreign_g_array_params.right = 0;
	ret->foreign_g_array_params.right_set = false;
	ret->foreign_g_array_params.direction = 0;
	ret->foreign_g_array_params.direction_set = false;
	ret->foreign_g_array_params.element_type = NULL;
	ret->foreign_g_array_params.formal_name = NULL;

	ret->foreign_mode = FOREIGN_MODE_COMPONENT;
	ret->wakeup_scheduled = false;

	ret->trace_list = NULL;

	assert(_cpssp != NULL);
	/* FIXME type should be opaque to us. */
	ret->glue_vhdl = _cpssp;


	if (trace_file != NULL) {
		ret->tracer = trace_create(trace_file, callbacks);
	} else {
		ret->tracer = NULL;
	}

	r = fauhdli_parse(ret, parse_file);
	if (! r) {
		assert(0);
		return ret;
	}

	return ret;
}

void
fauhdli_destroy(struct fauhdli *instance)
{
	assert(instance != NULL);
	assert(instance->cleanup_ptrs != NULL);

	if (instance->container != NULL) {
		fauhdli_destroy_container(instance->container,
					&instance->callbacks);
	}

	fauhdli_kernel_destroy(instance);
	slset_destroy(instance->pending_stack_frames, 
			instance->callbacks.free);
	slset_destroy(instance->drivers, instance->callbacks.free);
	slset_destroy(instance->foreign_drivers, instance->callbacks.free);
	slset_destroy_data(instance->cleanup_ptrs, instance->callbacks.free);
	fauhdli_destroy_signals(instance->signals, &instance->callbacks);
	vhdl_sched_destroy(instance->scheduler, &instance->callbacks);
	slset_destroy(instance->processes, instance->callbacks.free);
	if (instance->tracer != NULL) {
		trace_destroy(instance->tracer, &instance->callbacks);
	}
	instance->callbacks.free(instance);
}

void
fauhdli_init(
	struct fauhdli *instance, 
	const char *top_entity
)
{
	if (top_entity == NULL) {
		instance->callbacks.log(
			FAUHDLI_LOG_WARNING, "fauhdli", __func__,
			"Top entity not specified!\n");
	}

	fauhdli_resolve_symbols(instance);
	fauhdli_kernel_init(instance, top_entity);
}

const struct annotation_spec *
fauhdli_find_annotation(const char *name, const struct slist *annotations)
{
	struct slist_entry *i;

	if (annotations == NULL) {
		return NULL;
	}

	for (i = annotations->first; i != NULL; i = i->next) {
		const struct annotation_spec *spec = 
			(const struct annotation_spec *)i->data;
		if (strcmp(spec->name, name) == 0) {
			return spec;
		}
	}

	return NULL;
}

void
fauhdli_set_driver(
	struct fauhdli *instance,
	void *_drv,
	union fauhdli_value value
)
{
	struct driver *drv = (struct driver *)_drv;
	int64_t t;

	/* set driver value */
	assert(instance->callbacks.time_virt != NULL);
	t = (int64_t)instance->callbacks.time_virt();
	assert(0 <= t);
	driver_update(drv, value, t, &instance->callbacks);

	/* kernel already running? */
	if (instance->wakeup_scheduled) {
		return;
	}

	/* schedule a delta cycle */
	assert(instance->callbacks.time_call_delete != NULL);
	assert(instance->callbacks.time_call_at != NULL);

	instance->callbacks.time_call_delete(
			fauhdli_kernel_simulation_cycle, 
			instance);
	instance->callbacks.time_call_at(
			t,
			fauhdli_kernel_simulation_cycle,
			instance);

	instance->wakeup_scheduled = true;
}

