/* HTML viewer (and much more) */

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

#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "elinks.h"

#include "bfu/leds.h"
#include "bfu/menu.h"
#include "bfu/dialog.h"
#include "config/kbdbind.h"
#include "config/options.h"
#include "dialogs/document.h"
#include "dialogs/menu.h"
#include "dialogs/options.h"
#include "dialogs/status.h"
#include "document/document.h"
#include "document/html/frames.h"
#include "document/options.h"
#include "document/renderer.h"
#include "document/view.h"
#include "intl/charsets.h"
#include "intl/gettext/libintl.h"
#include "main/event.h"
#include "osdep/osdep.h"
#include "protocol/uri.h"
#include "session/download.h"
#include "session/location.h"
#include "session/session.h"
#include "session/task.h"
#include "terminal/draw.h"
#include "terminal/event.h"
#include "terminal/kbd.h"
#include "terminal/mouse.h"
#include "terminal/tab.h"
#include "terminal/terminal.h"
#include "terminal/window.h"
#include "util/color.h"
#include "util/conv.h"
#include "util/error.h"
#include "util/memory.h"
#include "util/snprintf.h"
#include "util/string.h"
#include "viewer/action.h"
#include "viewer/dump/dump.h"
#include "viewer/text/draw.h"
#include "viewer/text/form.h"
#include "viewer/text/link.h"
#include "viewer/text/marks.h"
#include "viewer/text/search.h"
#include "viewer/text/textarea.h"
#include "viewer/text/view.h"
#include "viewer/text/vs.h"



void
detach_formatted(struct document_view *doc_view)
{
	assert(doc_view);
	if_assert_failed return;

	if (doc_view->document) {
		release_document(doc_view->document);
		doc_view->document = NULL;
	}
	if (doc_view->vs) {
		doc_view->vs->doc_view = NULL;
		doc_view->vs = NULL;
	}
	if (doc_view->link_bg) free_link(doc_view);
	mem_free_set(&doc_view->name, NULL);
}

/* type == 0 -> PAGE_DOWN
 * type == 1 -> DOWN */
static enum frame_event_status
move_down(struct session *ses, struct document_view *doc_view, int type)
{
	int newpos;

	assert(ses && doc_view && doc_view->vs);
	if_assert_failed return FRAME_EVENT_OK;

	assert(ses->navigate_mode == NAVIGATE_LINKWISE);	/* XXX: drop it at some time. --Zas */

	newpos = doc_view->vs->y + doc_view->box.height;
	if (newpos < doc_view->document->height)
		doc_view->vs->y = newpos;

	if (current_link_is_visible(doc_view))
		return FRAME_EVENT_REFRESH;

	if (type)
		find_link_down(doc_view);
	else
		find_link_page_down(doc_view);

	return FRAME_EVENT_REFRESH;
}

enum frame_event_status
move_page_down(struct session *ses, struct document_view *doc_view)
{
	enum frame_event_status status;
	int count = eat_kbd_repeat_count(ses);

	ses->navigate_mode = NAVIGATE_LINKWISE;

	do {
		status = move_down(ses, doc_view, 0);
		if (status != FRAME_EVENT_REFRESH) break;
	} while (--count > 0);

	return status;
}

/* type == 0 -> PAGE_UP
 * type == 1 -> UP */
static enum frame_event_status
move_up(struct session *ses, struct document_view *doc_view, int type)
{
	assert(ses && doc_view && doc_view->vs);
	if_assert_failed return FRAME_EVENT_OK;

	assert(ses->navigate_mode == NAVIGATE_LINKWISE);	/* XXX: drop it at some time. --Zas */

	if (doc_view->vs->y == 0) return FRAME_EVENT_OK;

	doc_view->vs->y -= doc_view->box.height;
	int_lower_bound(&doc_view->vs->y, 0);

	if (current_link_is_visible(doc_view))
		return FRAME_EVENT_REFRESH;

	if (type)
		find_link_up(doc_view);
	else
		find_link_page_up(doc_view);

	return FRAME_EVENT_REFRESH;
}

enum frame_event_status
move_page_up(struct session *ses, struct document_view *doc_view)
{
	enum frame_event_status status;
	int count = eat_kbd_repeat_count(ses);

	ses->navigate_mode = NAVIGATE_LINKWISE;

	do {
		status = move_up(ses, doc_view, 0);
		if (status != FRAME_EVENT_REFRESH) break;
	} while (--count > 0);

	return status;
}

enum frame_event_status
move_link(struct session *ses, struct document_view *doc_view, int direction,
	  int wraparound_bound, int wraparound_link)
{
	int wraparound = 0;
	int count;

	assert(ses && doc_view && doc_view->vs && doc_view->document);
	if_assert_failed return FRAME_EVENT_OK;

	ses->navigate_mode = NAVIGATE_LINKWISE;

	if (doc_view->document->nlinks < 2) {
		/* Wraparound only makes sense with more than one link. */
		wraparound_bound = -1;
	} else {
		/* We only bother this option if there's some links
		 * in document. */
		wraparound = get_opt_bool("document.browse.links.wraparound");
	}

	count = eat_kbd_repeat_count(ses);

	do {
		int current_link = doc_view->vs->current_link;

		if (current_link == wraparound_bound) {
			if (wraparound) {
				jump_to_link_number(ses, doc_view, wraparound_link);
				/* FIXME: This needs further work, we should call
				 * page_down() and set_textarea() under some conditions
				 * as well. --pasky */
				continue;
			}

		} else {
			if (next_link_in_view_y(doc_view, current_link + direction,
					        direction))
				continue;
		}

		/* This is a work around for the case where the index of
		 * @wraparound_bound is not necessarily the index of the first
		 * or the last link in the view. It means that the link moving
		 * could end up calling next_link_in_view() in the condition
		 * above. This is bad because next_link_in_view() will then
		 * 'reset' doc_view->vs->current_link to -1 and the effect will
		 * be that the current link will 'wrap around'. By restoring
		 * the index of the @current_link nothing will be wrapped
		 * around and move_{up,down} will take care of finding the next
		 * link. */
		doc_view->vs->current_link = current_link;

		if (direction > 0) {
			move_down(ses, doc_view, 1);
		} else {
			move_up(ses, doc_view, 1);
		}

		if (current_link != wraparound_bound
		    && current_link != doc_view->vs->current_link) {
			set_textarea(doc_view, -direction);
		}
	} while (--count > 0);

	return FRAME_EVENT_REFRESH;
}

enum frame_event_status
move_link_dir(struct session *ses, struct document_view *doc_view, int dir_x, int dir_y)
{
	int count;

	assert(ses && doc_view && doc_view->vs && doc_view->document);
	if_assert_failed return FRAME_EVENT_OK;

	ses->navigate_mode = NAVIGATE_LINKWISE;

	count = eat_kbd_repeat_count(ses);

	do {
		int current_link = doc_view->vs->current_link;

		if (next_link_in_dir(doc_view, dir_x, dir_y))
			continue;

		/* FIXME: This won't preserve the column! */
		if (dir_y > 0)
			move_down(ses, doc_view, 1);
		else if (dir_y < 0)
			move_up(ses, doc_view, 1);

		if (dir_y && current_link != doc_view->vs->current_link) {
			set_textarea(doc_view, -dir_y);
		}
	} while (--count > 0);

	return FRAME_EVENT_REFRESH;
}

/* @steps > 0 -> down */
static enum frame_event_status
vertical_scroll(struct session *ses, struct document_view *doc_view, int steps)
{
	int y;

	assert(ses && doc_view && doc_view->vs && doc_view->document);
	if_assert_failed return FRAME_EVENT_OK;

	y = doc_view->vs->y + steps;
	if (steps > 0) {
		/* DOWN */
		int max_height = doc_view->document->height - doc_view->box.height;

		if (doc_view->vs->y >= max_height) return FRAME_EVENT_OK;
		int_upper_bound(&y, max_height);
	}

	int_lower_bound(&y, 0);

	if (doc_view->vs->y == y) return FRAME_EVENT_OK;

	doc_view->vs->y = y;

	if (current_link_is_visible(doc_view))
		return FRAME_EVENT_REFRESH;

	if (steps > 0)
		find_link_page_down(doc_view);
	else
		find_link_page_up(doc_view);

	return FRAME_EVENT_REFRESH;
}

/* @steps > 0 -> right */
static enum frame_event_status
horizontal_scroll(struct session *ses, struct document_view *doc_view, int steps)
{
	int x, max;

	assert(ses && doc_view && doc_view->vs && doc_view->document);
	if_assert_failed return FRAME_EVENT_OK;

	x = doc_view->vs->x + steps;

	if (get_opt_bool("document.browse.scrolling.horizontal_extended")) {
		max = doc_view->document->width - 1;
	} else {
		max = int_max(doc_view->vs->x,
			      doc_view->document->width - doc_view->box.width);
	}

	int_bounds(&x, 0, max);
	if (doc_view->vs->x == x) return FRAME_EVENT_OK;

	doc_view->vs->x = x;

	if (current_link_is_visible(doc_view))
		return FRAME_EVENT_REFRESH;

	find_link_page_down(doc_view);

	return FRAME_EVENT_REFRESH;
}

enum frame_event_status
scroll_up(struct session *ses, struct document_view *doc_view)
{
	int steps = eat_kbd_repeat_count(ses);

	if (!steps)
		steps = get_opt_int("document.browse.scrolling.vertical_step");

	return vertical_scroll(ses, doc_view, -steps);
}

enum frame_event_status
scroll_down(struct session *ses, struct document_view *doc_view)
{
	int steps = eat_kbd_repeat_count(ses);

	if (!steps)
		steps = get_opt_int("document.browse.scrolling.vertical_step");

	return vertical_scroll(ses, doc_view, steps);
}

enum frame_event_status
scroll_left(struct session *ses, struct document_view *doc_view)
{
	int steps = eat_kbd_repeat_count(ses);

	if (!steps)
		steps = get_opt_int("document.browse.scrolling.horizontal_step");

	return horizontal_scroll(ses, doc_view, -steps);
}

enum frame_event_status
scroll_right(struct session *ses, struct document_view *doc_view)
{
	int steps = eat_kbd_repeat_count(ses);

	if (!steps)
		steps = get_opt_int("document.browse.scrolling.horizontal_step");

	return horizontal_scroll(ses, doc_view, steps);
}

#ifdef CONFIG_MOUSE
static enum frame_event_status
scroll_mouse_up(struct session *ses, struct document_view *doc_view)
{
	int steps = get_opt_int("document.browse.scrolling.vertical_step");

	return vertical_scroll(ses, doc_view, -steps);
}

static enum frame_event_status
scroll_mouse_down(struct session *ses, struct document_view *doc_view)
{
	int steps = get_opt_int("document.browse.scrolling.vertical_step");

	return vertical_scroll(ses, doc_view, steps);
}

static enum frame_event_status
scroll_mouse_left(struct session *ses, struct document_view *doc_view)
{
	int steps = get_opt_int("document.browse.scrolling.horizontal_step");

	return horizontal_scroll(ses, doc_view, -steps);
}

static enum frame_event_status
scroll_mouse_right(struct session *ses, struct document_view *doc_view)
{
	int steps = get_opt_int("document.browse.scrolling.horizontal_step");

	return horizontal_scroll(ses, doc_view, steps);
}
#endif /* CONFIG_MOUSE */

enum frame_event_status
move_document_start(struct session *ses, struct document_view *doc_view)
{
	assert(ses && doc_view && doc_view->vs);
	if_assert_failed return FRAME_EVENT_OK;

	doc_view->vs->y = doc_view->vs->x = 0;

	if (ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
		/* Move to the first line and the first column. */
		move_cursor(ses, doc_view, doc_view->box.x, doc_view->box.y);
	} else {
		find_link_page_down(doc_view);
	}

	return FRAME_EVENT_REFRESH;
}

enum frame_event_status
move_document_end(struct session *ses, struct document_view *doc_view)
{
	int max_height;

	assert(ses && doc_view && doc_view->vs && doc_view->document);
	if_assert_failed return FRAME_EVENT_OK;

	max_height = doc_view->document->height - doc_view->box.height;
	doc_view->vs->x = 0;
	int_lower_bound(&doc_view->vs->y, int_max(0, max_height));

	if (ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
		/* Move to the last line of the document,
		 * but preserve the column. This is done to avoid
		 * moving the cursor backwards if it is already
		 * on the last line but is not on the first column. */
		move_cursor(ses, doc_view, ses->tab->x,
			    doc_view->document->height - doc_view->vs->y);
	} else {
		find_link_page_up(doc_view);
	}

	return FRAME_EVENT_REFRESH;
}

enum frame_event_status
set_frame(struct session *ses, struct document_view *doc_view, int xxxx)
{
	assert(ses && ses->doc_view && doc_view && doc_view->vs);
	if_assert_failed return FRAME_EVENT_OK;

	if (doc_view == ses->doc_view) return FRAME_EVENT_OK;
	goto_uri(ses, doc_view->vs->uri);
	ses->navigate_mode = NAVIGATE_LINKWISE;

	return FRAME_EVENT_OK;
}


void
toggle_plain_html(struct session *ses, struct document_view *doc_view, int xxxx)
{
	assert(ses && doc_view && ses->tab && ses->tab->term);
	if_assert_failed return;

	if (!doc_view->vs) {
		nowhere_box(ses->tab->term, NULL);
		return;
	}

	doc_view->vs->plain = !doc_view->vs->plain;
	draw_formatted(ses, 1);
}

void
toggle_wrap_text(struct session *ses, struct document_view *doc_view, int xxxx)
{
	assert(ses && doc_view && ses->tab && ses->tab->term);
	if_assert_failed return;

	if (!doc_view->vs) {
		nowhere_box(ses->tab->term, NULL);
		return;
	}

	doc_view->vs->wrap = !doc_view->vs->wrap;
	draw_formatted(ses, 1);
}

/* Move the cursor to the document view co-ordinates provided as @x and @y,
 * scroll the document if necessary, put us in cursor-routing navigation mode if
 * that is not the current mode, and select any link under the cursor. */
enum frame_event_status
move_cursor(struct session *ses, struct document_view *doc_view, int x, int y)
{
	enum frame_event_status status = FRAME_EVENT_REFRESH;
	struct terminal *term = ses->tab->term;
	struct box *box = &doc_view->box;
	struct link *link;

	/* If cursor was moved outside the document view scroll it, but only
	 * within the document canvas */
	if (!is_in_box(box, x, y)) {
		int max_height = doc_view->document->height - doc_view->vs->y;
		int max_width = doc_view->document->width - doc_view->vs->x;

		if (y < box->y) {
			status = vertical_scroll(ses, doc_view, y - box->y);

		} else if (y >= box->y + box->height && y <= max_height) {
			status = vertical_scroll(ses, doc_view,
						 y - (box->y + box->height - 1));

		} else if (x < box->x) {
			status = horizontal_scroll(ses, doc_view, x - box->x);

		} else if (x >= box->x + box->width && x <= max_width) {
			status = horizontal_scroll(ses, doc_view,
						   x - (box->x + box->width - 1));
		}

		/* If the view was not scrolled there's nothing more to do */
		if (status != FRAME_EVENT_REFRESH)
			return status;

		/* Restrict the cursor position within the current view */
		int_bounds(&x, box->x, box->x + box->width - 1);
		int_bounds(&y, box->y, box->y + box->height - 1);
	}

	/* Scrolling could have changed the navigation mode */
	ses->navigate_mode = NAVIGATE_CURSOR_ROUTING;

	link = get_link_at_coordinates(doc_view, x - box->x, y - box->y);
	if (link) {
		doc_view->vs->current_link = link - doc_view->document->links;
	} else {
		doc_view->vs->current_link = -1;
	}

	/* Set the unblockable cursor position and update the window pointer so
	 * stuff like the link menu will be drawn relative to the cursor. */
	set_cursor(term, x, y, 0);
	set_window_ptr(ses->tab, x, y);

	return status;
}

enum frame_event_status
move_cursor_rel(struct session *ses, struct document_view *view,
	        int rx, int ry)
{
	int count = eat_kbd_repeat_count(ses);
	int x, y;

	int_lower_bound(&count, 1);

	x = ses->tab->x + rx*count;
	y = ses->tab->y + ry*count;
	return move_cursor(ses, view, x, y);
}

enum frame_event_status
move_cursor_left(struct session *ses, struct document_view *view)
{
	return move_cursor_rel(ses, view, -1, 0);
}

enum frame_event_status
move_cursor_right(struct session *ses, struct document_view *view)
{
	return move_cursor_rel(ses, view, 1, 0);
}

enum frame_event_status
move_cursor_up(struct session *ses, struct document_view *view)
{
	return move_cursor_rel(ses, view, 0, -1);
}

enum frame_event_status
move_cursor_down(struct session *ses, struct document_view *view)
{
	return move_cursor_rel(ses, view, 0, 1);
}


enum frame_event_status
copy_current_link_to_clipboard(struct session *ses,
			       struct document_view *doc_view,
			       int xxx)
{
	struct link *link;
	struct uri *uri;
	unsigned char *uristring;

	link = get_current_link(doc_view);
	if (!link) return FRAME_EVENT_OK;

	uri = get_link_uri(ses, doc_view, link);
	if (!uri) return FRAME_EVENT_OK;

	uristring = get_uri_string(uri, URI_ORIGINAL);
	done_uri(uri);

	if (uristring) {
		set_clipboard_text(uristring);
		mem_free(uristring);
	}

	return FRAME_EVENT_OK;
}


int
try_jump_to_link_number(struct session *ses, struct document_view *doc_view)
{
	int link_number = eat_kbd_repeat_count(ses) - 1;

	if (link_number < 0) return 1;

	if (!doc_view) return 0;

	if (link_number >= doc_view->document->nlinks)
		return 0;

	jump_to_link_number(ses, doc_view, link_number);
	refresh_view(ses, doc_view, 0);

	return 1;
}

#ifdef CONFIG_MARKS
enum frame_event_status
try_mark_key(struct session *ses, struct document_view *doc_view,
	     struct term_event *ev)
{
	unsigned char mark = get_kbd_key(ev);

	switch (ses->kbdprefix.mark) {
		case KP_MARK_NOTHING:
			return FRAME_EVENT_IGNORED;

		case KP_MARK_SET:
			/* It is intentional to set the mark
			 * to NULL if !doc_view->vs. */
			set_mark(mark, doc_view->vs);
			break;

		case KP_MARK_GOTO:
			goto_mark(mark, doc_view->vs);
			break;
	}

	ses->kbdprefix.repeat_count = 0;
	ses->kbdprefix.mark = KP_MARK_NOTHING;

	return FRAME_EVENT_REFRESH;
}
#endif

static enum frame_event_status
try_prefix_key(struct session *ses, struct document_view *doc_view,
	       struct term_event *ev)
{
	struct document *document = doc_view->document;
	struct document_options *doc_opts = &document->options;
	int digit = get_kbd_key(ev) - '0';

	if (digit < 0 || digit > 9)
		return FRAME_EVENT_IGNORED;

	if (get_kbd_modifier(ev)
	    || ses->kbdprefix.repeat_count /* The user has already begun
	                                    * entering a prefix. */
	    || !doc_opts->num_links_key
	    || (doc_opts->num_links_key == 1 && !doc_opts->links_numbering)) {
		/* Repeat count.
		 * ses->kbdprefix.repeat_count is initialized to zero
		 * the first time by init_session() calloc() call.
		 * When used, it has to be reset to zero. */

		ses->kbdprefix.repeat_count *= 10;
		ses->kbdprefix.repeat_count += digit;

		/* If too big, just restart from zero, so pressing
		 * '0' six times or more will reset the count. */
		if (ses->kbdprefix.repeat_count > 99999)
			ses->kbdprefix.repeat_count = 0;

		return FRAME_EVENT_OK;
	}

	if (digit >= 1 && !get_kbd_modifier(ev)) {
		int nlinks = document->nlinks, length;
		unsigned char d[2] = { get_kbd_key(ev), 0 };

		ses->kbdprefix.repeat_count = 0;

		if (!nlinks) return FRAME_EVENT_OK;

		for (length = 1; nlinks; nlinks /= 10)
			length++;

		input_dialog(ses->tab->term, NULL,
			     N_("Go to link"), N_("Enter link number"),
			     ses, NULL,
			     length, d, 1, document->nlinks, check_number,
			     (void (*)(void *, unsigned char *)) goto_link_number, NULL);

		return FRAME_EVENT_OK;
	}

	return FRAME_EVENT_IGNORED;
}

static enum frame_event_status
try_form_insert_mode(struct session *ses, struct document_view *doc_view,
		     struct link *link, struct term_event *ev)
{
	enum frame_event_status status = FRAME_EVENT_IGNORED;
	enum edit_action action_id;

	if (!link_is_textinput(link))
		return FRAME_EVENT_IGNORED;

	action_id = kbd_action(KEYMAP_EDIT, ev, NULL);

	if (ses->insert_mode == INSERT_MODE_OFF) {
		if (action_id == ACT_EDIT_ENTER) {
			ses->insert_mode = INSERT_MODE_ON;
			status = FRAME_EVENT_REFRESH;
		}
	}

	return status;
}

static enum frame_event_status
try_form_action(struct session *ses, struct document_view *doc_view,
		struct link *link, struct term_event *ev)
{
	enum frame_event_status status;

	assert(link);

	if (!link_is_textinput(link))
		return FRAME_EVENT_IGNORED;

	status = field_op(ses, doc_view, link, ev);

	if (status != FRAME_EVENT_IGNORED
	    && ses->insert_mode == INSERT_MODE_ON) {
		assert(link == get_current_link(doc_view));
	}

	return status;
}

static enum frame_event_status
frame_ev_kbd(struct session *ses, struct document_view *doc_view, struct term_event *ev)
{
	enum frame_event_status status = FRAME_EVENT_IGNORED;
	int accesskey_priority;
	struct link *link = get_current_link(doc_view);

	if (link) {
		status = try_form_insert_mode(ses, doc_view, link, ev);
		if (status != FRAME_EVENT_IGNORED)
			return status;

		status = try_form_action(ses, doc_view, link, ev);
		if (status != FRAME_EVENT_IGNORED)
			return status;
	}

#ifdef CONFIG_MARKS
	status = try_mark_key(ses, doc_view, ev);
	if (status != FRAME_EVENT_IGNORED)
		return status;
#endif
	accesskey_priority = get_opt_int("document.browse.accesskey.priority");

	if (accesskey_priority >= 2) {
		status = try_document_key(ses, doc_view, ev);

		if (status != FRAME_EVENT_IGNORED) {
			/* The document ate the key! */
			return status;
		}
	}

	status = try_prefix_key(ses, doc_view, ev);
	if (status != FRAME_EVENT_IGNORED)
		return status;

	if (accesskey_priority == 1) {
		status = try_document_key(ses, doc_view, ev);

		if (status != FRAME_EVENT_IGNORED) {
			/* The document ate the key! */
			return status;
		}
	}

	return FRAME_EVENT_IGNORED;
}

#ifdef CONFIG_MOUSE
static enum frame_event_status
frame_ev_mouse(struct session *ses, struct document_view *doc_view, struct term_event *ev)
{
	int x = ev->info.mouse.x;
	int y = ev->info.mouse.y;
	struct link *link;

	if (check_mouse_wheel(ev)) {
		if (!check_mouse_action(ev, B_DOWN)) {
			/* We handle only B_DOWN case... */
		} else if (check_mouse_button(ev, B_WHEEL_UP)) {
			return scroll_mouse_up(ses, doc_view);
		} else if (check_mouse_button(ev, B_WHEEL_DOWN)) {
			return scroll_mouse_down(ses, doc_view);
		}

		return FRAME_EVENT_OK;
	}

	link = get_link_at_coordinates(doc_view, x, y);
	if (link) {
		enum frame_event_status status = FRAME_EVENT_REFRESH;

		doc_view->vs->current_link = link - doc_view->document->links;

		if (!link_is_textinput(link)) {

			status = FRAME_EVENT_OK;

			refresh_view(ses, doc_view, 0);

			if (check_mouse_button(ev, B_LEFT)
			     || check_mouse_button(ev, B_MIDDLE)) {
				if (check_mouse_action(ev, B_DOWN))
					do_not_ignore_next_mouse_event(ses->tab->term);
				else if (check_mouse_button(ev, B_LEFT))
					status = enter(ses, doc_view, 0);
				else if (check_mouse_button(ev, B_MIDDLE))
					open_current_link_in_new_tab(ses, 1);
			} else {
				link_menu(ses->tab->term, NULL, ses);
			}
		}

		return status;
	}

	if (check_mouse_button(ev, B_LEFT)) {
		/* Clicking the edge of screen will scroll the document. */

		int scrollmargin = get_opt_int("document.browse.scrolling.margin");

		/* XXX: This is code duplication with kbd handlers. But
		 * repeatcount-free here. */

		if (y < scrollmargin) {
			return scroll_mouse_up(ses, doc_view);
		}
		if (y >= doc_view->box.height - scrollmargin) {
			return scroll_mouse_down(ses, doc_view);
		}

		if (x < scrollmargin * 2) {
			return scroll_mouse_left(ses, doc_view);
		}
		if (x >= doc_view->box.width - scrollmargin * 2) {
			return scroll_mouse_right(ses, doc_view);
		}

		return FRAME_EVENT_OK;
	}

	return FRAME_EVENT_IGNORED;
}
#endif /* CONFIG_MOUSE */

static enum frame_event_status
frame_ev(struct session *ses, struct document_view *doc_view, struct term_event *ev)
{
	assertm(doc_view && doc_view->document, "document not formatted");
	if_assert_failed return FRAME_EVENT_IGNORED;

	assert(ses && ev);
	if_assert_failed return FRAME_EVENT_IGNORED;

	/* When changing frame, vs may be NULL. See bug 525. */
	if (!doc_view->vs) return FRAME_EVENT_IGNORED;

	switch (ev->ev) {
	case EVENT_KBD:
		return frame_ev_kbd(ses, doc_view, ev);
#ifdef CONFIG_MOUSE
	case EVENT_MOUSE:
		return frame_ev_mouse(ses, doc_view, ev);
#endif /* CONFIG_MOUSE */
	default:
		return FRAME_EVENT_IGNORED;
	}
}

struct document_view *
current_frame(struct session *ses)
{
	struct document_view *doc_view = NULL;
	int current_frame_number;

	assert(ses);
	if_assert_failed return NULL;

	if (!have_location(ses)) return NULL;

	current_frame_number = cur_loc(ses)->vs.current_link;
	if (current_frame_number == -1) current_frame_number = 0;

	foreach (doc_view, ses->scrn_frames) {
		if (document_has_frames(doc_view->document)) continue;
		if (!current_frame_number--) return doc_view;
	}

	doc_view = ses->doc_view;

	assert(doc_view && doc_view->document);
	if_assert_failed return NULL;

	if (document_has_frames(doc_view->document)) return NULL;
	return doc_view;
}

static enum frame_event_status
send_to_frame(struct session *ses, struct document_view *doc_view,
	      struct term_event *ev)
{
	enum frame_event_status status;

	assert(ses && ses->tab && ses->tab->term && ev);
	if_assert_failed return FRAME_EVENT_IGNORED;

	status = frame_ev(ses, doc_view, ev);

	if (status == FRAME_EVENT_REFRESH)
		refresh_view(ses, doc_view, 0);
	else
		print_screen_status(ses);

	return status;
}

#ifdef CONFIG_MOUSE
static int
do_mouse_event(struct session *ses, struct term_event *ev,
	       struct document_view *doc_view)
{
	struct term_event evv;
	struct document_view *matched = NULL, *first = doc_view;

	assert(ses && ev);
	if_assert_failed return 0;

	if (!doc_view) return 0;

	do {
		assert(doc_view && doc_view->document);
		if_assert_failed return 0;

		assertm(doc_view->document->options.box.x == doc_view->box.x
		        && doc_view->document->options.box.y == doc_view->box.y,
			"Jonas' 1.565 -> 1.566 patch sucks");
		if_assert_failed return 0;

		if (check_mouse_position(ev, &doc_view->box)) {
			matched = doc_view;
			break;
		}

		next_frame(ses, 1);
		doc_view = current_frame(ses);

	} while (doc_view != first);

	if (!matched) return 0;

	if (doc_view != first) draw_formatted(ses, 0);

	set_mouse_term_event(&evv,
			     ev->info.mouse.x - doc_view->box.x,
			     ev->info.mouse.y - doc_view->box.y,
			     ev->info.mouse.button);

	return send_to_frame(ses, doc_view, &evv);
}

/* Returns the session if event cleanup should be done or NULL if no cleanup is
 * needed. */
static struct session *
send_mouse_event(struct session *ses, struct document_view *doc_view,
		 struct term_event *ev)
{
	struct terminal *term = ses->tab->term;
	struct term_event_mouse *mouse = &ev->info.mouse;

	if (mouse->y == 0
	    && check_mouse_action(ev, B_DOWN)
	    && !check_mouse_wheel(ev)) {
		struct window *m;

		activate_bfu_technology(ses, -1);
		m = term->windows.next;
		m->handler(m, ev);

		return ses;
	}

	/* Handle tabs navigation if tabs bar is displayed. */
	if (ses->status.show_tabs_bar
	    && mouse->y == term->height - 1 - !!ses->status.show_status_bar) {
		int tab_num = get_tab_number_by_xpos(term, mouse->x);
		struct window *tab = get_current_tab(term);

		if (check_mouse_action(ev, B_UP)) {
			if (check_mouse_button(ev, B_MIDDLE)
			    && term->current_tab == tab_num
			    && mouse->y == term->prev_mouse_event.y) {
				if (tab->data == ses) ses = NULL;

				close_tab(term, tab->data);
			}

			return ses;
		}

		if (check_mouse_button(ev, B_WHEEL_UP)) {
			switch_current_tab(ses, -1);

		} else if (check_mouse_button(ev, B_WHEEL_DOWN)) {
			switch_current_tab(ses, 1);

		} else if (tab_num != -1) {
			switch_to_tab(term, tab_num, -1);

			if (check_mouse_button(ev, B_MIDDLE)) {
				do_not_ignore_next_mouse_event(term);
			} else if (check_mouse_button(ev, B_RIGHT)) {
				tab_menu(tab->data, mouse->x, mouse->y, 1);
			}
		}

		return ses;
	}

	if (!do_mouse_event(ses, ev, doc_view)
	    && check_mouse_button(ev, B_RIGHT)) {
		tab_menu(ses, mouse->x, mouse->y, 0);
		return NULL;
	}

#ifdef CONFIG_LEDS
	if (ses->status.show_leds
	    && mouse->y == term->height - 1
	    && mouse->x >= term->width - LEDS_COUNT - 3) {
		menu_leds_info(term, NULL, NULL);
		return NULL;
	}
#endif

	return NULL;
}
#endif /* CONFIG_MOUSE */

static void
try_typeahead(struct session *ses, struct document_view *doc_view,
              struct term_event *ev, enum main_action action_id)
{
	switch (get_opt_int("document.browse.search.typeahead")) {
		case 0:
			return;
		case 1:
			action_id = ACT_MAIN_SEARCH_TYPEAHEAD_LINK;
			break;
		case 2:
			action_id = ACT_MAIN_SEARCH_TYPEAHEAD_TEXT;
			break;
		default:
			INTERNAL("invalid value for document.browse.search.typeahead");
	}

	search_typeahead(ses, doc_view, action_id);

	/* Cross your fingers -- I'm just asking
	 * for an infinite loop! -- Miciah */
	term_send_event(ses->tab->term, ev);
}

/* Returns the session if event cleanup should be done or NULL if no cleanup is
 * needed. */
static struct session *
send_kbd_event(struct session *ses, struct document_view *doc_view,
	       struct term_event *ev)
{
	int event;
	enum main_action action_id;

	if (doc_view && send_to_frame(ses, doc_view, ev) != FRAME_EVENT_IGNORED)
		return NULL;

	action_id = kbd_action(KEYMAP_MAIN, ev, &event);

	if (action_id == ACT_MAIN_QUIT) {
		if (check_kbd_key(ev, KBD_CTRL_C))
quit:
			action_id = ACT_MAIN_REALLY_QUIT;
	}

	switch (do_action(ses, action_id, 0)) {
		case FRAME_EVENT_SESSION_DESTROYED:
			return NULL;
		case FRAME_EVENT_IGNORED:
			break;
		case FRAME_EVENT_OK:
		case FRAME_EVENT_REFRESH:
			return ses;
	}

	if (action_id == ACT_MAIN_SCRIPTING_FUNCTION) {
#ifdef CONFIG_SCRIPTING
		trigger_event(event, ses);
#endif
		return NULL;
	}

	if (check_kbd_key(ev, KBD_CTRL_C)) goto quit;

	if (get_kbd_modifier(ev) & KBD_MOD_ALT) {
		struct window *win;

		get_kbd_modifier(ev) &= ~KBD_MOD_ALT;
		activate_bfu_technology(ses, -1);
		win = ses->tab->term->windows.next;
		win->handler(win, ev);
		if (ses->tab->term->windows.next == win) {
			delete_window(win);
			get_kbd_modifier(ev) |= KBD_MOD_ALT;

			return NULL;
		}

		if (doc_view
		    && get_opt_int("document.browse.accesskey"
				   ".priority") <= 0
		    && try_document_key(ses, doc_view, ev)
		       == FRAME_EVENT_REFRESH) {
			/* The document ate the key! */
			refresh_view(ses, doc_view, 0);

			return NULL;
		}

		return ses;
	}

	if (!(get_kbd_modifier(ev) & KBD_MOD_CTRL)) {
		if (doc_view) try_typeahead(ses, doc_view, ev, action_id);
	}

	return NULL;
}

void
send_event(struct session *ses, struct term_event *ev)
{
	assert(ses && ev);
	if_assert_failed return;

	if (ev->ev == EVENT_KBD) {
		struct document_view *doc_view = current_frame(ses);

		ses = send_kbd_event(ses, doc_view, ev);
	}
#ifdef CONFIG_MOUSE
	else if (ev->ev == EVENT_MOUSE) {
		struct document_view *doc_view = current_frame(ses);

		ses = send_mouse_event(ses, doc_view, ev);
	}
#endif /* CONFIG_MOUSE */

	/* @ses may disappear ie. in close_tab() */
	if (ses) ses->kbdprefix.repeat_count = 0;
}

enum frame_event_status
download_link(struct session *ses, struct document_view *doc_view,
	      action_id_T action_id)
{
	struct link *link = get_current_link(doc_view);
	void (*download)(void *ses, unsigned char *file) = start_download;

	if (!link) return FRAME_EVENT_OK;

	if (ses->download_uri) {
		done_uri(ses->download_uri);
		ses->download_uri = NULL;
	}

	switch (action_id) {
		case ACT_MAIN_LINK_DOWNLOAD_RESUME:
			download = resume_download;
		case ACT_MAIN_LINK_DOWNLOAD:
			ses->download_uri = get_link_uri(ses, doc_view, link);
			break;

		case ACT_MAIN_LINK_DOWNLOAD_IMAGE:
			if (!link->where_img) break;
			ses->download_uri = get_uri(link->where_img, 0);
			break;

		default:
			INTERNAL("I think you forgot to take your medication, mental boy!");
			return FRAME_EVENT_OK;
	}

	if (!ses->download_uri) return FRAME_EVENT_OK;

	set_session_referrer(ses, doc_view->document->uri);
	query_file(ses, ses->download_uri, ses, download, NULL, 1);

	return FRAME_EVENT_OK;
}

enum frame_event_status
view_image(struct session *ses, struct document_view *doc_view, int xxxx)
{
	struct link *link = get_current_link(doc_view);

	if (link && link->where_img)
		goto_url(ses, link->where_img);

	return FRAME_EVENT_OK;
}

enum frame_event_status
save_as(struct session *ses, struct document_view *doc_view, int magic)
{
	assert(ses);
	if_assert_failed return FRAME_EVENT_OK;

	if (!have_location(ses)) return FRAME_EVENT_OK;

	if (ses->download_uri) done_uri(ses->download_uri);
	ses->download_uri = get_uri_reference(cur_loc(ses)->vs.uri);

	assert(doc_view && doc_view->document && doc_view->document->uri);
	if_assert_failed return FRAME_EVENT_OK;

	set_session_referrer(ses, doc_view->document->uri);
	query_file(ses, ses->download_uri, ses, start_download, NULL, 1);

	return FRAME_EVENT_OK;
}

static void
save_formatted_finish(struct terminal *term, int h, void *data, int resume)
{
	struct document *document = data;

	assert(term && document);
	if_assert_failed return;

	if (h == -1) return;
	if (dump_to_file(document, h)) {
		info_box(term, 0, N_("Save error"), ALIGN_CENTER,
			 N_("Error writing to file"));
	}
	close(h);
}

static void
save_formatted(void *data, unsigned char *file)
{
	struct session *ses = data;
	struct document_view *doc_view;

	assert(ses && ses->tab && ses->tab->term && file);
	if_assert_failed return;
	doc_view = current_frame(ses);
	assert(doc_view && doc_view->document);
	if_assert_failed return;

	create_download_file(ses->tab->term, file, NULL, 0, 0,
			     save_formatted_finish, doc_view->document);
}

enum frame_event_status
save_formatted_dlg(struct session *ses, struct document_view *doc_view, int xxxx)
{
	query_file(ses, doc_view->vs->uri, ses, save_formatted, NULL, 1);
	return FRAME_EVENT_OK;
}
