/* document binding for browser using duktape and libdom
 *
 * Copyright 2015 Vincent Sanders <vince@netsurf-browser.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
 */

prologue Document()
%{
#include "utils/corestrings.h"
#include "utils/libdom.h"
#include "utils/utils.h"
#include "content/hlcache.h"
#include "html/private.h"
#include "content/urldb.h"

#define HANDLER_MAGIC MAGIC(HANDLER_MAP)
%}


init Document(struct dom_document *document::node);

method Document::write()
%{
	struct html_content *htmlc;
	duk_size_t text_len;
	dom_exception err;
	const char *text;

	for (int i = 0; i < duk_get_top(ctx); ++i) {
		duk_safe_to_string(ctx, i);
	}
	duk_concat(ctx, duk_get_top(ctx));
	text = duk_safe_to_lstring(ctx, 0, &text_len);
	NSLOG(netsurf, DEBUG, "Writing %*s", (int)text_len, text);

	err = dom_node_get_user_data(priv->parent.node,
				     corestring_dom___ns_key_html_content_data,
				     &htmlc);
	if ((err != DOM_NO_ERR) || (htmlc == NULL)) {
		NSLOG(netsurf, INFO,
		      "error getting htmlc. parent node:%p htmlc:%p",
		      priv->parent.node, htmlc);
		return 0;
	} else if (htmlc->parser == NULL) {
		NSLOG(netsurf, INFO, "error; no parser for htmlc: %p", htmlc);
		return 0;
	}

	dom_hubbub_parser_insert_chunk(htmlc->parser,
			(uint8_t *)text, text_len);

	return 0;
%}

method Document::writeln()
%{
	const char nl[] = "\n";
	struct html_content *htmlc;
	duk_size_t text_len;
	const char *text;
	dom_exception err;

	for (int i = 0; i < duk_get_top(ctx); ++i) {
		duk_safe_to_string(ctx, i);
	}
	duk_concat(ctx, duk_get_top(ctx));
	text = duk_safe_to_lstring(ctx, 0, &text_len);

	NSLOG(netsurf, DEBUG, "Writeln %*s", (int)text_len, text);
	err = dom_node_get_user_data(priv->parent.node,
				     corestring_dom___ns_key_html_content_data,
				     &htmlc);
	if ((err != DOM_NO_ERR) || (htmlc == NULL)) {
		NSLOG(netsurf, INFO,
		      "error getting htmlc. parent node:%p htmlc:%p",
		      priv->parent.node, htmlc);
		return 0;
	} else if (htmlc->parser == NULL) {
		NSLOG(netsurf, INFO, "error; no parser for htmlc: %p", htmlc);
		return 0;
	}

	dom_hubbub_parser_insert_chunk(htmlc->parser, (uint8_t *)text, text_len);
	dom_hubbub_parser_insert_chunk(htmlc->parser, (uint8_t *)nl, SLEN(nl));

	return 0;
%}

method Document::createTextNode()
%{
	dom_node *newnode;
	dom_exception err;
	duk_size_t text_len;
	const char *text = duk_safe_to_lstring(ctx, 0, &text_len);
	dom_string *text_str;

	err = dom_string_create((const uint8_t*)text, text_len, &text_str);
	if (err != DOM_NO_ERR) return 0; /* coerced to undefined */

	err = dom_document_create_text_node(priv->parent.node,
					    text_str,
					    &newnode);
	if (err != DOM_NO_ERR) {
		dom_string_unref(text_str);
		return 0; /* coerced to undefined */
	}

	dom_string_unref(text_str);

	dukky_push_node(ctx, newnode);

	dom_node_unref(newnode);

	return 1;
%}

method Document::createElement()
%{
	dom_node *newnode;
	dom_exception err;
	duk_size_t text_len;
	const char *text = duk_safe_to_lstring(ctx, 0, &text_len);
	dom_string *text_str;

	err = dom_string_create((const uint8_t*)text, text_len, &text_str);
	if (err != DOM_NO_ERR) return 0; /* coerced to undefined */

	err = dom_document_create_element_ns(priv->parent.node,
					     corestring_dom_html_namespace,
					     text_str,
					     &newnode);
	if (err != DOM_NO_ERR) {
		dom_string_unref(text_str);
		return 0; /* coerced to undefined */
	}

	dom_string_unref(text_str);

	dukky_push_node(ctx, newnode);

	dom_node_unref(newnode);

	return 1;
%}

method Document::createElementNS()
%{
	dom_node *newnode;
	dom_exception err;
	duk_size_t text_len;
	duk_size_t ns_len;
	const char *ns = duk_safe_to_lstring(ctx, 0, &ns_len);
	const char *text = duk_safe_to_lstring(ctx, 0, &text_len);
	dom_string *text_str;
	dom_string *ns_str;

	err = dom_string_create((const uint8_t*)ns, ns_len, &ns_str);
	if (err != DOM_NO_ERR) return 0; /* coerced to undefined */

	err = dom_string_create((const uint8_t*)text, text_len, &text_str);
	if (err != DOM_NO_ERR) {
		dom_string_unref(ns_str);
		return 0; /* coerced to undefined */
	}

	err = dom_document_create_element_ns(priv->parent.node,
					     ns_str,
					     text_str,
					     &newnode);
	if (err != DOM_NO_ERR) {
		dom_string_unref(ns_str);
		dom_string_unref(text_str);
		return 0; /* coerced to undefined */
	}

	dom_string_unref(text_str);
	dom_string_unref(ns_str);

	dukky_push_node(ctx, newnode);

	dom_node_unref(newnode);

	return 1;
%}

method Document::createDocumentFragment()
%{
	struct dom_document_fragment *frag = NULL;
	dom_exception err;

	err = dom_document_create_document_fragment(priv->parent.node, &frag);

	if (err != DOM_NO_ERR) return 0; /* coerced to undefined */

	dukky_push_node(ctx, (dom_node *)frag);

	dom_node_unref(frag); /* The pushed node holds the reference now */

	return 1;
%}

getter Document::head()
%{
	struct dom_nodelist *nodes;
	struct dom_node *retnode;
	dom_exception err;
	err = dom_document_get_elements_by_tag_name(priv->parent.node,
						    corestring_dom_HEAD,
						    &nodes);
	if (err != DOM_NO_ERR) return 0; /* coerced to undefined */

	err = dom_nodelist_item(nodes, 0, &retnode);

	if (err != DOM_NO_ERR) {
		dom_nodelist_unref(nodes);
		return 0; /* coerced to undefined */
	}

	dom_nodelist_unref(nodes);

	if (retnode == NULL) return 0; /* coerced to undefined */

	dukky_push_node(ctx, retnode);

	dom_node_unref(retnode);

	return 1;
%}

getter Document::body()
%{
	struct dom_nodelist *nodes;
	struct dom_node *retnode;
	dom_exception err;
	err = dom_document_get_elements_by_tag_name(priv->parent.node,
						    corestring_dom_BODY,
						    &nodes);
	if (err != DOM_NO_ERR) {
		return 0; /* coerced to undefined */
	}

	err = dom_nodelist_item(nodes, 0, &retnode);

	if (err != DOM_NO_ERR) {
		dom_nodelist_unref(nodes);
		return 0; /* coerced to undefined */
	}

	dom_nodelist_unref(nodes);

	if (retnode != NULL) {
		dukky_push_node(ctx, retnode);

		dom_node_unref(retnode);

		return 1;
	}

	return 0; /* coerced to undefined */
%}

getter Document::location()
%{
	/* retrieve the location object from the root object (window) */
	duk_push_global_object(ctx);
	duk_get_prop_string(ctx, -1, "location");
	if (duk_is_undefined(ctx, -1)) {
		duk_pop(ctx);
		return 0;
	}
	return 1;
%}


method Document::getElementById()
%{
	dom_string *elementId_dom;
	dom_element *element;
	dom_exception exc;
	duk_size_t text_len;
	const char *text = duk_safe_to_lstring(ctx, 0, &text_len);

	exc = dom_string_create((uint8_t*)text, text_len, &elementId_dom);
	if (exc != DOM_NO_ERR) {
		return 0;
	}

	exc = dom_document_get_element_by_id(((node_private_t *)priv)->node,
					     elementId_dom, &element);
	dom_string_unref(elementId_dom);
	if (exc != DOM_NO_ERR) {
		return 0;
	}

	if (element != NULL) {
		dukky_push_node(ctx, (dom_node *)element);
		dom_node_unref(element);
		return 1;
	}

	return 0;
%}

getter Document::documentElement()
%{
	dom_exception exc;
	dom_element *element;

	exc = dom_document_get_document_element(((node_private_t *)priv)->node,
						&element);
	if (exc != DOM_NO_ERR) {
		return 0;
	}

	if (element == NULL) {
		return 0;
	}

	dukky_push_node(ctx, (dom_node *)element);
	dom_node_unref(element);

	return 1;

%}

method Document::getElementsByTagName()
%{
	dom_nodelist *nodes;
	dom_exception err;
	duk_size_t text_len;
	const char *text = duk_safe_to_lstring(ctx, 0, &text_len);
	dom_string *tag;

	err = dom_string_create((uint8_t*)text, text_len, &tag);

	if (err != DOM_NO_ERR) return 0; /* coerced to undefined */

	err = dom_document_get_elements_by_tag_name(((node_private_t *)priv)->node,
						    tag, &nodes);
	dom_string_unref(tag);
	if (err != DOM_NO_ERR) return 0; /* coerced to undefined */

	if (nodes == NULL) return 0; /* coerced to undefined */

	dukky_push_generics(ctx, "makeListProxy");

	duk_push_pointer(ctx, nodes);
	dukky_create_object(ctx, PROTO_NAME(NODELIST), 1);
	dom_nodelist_unref(nodes);

	if (dukky_pcall(ctx, 1, false) != 0) {
		NSLOG(dukky, DEBUG, "Unable to construct nodelist?");
		return 0; /* coerced to undefined */
	}

	return 1; /* The Proxy(NodeList) wrapper */
%}

getter Document::cookie()
%{
	char *cookie_str;
	struct html_content *htmlc;
	dom_exception err;

	err = dom_node_get_user_data(priv->parent.node,
				     corestring_dom___ns_key_html_content_data,
				     &htmlc);
	if ((err == DOM_NO_ERR) && (htmlc != NULL)) {
		cookie_str = urldb_get_cookie(llcache_handle_get_url(htmlc->base.llcache), false);
		if (cookie_str != NULL) {
			duk_push_string(ctx, cookie_str);
			free(cookie_str);
		} else {
			duk_push_string(ctx, "");
		}
		return 1;
	} else {
		NSLOG(netsurf, INFO,
		      "error getting htmlc. parent node:%p htmlc:%p",
		      priv->parent.node, htmlc);
	}
	return 0;
%}

setter Document::cookie()
%{
	struct html_content *htmlc;
	dom_exception err;

	const char * cookie_str = duk_safe_to_string(ctx, 0);

	err = dom_node_get_user_data(priv->parent.node,
				     corestring_dom___ns_key_html_content_data,
				     &htmlc);
	if ((err == DOM_NO_ERR) && (htmlc != NULL)) {
		/* At this point we need to get the given cookie string parsed
		 * and inserted into the urldb
		 */
		bool ok = urldb_set_cookie(cookie_str, /* The cookie string to set */
					   /* The location to set the cookie for */
					   llcache_handle_get_url(htmlc->base.llcache),
					   NULL); /* The referer, which we trust */
		if (!ok) {
			NSLOG(netsurf, DEEPDEBUG, "unable to set cookie: %s", cookie_str);
			/* However there's no useful way to signal that to JS */
		}
	} else {
		NSLOG(netsurf, INFO,
		      "error getting htmlc. parent node:%p htmlc:%p",
		      priv->parent.node, htmlc);
	}
	return 0;
%}

method Document::createEvent ()
%{
	/* Create a new event, mark it untrusted since it's new from JS */
	dom_event *evt = NULL;
	dom_exception exc;

	exc = dom_event_create(&evt);
	if (exc != DOM_NO_ERR) return 0;
	exc = dom_event_set_is_trusted(evt, false);
	if (exc != DOM_NO_ERR) { dom_event_unref(evt); return 0; }

	dukky_push_event(ctx, evt);

	dom_event_unref(evt);
	return 1;
%}

getter Document::implementation ()
%{
	/* Create a new DOMImplementation instance */
	duk_push_this(ctx);
	duk_get_prop_string(ctx, -1, MAGIC(DOMImplementation));
	if (duk_is_undefined(ctx, -1)) {
		duk_pop(ctx);

		if (dukky_create_object(ctx,
					PROTO_NAME(DOMIMPLEMENTATION),
					0) != DUK_EXEC_SUCCESS) {
			return duk_error(ctx,
				  DUK_ERR_ERROR,
				  "Unable to create dom implementation object");
		}
		duk_dup(ctx, -1);
		duk_put_prop_string(ctx, -3, MAGIC(DOMImplementation));
	}
	return 1;
%}

getter Document::onabort();
setter Document::onabort();
getter Document::onautocompleteerror();
setter Document::onautocompleteerror();
getter Document::onautocomplete();
setter Document::onautocomplete();
getter Document::onblur();
setter Document::onblur();
getter Document::oncancel();
setter Document::oncancel();
getter Document::oncanplaythrough();
setter Document::oncanplaythrough();
getter Document::oncanplay();
setter Document::oncanplay();
getter Document::onchange();
setter Document::onchange();
getter Document::onclick();
setter Document::onclick();
getter Document::onclose();
setter Document::onclose();
getter Document::oncontextmenu();
setter Document::oncontextmenu();
getter Document::oncuechange();
setter Document::oncuechange();
getter Document::ondblclick();
setter Document::ondblclick();
getter Document::ondragend();
setter Document::ondragend();
getter Document::ondragenter();
setter Document::ondragenter();
getter Document::ondragexit();
setter Document::ondragexit();
getter Document::ondragleave();
setter Document::ondragleave();
getter Document::ondragover();
setter Document::ondragover();
getter Document::ondragstart();
setter Document::ondragstart();
getter Document::ondrag();
setter Document::ondrag();
getter Document::ondrop();
setter Document::ondrop();
getter Document::ondurationchange();
setter Document::ondurationchange();
getter Document::onemptied();
setter Document::onemptied();
getter Document::onended();
setter Document::onended();
getter Document::onfocus();
setter Document::onfocus();
getter Document::oninput();
setter Document::oninput();
getter Document::oninvalid();
setter Document::oninvalid();
getter Document::onkeydown();
setter Document::onkeydown();
getter Document::onkeypress();
setter Document::onkeypress();
getter Document::onkeyup();
setter Document::onkeyup();
getter Document::onloadeddata();
setter Document::onloadeddata();
getter Document::onloadedmetadata();
setter Document::onloadedmetadata();
getter Document::onloadstart();
setter Document::onloadstart();
getter Document::onload();
setter Document::onload();
getter Document::onmousedown();
setter Document::onmousedown();
getter Document::onmouseenter();
setter Document::onmouseenter();
getter Document::onmouseleave();
setter Document::onmouseleave();
getter Document::onmousemove();
setter Document::onmousemove();
getter Document::onmouseout();
setter Document::onmouseout();
getter Document::onmouseover();
setter Document::onmouseover();
getter Document::onmouseup();
setter Document::onmouseup();
getter Document::onpause();
setter Document::onpause();
getter Document::onplaying();
setter Document::onplaying();
getter Document::onplay();
setter Document::onplay();
getter Document::onprogress();
setter Document::onprogress();
getter Document::onratechange();
setter Document::onratechange();
getter Document::onreadystatechange();
setter Document::onreadystatechange();
getter Document::onreset();
setter Document::onreset();
getter Document::onresize();
setter Document::onresize();
getter Document::onscroll();
setter Document::onscroll();
getter Document::onseeked();
setter Document::onseeked();
getter Document::onseeking();
setter Document::onseeking();
getter Document::onselect();
setter Document::onselect();
getter Document::onshow();
setter Document::onshow();
getter Document::onsort();
setter Document::onsort();
getter Document::onstalled();
setter Document::onstalled();
getter Document::onsubmit();
setter Document::onsubmit();
getter Document::onsuspend();
setter Document::onsuspend();
getter Document::ontimeupdate();
setter Document::ontimeupdate();
getter Document::ontoggle();
setter Document::ontoggle();
getter Document::onvolumechange();
setter Document::onvolumechange();
getter Document::onwaiting();
setter Document::onwaiting();
getter Document::onwheel();
setter Document::onwheel();
