/** View state manager
 * @file */

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

#include <stdlib.h>
#include <string.h>

#include "elinks.h"

#include "document/document.h"
#include "document/view.h"
#ifdef CONFIG_ECMASCRIPT
#include "js/ecmascript-c.h"
#endif
#include "protocol/uri.h"
#ifdef CONFIG_SCRIPTING_SPIDERMONKEY
# include "scripting/smjs/smjs.h"
#endif
#include "session/location.h"
#include "session/session.h"
#include "util/memory.h"
#include "util/string.h"
#include "viewer/text/form.h"
#include "viewer/text/link.h"
#include "viewer/text/view.h"
#include "viewer/text/vs.h"

/** @relates view_state */
void
init_vs(struct view_state *vs, struct uri *uri, int plain)
{
	ELOG
	memset(vs, 0, sizeof(*vs));
	vs->current_link = -1;
	vs->old_current_link = -1;
	vs->current_search_number = -1;
	vs->plain = plain;
	vs->wrap = get_opt_bool("document.browse.wrap", NULL);
	vs->uri = uri ? get_uri_reference(uri) : NULL;
	vs->did_fragment = !uri->fragmentlen;
#if defined(CONFIG_ECMASCRIPT_SMJS) || defined(CONFIG_QUICKJS) || defined(CONFIG_MUJS)
#ifdef CONFIG_QUICKJS
	vs->win_obj = JS_NULL;
	vs->location_obj = JS_NULL;
#endif
	/* If we ever get to render this vs, give it an interpreter. */
	vs->ecmascript_fragile = 1;
#endif
	init_list(vs->forms);
}

/** @relates view_state */
void
destroy_vs(struct view_state *vs, int blast_ecmascript)
{
	ELOG
	struct form_view *fv, *next;

#ifdef CONFIG_SCRIPTING_SPIDERMONKEY
	smjs_detach_view_state_object(vs);
#endif

	/* form_state contains a pointer to form_view, so it's safest
	 * to delete the form_state first.  */
	for (; vs->form_info_len > 0; vs->form_info_len--)
		done_form_state(&vs->form_info[vs->form_info_len - 1]);
	mem_free_set(&vs->form_info, NULL);

	foreachsafe (fv, next, vs->forms) {
		del_from_list(fv);
		done_form_view(fv);
	}
	
	if (vs->uri) done_uri(vs->uri);
#if defined(CONFIG_ECMASCRIPT_SMJS) || defined(CONFIG_QUICKJS) || defined(CONFIG_MUJS)
#ifdef CONFIG_ECMASCRIPT_SMJS
	detach_js_view_state(vs);
#endif

#ifdef CONFIG_QUICKJS
	detach_js_view_state(vs);
#endif

	if (blast_ecmascript && vs->ecmascript) {
		ecmascript_put_interpreter(vs->ecmascript);
	}
#endif
	if (vs->doc_view) {
		vs->doc_view->vs = NULL;
		vs->doc_view = NULL;
	}
}

/** @relates view_state */
void
copy_vs(struct view_state *dst, struct view_state *src)
{
	ELOG
	struct form_view *fv;

	copy_struct(dst, src);

	/* We do not copy ecmascript stuff around since it's specific for
	 * a single location, offsprings (followups and so) nedd their own. */
#if defined(CONFIG_ECMASCRIPT_SMJS) || defined(CONFIG_QUICKJS) || defined(CONFIG_MUJS)
#ifdef CONFIG_ECMASCRIPT_SMJS
	dst->winobject = NULL;
	dst->locobject = NULL;
#endif
#ifdef CONFIG_QUICKJS
	dst->win_obj = JS_NULL;
	dst->location_obj = JS_NULL;
#endif
	dst->ecmascript = NULL;
	/* If we ever get to render this vs, give it an interpreter. */
	dst->ecmascript_fragile = 1;
#endif
#ifdef CONFIG_SCRIPTING_SPIDERMONKEY
	dst->jsobject = NULL;
#endif

	/* destroy_vs(vs) does mem_free_if(vs->form_info), so each
	 * view_state must have its own form_info.  Normally we make a
	 * copy below, but not if src->form_info_len is 0, which it
	 * can be even if src->form_info is not NULL.  */
	dst->form_info = NULL;

	/* Clean as a baby. */
	dst->doc_view = NULL;

	dst->uri = get_uri_reference(src->uri);
	/* Redo fragment if there is one? */
	dst->did_fragment = !src->uri->fragmentlen;

	init_list(dst->forms);
	foreach (fv, src->forms) {
		struct form_view *newfv = (struct form_view *)mem_calloc(1, sizeof(*newfv));

		if (!newfv) continue;
		newfv->form_num = fv->form_num;
		/* We do leave out the ECMAScript object intentionally. */
		add_to_list(dst->forms, newfv);
	}

	if (src->form_info_len) {
		dst->form_info = (struct form_state *)mem_alloc(src->form_info_len
					   * sizeof(*src->form_info));
		if (dst->form_info) {
			int i;

			memcpy(dst->form_info, src->form_info,
			       src->form_info_len * sizeof(*src->form_info));
			for (i = 0; i < src->form_info_len; i++) {
				struct form_state *srcfs = &src->form_info[i];
				struct form_state *dstfs = &dst->form_info[i];

#ifdef CONFIG_ECMASCRIPT_SMJS
				dstfs->ecmascript_obj = NULL;
#endif
#ifdef CONFIG_QUICKJS
				dstfs->ecmascript_obj = JS_NULL;
#endif
				if (srcfs->value)
					dstfs->value = stracpy(srcfs->value);
				/* XXX: This makes it O(nm). */
				dstfs->form_view =
					srcfs->form_view
					? find_form_view_in_vs(dst, srcfs->form_view->form_num)
					: NULL;
			}
		}
	}
}

void
check_vs(struct document_view *doc_view)
{
	ELOG
	struct view_state *vs = doc_view->vs;

	int_upper_bound(&vs->current_link, doc_view->document->nlinks - 1);

	if (vs->current_link != -1) {
		if (!current_link_is_visible(doc_view)) {
			struct link *links = doc_view->document->links;

			set_pos_x(doc_view, &links[vs->current_link]);
			set_pos_y(doc_view, &links[vs->current_link]);
		}
	} else {
		find_link_page_down(doc_view);
	}
}

void
next_frame(struct session *ses, int p)
{
	ELOG
	struct view_state *vs;
	struct document_view *doc_view;
	int n;

	if (!have_location(ses)
	    || (ses->doc_view && !document_has_frames(ses->doc_view->document))) {
		return;
	}

	ses->navigate_mode = NAVIGATE_LINKWISE;

	vs = &cur_loc(ses)->vs;

	n = 0;
	foreach (doc_view, ses->scrn_frames) {
		if (!document_has_frames(doc_view->document))
			n++;
	}

	vs->current_link += p;
	if (!n) n = 1;
	while (vs->current_link < 0) vs->current_link += n;
	vs->current_link %= n;
}
