/*** exception.c ***/
/* C exception handling system
 * (c) Paul Field
 */

#include "exception.h"

#include <assert.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "kernel.h"

static const _kernel_oserror user_error = {0, "Custom throwed exception"};
static exception current;
_exception_block *_exception_list = NULL;

void throw_current(void)
{
	_exception_block* first_block;

	first_block = _exception_list;
	if (first_block == NULL)
	{
		exit(EXIT_FAILURE);
	}
	else
	{
		_exception_list = first_block->previous;
		longjmp(first_block->jmpbuf, 1);
	}
}

void __throw_user(const char* file , int line, unsigned id)
{
	current.file  = file;
	current.line  = line;
	current.bt    = 0;
	current.type  = exception_user;
	current.error = user_error;
	current.error.errnum = id;
	throw_current();
}

void __throw_os(const char* file , int line, const _kernel_oserror* err)
{
	if (!err) return;

	current.file  = file;
	current.line  = line;
	current.bt    = 0;
	current.type  = exception_os;
	current.error = *err;
	throw_current();
}

void __throw_last_oserror(const char* file , int line)
{
	current.file  = file;
	current.line  = line;
	current.bt    = 0;
	current.type  = exception_os;
	current.error = *_kernel_last_oserror();
	throw_current();
}

void __throw_string(const char* file , int line, const char* pformat, ...)
{
	va_list arg;

	current.file  = file;
	current.line  = line;
	current.bt    = 0;
	current.type  = exception_os;

	va_start(arg, pformat);
	vsnprintf(current.error.errmess, sizeof(current.error.errmess), pformat, arg);
	va_end(arg);

	current.error.errnum = 1;

	throw_current();
}

const exception* exception_current(void)
{
	return &current;
}

static void throw_signal(int sig)
{
	const _kernel_oserror* perr = _kernel_last_oserror();

	signal(sig, throw_signal);  // reinstate signal handler

	if (perr)
	{
		const _kernel_oserror err = *perr;

		__throw_os(NULL, 0, &err);
	}
	else
	{
		__throw_string(NULL, 0, "ErrSignal%d", sig);
	}
}

static void throw_stacksignal(int sig)
{
	current.file  = NULL;
	current.line  = 0;
	current.bt    = 1;
	current.type  = exception_os;
	strncpy(current.error.errmess, "Stack overflow", sizeof(current.error.errmess));
	current.error.errnum = 1;

	signal(sig, throw_stacksignal);  // reinstate signal handler

	throw_current();
}

void exception_initialise(void)
{
	signal(SIGABRT, throw_signal);
	signal(SIGFPE,  throw_signal);
	signal(SIGILL,  throw_signal);
	signal(SIGINT,  throw_signal);
	signal(SIGSEGV, throw_signal);
	signal(SIGTERM, throw_signal);
	signal(SIGSTAK, throw_stacksignal);
	signal(SIGOSERROR, throw_signal);
}
