/* $Id$
 *
 * C-Code kernel / runtime library.
 *
 * Copyright (C) 2010 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.
 */


/* FIXME: name too generic? 
 * FIXME: cleanup between stuff in interpreter and this file.
 */

#ifndef __RUNTIME_H_INCLUDED
#define __RUNTIME_H_INCLUDED

#include <basetypes.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <stdbool.h>
#include <slset.h>
#include <errno.h>

#define ALWAYS_INLINE inline __attribute__((__always_inline__))


union vhdl_value {
	universal_integer univ_int;
	universal_integer univ_real;
};

struct vhdl_kernel {
	struct slset *signals;
	struct slset *processes;
	universal_integer sim_time;
	struct slset *drivers;
	struct slset *run_q;
	struct slset *wait_q;
	struct slset *create_q;
};

struct vhdl_process {
	pthread_t thread;
	pthread_mutex_t sleeping;
	struct slset *sensitivities;
	universal_integer next_event;
	void *stack_frame;
};

struct signal {
	union vhdl_value value;
	struct slset *connected_drivers;
	bool event;
	/* FIXME resolution function */
};

struct drv_trans {
	universal_integer sim_time;
	union vhdl_value val;
};

struct driver {
	union vhdl_value driving_value;
	struct signal *connected_signal;
	bool active;
	struct slset *transactions;
};


static ALWAYS_INLINE universal_integer
signal_read_int(const struct signal *sig)
{
	return sig->value.univ_int;
}

static ALWAYS_INLINE universal_real
signal_read_real(const struct signal *sig)
{
	return sig->value.univ_real;
}

static ALWAYS_INLINE struct signal *
signal_create(struct vhdl_kernel *kernel, const char *name)
{
	struct signal *sig = malloc(sizeof(struct signal));
	assert(sig != NULL);
	/* FIXME initial value */
	sig->value.univ_int = 0;

	sig->connected_drivers = slset_create(NULL, malloc);
	sig->event = false;

	slset_add(kernel->signals, sig, malloc);
	return sig;
}

static int
__attribute__((__pure__))
drv_trans_compare(const void *_t1, const void *_t2)
{
	const struct drv_trans *t1 = (const struct drv_trans *)_t1;
	const struct drv_trans *t2 = (const struct drv_trans *)_t2;

	if (t1->sim_time < t2->sim_time) {
		return -1;
	}

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

	return 1;
}

static ALWAYS_INLINE struct driver *
driver_create(struct vhdl_kernel *kernel)
{
	struct driver *drv = malloc(sizeof(struct driver));
	assert(drv != NULL);
	/* FIXME initial value */
	drv->driving_value.univ_int = 0;
	drv->connected_signal = NULL;
	drv->transactions = slset_create(drv_trans_compare, malloc);
	drv->active = false;

	slset_add(kernel->drivers, drv, malloc);
	return drv;
}

static ALWAYS_INLINE void
driver_connect(struct driver *drv, struct signal *s)
{
	assert(drv != NULL);
	assert(s != NULL);
	slset_add(s->connected_drivers, drv, malloc);
	drv->connected_signal = s;
}

static ALWAYS_INLINE void
driver_update_int_after(
	struct driver *drv, 
	universal_integer sim_time,
	universal_integer value
)
{
	struct drv_trans *t = malloc(sizeof(struct drv_trans));
	assert(t != NULL);

	t->sim_time = sim_time;
	t->val.univ_int = value;

	slset_truncate_at(drv->transactions, t, true, free);
	slset_add(drv->transactions, t, malloc);
}

static ALWAYS_INLINE void
driver_update_real_after(
	struct driver *drv, 
	universal_integer sim_time,
	universal_real value
)
{
	struct drv_trans *t = malloc(sizeof(struct drv_trans));
	assert(t != NULL);

	t->sim_time = sim_time;
	t->val.univ_real = value;

	slset_truncate_at(drv->transactions, t, true, free);
	slset_add(drv->transactions, t, malloc);
}


static ALWAYS_INLINE struct vhdl_process *
sched_create_process(
	struct vhdl_kernel *kernel, 
	void *sf, 
	void *(*fun)(void *)
)
{
	struct vhdl_process *proc = malloc(sizeof(struct vhdl_process));
	int ret;
	assert(proc != NULL);

	proc->sensitivities = slset_create(NULL, malloc);
	proc->next_event = INT64_MAX;
	proc->stack_frame = sf;
	do {
		ret = pthread_mutex_init(&proc->sleeping, NULL);
		assert((ret == 0) || (ret == EAGAIN));
	} while (ret == EAGAIN);

	slset_add(kernel->create_q, proc, malloc);
	return proc;
}

static ALWAYS_INLINE void
sched_wake_on(struct vhdl_process *proc, struct signal *sensitivity)
{
	slset_add(proc->sensitivities, sensitivity, malloc);
}

static ALWAYS_INLINE void
sched_suspend(struct vhdl_kernel *kernel, struct vhdl_process *proc)
{
	int ret;
	ret = pthread_mutex_lock(&proc->sleeping);
	/* FIXME decrease *shared* number of running processes */
	assert(ret == 0);
}

static ALWAYS_INLINE void
sched_abort(void)
{
	/* FIXME */
}

static ALWAYS_INLINE void
vhdl_log(universal_integer severity, universal_integer c)
{
	switch (severity) {
	case 3: /* failure */
		fprintf(stdout, "FAILURE: ");
		break;

	case 2: /* error */
		fprintf(stdout, "ERROR: ");
		break;

	case 1: /* warning */
		fprintf(stdout, "WARNING: ");
		break;

	case 0: /* note */
		fprintf(stdout, "NOTE: ");
		break;

	default:
		break;
	}

	fprintf(stdout, "%c", (int)c);
}

#endif /* __RUNTIME_H_INCLUDED */
