/* Event Target binding for browser using duktape and libdom
 *
 * Copyright 2016 Daniel Silverstone <dsilvers@digital-scurf.org>
 *
 * This file is part of NetSurf, http://www.netsurf-browser.org/
 *
 * Released under the terms of the MIT License,
 *         http://www.opensource.org/licenses/mit-license
 */

class EventTarget {
	private bool is_node;
	private bool capture_registered;
	private bool bubbling_registered;
};

prologue EventTarget()
%{

static event_listener_flags event_listener_pop_options(duk_context *ctx)
{
	event_listener_flags ret = ELF_NONE;
	/* ... options */
	duk_get_prop_string(ctx, -1, "capture");
	if (duk_to_boolean(ctx, -1))
		ret |= ELF_CAPTURE;
	duk_pop(ctx);
	duk_get_prop_string(ctx, -1, "passive");
	if (duk_to_boolean(ctx, -1))
		ret |= ELF_PASSIVE;
	duk_pop(ctx);
	duk_get_prop_string(ctx, -1, "once");
	if (duk_to_boolean(ctx, -1))
		ret |= ELF_ONCE;
	duk_pop_2(ctx);
	/* ... */
	return ret;
}

static void event_target_register_listener(duk_context *ctx,
					   event_listener_flags flags)
{
	/* ... listeners callback */
	/* If the given callback with the given flags is already present,
	 * we do not re-add it, otherwise we need to add to listeners
	 * a tuple of the callback and flags
	 */
	duk_uarridx_t idx = 0;
	while (duk_get_prop_index(ctx, -1, idx)) {
		/* ... listeners callback candidate */
		duk_get_prop_index(ctx, -1, 0);
		duk_get_prop_index(ctx, -2, 1);
		/* ... listeners callback candidate candidatecallback candidateflags */
		if (duk_strict_equals(ctx, -1, -3) &&
		    duk_get_int(ctx, -1) == (duk_int_t)flags) {
			/* already present, nothing to do */
			duk_pop_n(ctx, 5);
			/* ... */
			return;
		}
		/* ... listeners callback candidate candidatecallback candidateflags */
		duk_pop_3(ctx);
		/* ... listeners callback */
		idx++;
	}
	/* ... listeners callback undefined */
	duk_pop(ctx);
	/* ... listeners callback */
	duk_push_array(ctx);
	/* ... listeners callback newcandidate */
	duk_insert(ctx, -2);
	/* ... listeners newcandidate callback */
	duk_put_prop_index(ctx, -2, 0);
	/* ... listeners newcandidate */
	duk_push_int(ctx, (duk_int_t)flags);
	/* ... listeners newcandidate flags */
	duk_put_prop_index(ctx, -2, 1);
	/* ... listeners newcandidate */
	duk_put_prop_index(ctx, -2, idx);
	/* ... listeners */
	duk_pop(ctx);
	/* ... */
}

static void event_target_unregister_listener(duk_context *ctx,
					     event_listener_flags flags)
{
	/* ... listeners callback */
	/* If the given callback with the given flags is present,
	 * we remove it and shuffle the rest up.
	 */
	duk_uarridx_t idx = 0;
	while (duk_get_prop_index(ctx, -1, idx)) {
		/* ... listeners callback candidate */
		duk_get_prop_index(ctx, -1, 0);
		duk_get_prop_index(ctx, -2, 1);
		/* ... listeners callback candidate candidatecallback candidateflags */
		if (duk_strict_equals(ctx, -1, -3) &&
		    duk_get_int(ctx, -1) == (duk_int_t)flags) {
			/* present */
			duk_pop(ctx);
			/* ... listeners callback candidate candidatecallback */
			duk_put_prop_index(ctx, -2, 2);
			/* ... listeners callback candidate */
			duk_pop(ctx);
			/* ... listeners callback */
			duk_push_int(ctx, idx);
			/* ... listeners callback found_at */
			break;
		}
		/* ... listeners callback candidate candidatecallback candidateflags */
		duk_pop_3(ctx);
		/* ... listeners callback */
		idx++;
	}
	/* ... listeners callback undefined/found_at */
	if (duk_is_undefined(ctx, -1)) {
		/* not found, clean up and come out */
		duk_pop_3(ctx);
		return;
	}
	idx = duk_to_int(ctx, -1);
	duk_pop_2(ctx);
	/* ... listeners */
	dukky_shuffle_array(ctx, idx);
	/* ... listeners */
	duk_pop(ctx);
	/* ... */
}


%}

init EventTarget()
%{
	priv->is_node = false;
	priv->capture_registered = false;
	priv->bubbling_registered = false;
%}

method EventTarget::addEventListener()
%{
	dom_exception exc;
	event_listener_flags flags = ELF_NONE;
	/* Incoming stack is: type callback [options] */
	if (duk_get_top(ctx) < 2) return 0; /* Bad arguments */
	if (duk_get_top(ctx) > 3) return 0; /* Bad arguments */
	if (duk_get_top(ctx) == 2) {
		duk_push_object(ctx);
		/* type callback options */
	}
	if (duk_get_type(ctx, -1) != DUK_TYPE_OBJECT) {
		/* legacy support, if not object, it's the capture value */
		duk_push_object(ctx);
		/* ... capture options */
		duk_insert(ctx, -2);
		/* ... options capture */
		duk_put_prop_string(ctx, -2, "capture");
		/* ... options */
	}
	/* type callback options */
	flags = event_listener_pop_options(ctx);
	/* type callback */
	duk_dup(ctx, -2);
	/* type callback type */
	duk_push_this(ctx);
	/* type callback type this(=EventTarget) */
	if (dukky_event_target_push_listeners(ctx, false) && priv->is_node) {
		/* Take a moment to register a JS callback */
		duk_size_t ev_ty_l;
		const char *ev_ty = duk_to_lstring(ctx, -3, &ev_ty_l);
		dom_string *ev_ty_s;
		exc = dom_string_create((const uint8_t*)ev_ty, ev_ty_l,
					&ev_ty_s);
		if (exc != DOM_NO_ERR) {
			NSLOG(netsurf, INFO,
			      "Oh dear, failed to create dom_string in addEventListener()");
			return 0;
		}
		dukky_register_event_listener_for(
			ctx, (dom_element *)((node_private_t *)priv)->node,
			ev_ty_s,
			!!(flags & ELF_CAPTURE));
		dom_string_unref(ev_ty_s);
	}
	/* type callback typelisteners */
	duk_insert(ctx, -2);
	/* type typelisteners callback */
	event_target_register_listener(ctx, flags);
	/* type */
	return 0;
%}

method EventTarget::removeEventListener()
%{
	event_listener_flags flags = ELF_NONE;
	/* Incoming stack is: type callback [options] */
	if (duk_get_top(ctx) < 2) return 0; /* Bad arguments */
	if (duk_get_top(ctx) > 3) return 0; /* Bad arguments */
	if (duk_get_top(ctx) == 2) {
		duk_push_object(ctx);
		/* type callback options */
	}
	if (duk_get_type(ctx, -1) != DUK_TYPE_OBJECT) {
		/* legacy support, if not object, it's the capture value */
		duk_push_object(ctx);
		/* ... capture options */
		duk_insert(ctx, -2);
		/* ... options capture */
		duk_put_prop_string(ctx, -2, "capture");
		/* ... options */
	}
	/* type callback options */
	flags = event_listener_pop_options(ctx);
	/* type callback */
	duk_dup(ctx, -2);
	/* type callback type */
	duk_push_this(ctx);
	/* type callback type this(=EventTarget) */
	if (dukky_event_target_push_listeners(ctx, true)) {
		/* nothing to do because the listener wasn't there at all */
		duk_pop_3(ctx);
		return 0;
	}
	/* type callback typelisteners */
	duk_insert(ctx, -2);
	/* type typelisteners callback */
	event_target_unregister_listener(ctx, flags);
	/* type */
	return 0;
%}



method EventTarget::dispatchEvent()
%{
	dom_exception exc;
	if (!dukky_instanceof(ctx, 0, PROTO_NAME(EVENT))) return 0;

	duk_get_prop_string(ctx, 0, PRIVATE_MAGIC);
	event_private_t *evpriv = duk_get_pointer(ctx, -1);
	duk_pop(ctx);

	dom_event *evt = evpriv->evt;

	/* Dispatch event logic, see:
	 * https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent
	 */
	bool in_dispatch;
	if (dom_event_in_dispatch(evt, &in_dispatch) != DOM_NO_ERR) return 0;
	if (in_dispatch) {
		/** \todo Raise InvalidStateException */
		return 0;
	}

	bool is_initialised;
	if (dom_event_is_initialised(evt, &is_initialised) != DOM_NO_ERR) return 0;
	if (is_initialised == false) {
		/** \todo Raise InvalidStateException */
		return 0;
	}

	if (dom_event_set_is_trusted(evt, false) != DOM_NO_ERR) return 0;

	/** \todo work out how to dispatch against non-node things */
	if (priv->is_node == false) return 0;

	bool success;
	/* Event prepared, dispatch against ourselves */
	exc = dom_event_target_dispatch_event(
		((node_private_t *)priv)->node,
		evt,
		&success);
	if (exc != DOM_NO_ERR) return 0; /**< \todo raise correct exception */

	duk_push_boolean(ctx, success);
	return 1;
%}
