/* The SpiderMonkey ECMAScript backend heartbeat fuctionality. */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/time.h>    /* setitimer(2) */
#include <signal.h>

#include "elinks.h"

#include "js/spidermonkey/util.h"

#include "config/options.h"
#include "document/view.h"
#include "js/ecmascript.h"
#include "js/spidermonkey.h"
#include "js/spidermonkey/heartbeat.h"
#include "osdep/signals.h"
#include "session/session.h"
#include "util/lists.h"
#include "util/math.h"   /* int_upper_bound */
#include "util/memory.h"
#include "viewer/text/vs.h"



static INIT_LIST_OF(struct heartbeat, heartbeats);

static struct itimerval heartbeat_timer = { { 1, 0 }, { 1, 0 } };


/* This callback is installed by JS_SetInterruptCallback and triggered
 * by JS_RequestInterruptCallback in the heartbeat code below.  Returning
 * false terminates script execution immediately. */
bool
heartbeat_callback(JSContext *ctx)
{
	JS::Realm *comp = js::GetContextRealm(ctx);

	if (!comp) {
		return true;
	}

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp);

	if (!interpreter || !interpreter->heartbeat || interpreter->heartbeat->ttl > 0) {
		return true;
	}
	return false;
}

/* Callback for SIGVTALRM.  Go through all heartbeats, decrease each
 * one's TTL, and call JS_RequestInterruptCallback if a heartbeat's TTL
 * goes to 0. */
static void
check_heartbeats(void *data)
{
	struct heartbeat *hb;

	foreach (hb, heartbeats) {
		assert(hb->interpreter);
		--hb->ttl;

		if (hb->ttl <= 0) {
			if (hb->interpreter->vs
				&& hb->interpreter->vs->doc_view
				&& hb->interpreter->vs->doc_view->session
				&& hb->interpreter->vs->doc_view->session->tab
				&& hb->interpreter->vs->doc_view->session->tab->term) {
				struct session *ses = hb->interpreter->vs->doc_view->session;
				struct terminal *term = ses->tab->term;
				int max_exec_time = get_opt_int("ecmascript.max_exec_time", ses);

				ecmascript_timeout_dialog(term, max_exec_time);
			}
			JS_RequestInterruptCallback((JSContext *)hb->interpreter->backend_data);
		}
	}
	install_signal_handler(SIGVTALRM, check_heartbeats, NULL, 1);
}

/* Create a new heartbeat for the given interpreter. */
struct heartbeat *
add_heartbeat(struct ecmascript_interpreter *interpreter)
{
	struct session *ses;
	struct heartbeat *hb;

	assert(interpreter);

	if (interpreter->heartbeat) {
		interpreter->heartbeat->ref_count++;
		return interpreter->heartbeat;
	}

	if (!interpreter->vs || !interpreter->vs->doc_view)
		ses = NULL;
	else
		ses = interpreter->vs->doc_view->session;
	hb = (struct heartbeat *)mem_alloc(sizeof(struct heartbeat));

	if (!hb) return NULL;

	hb->ttl = get_opt_int("ecmascript.max_exec_time", ses);
	hb->interpreter = interpreter;
	hb->ref_count = 1;
	add_to_list(heartbeats, hb);

	/* Update the heartbeat timer. */
	if (list_is_singleton(*hb)) {
		heartbeat_timer.it_value.tv_sec = 1;
		setitimer(ITIMER_VIRTUAL, &heartbeat_timer, NULL);
	}

	/* We install the handler every call to add_heartbeat instead of only on
	 * module initialisation because other code may set other handlers for
	 * the signal.  */
	install_signal_handler(SIGVTALRM, check_heartbeats, NULL, 1);

	return hb;
}

/* Destroy the given heartbeat. */
void
done_heartbeat(struct heartbeat *hb)
{
	if (!hb) return; /* add_heartbeat returned NULL */
	assert(hb->interpreter);

	if (--hb->ref_count > 0) {
		return;
	}

	/* Stop the heartbeat timer if this heartbeat is the only one. */
	if (list_is_singleton(*hb)) {
		heartbeat_timer.it_value.tv_sec = 0;
		setitimer(ITIMER_VIRTUAL, &heartbeat_timer, NULL);
	}

	del_from_list(hb);
	hb->interpreter->heartbeat = NULL;
	mem_free(hb);
}
