#include "sbbs.h"
#include "js_rtpool.h"

static pthread_mutex_t jsrt_mutex;
static int             initialized = 0;
static link_list_t     rt_list;

#define TRIGGER_THREAD_STACK_SIZE       (256 * 1024)
static void trigger_thread(void *args)
{
	SetThreadName("sbbs/jsRTtrig");
	for (;;) {
		list_node_t *node;
		pthread_mutex_lock(&jsrt_mutex);
		for (node = listFirstNode(&rt_list); node; node = listNextNode(node))
			// Defensive: a NULL runtime must never reach the list, but skip one
			// if it does rather than dereferencing it in the JS engine.
			if (node->data != NULL)
				JS_TriggerAllOperationCallbacks(static_cast<JSRuntime *>(node->data));
		pthread_mutex_unlock(&jsrt_mutex);
		SLEEP(100);
	}
}

static void
jsrt_init(void)
{
	initialized = TRUE;
	pthread_mutex_init(&jsrt_mutex, NULL);
	pthread_mutex_lock(&jsrt_mutex);
	listInit(&rt_list, 0);
	pthread_mutex_unlock(&jsrt_mutex);
	_beginthread(trigger_thread, TRIGGER_THREAD_STACK_SIZE, NULL);
}

static pthread_once_t jsrt_once = PTHREAD_ONCE_INIT;
JSRuntime * jsrt_GetNew(int maxbytes, unsigned long timeout, const char *filename, long line)
{
	JSRuntime *ret;

	pthread_once(&jsrt_once, jsrt_init);
	pthread_mutex_lock(&jsrt_mutex);
	ret = JS_NewRuntime(maxbytes);
	// JS_NewRuntime() returns NULL on failure (e.g. under memory pressure).
	// Don't list a NULL runtime: trigger_thread would dereference it and crash
	// the whole process in JS_TriggerAllOperationCallbacks.
	if (ret != NULL)
		listPushNode(&rt_list, ret);
	pthread_mutex_unlock(&jsrt_mutex);
	return ret;
}

void jsrt_Release(JSRuntime *rt)
{
	list_node_t *node;

	// Nothing to release for a failed (NULL) runtime; JS_DestroyRuntime(NULL)
	// would itself crash.  Callers currently bail before getting here, but
	// guard anyway to keep a NULL runtime from ever crashing us.
	if (rt == NULL)
		return;
	pthread_mutex_lock(&jsrt_mutex);
	node = listFindNode(&rt_list, rt, 0);
	if (node)
		listRemoveNode(&rt_list, node, FALSE);
	JS_DestroyRuntime(rt);
	pthread_mutex_unlock(&jsrt_mutex);
}
