/*
 * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
 * Copyright 2006 James Bursa <bursa@users.sourceforge.net>
 * Copyright 2004 Andrew Timmins <atimmins@blueyonder.co.uk>
 * Copyright 2004 John Tytgat <joty@netsurf-browser.org>
 * Copyright 2006 Richard Wilson <info@tinct.net>
 * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org>
 * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net>  
 *
 * This file is part of NetSurf, http://www.netsurf-browser.org/
 *
 * NetSurf is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * NetSurf is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/** \file
 * Browser window creation and manipulation (implementation).
 */

#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <math.h>

#include "curl/curl.h"
#include "utils/config.h"
#include "content/content.h"
#include "content/fetch.h"
#include "content/hlcache.h"
#include "content/urldb.h"
#include "desktop/401login.h"
#include "desktop/browser.h"
#include "desktop/download.h"
#include "desktop/frames.h"
#include "desktop/history_core.h"
#include "desktop/gui.h"
#include "desktop/options.h"
#include "desktop/selection.h"
#include "desktop/textinput.h"
#include "render/form.h"
#include "render/html.h"
#include "render/textplain.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/url.h"
#include "utils/utils.h"
#include "utils/utf8.h"

/** browser window which is being redrawn. Valid only during redraw. */
struct browser_window *current_redraw_browser;

/** one or more windows require a reformat */
bool browser_reformat_pending;

/** maximum frame depth */
#define FRAME_DEPTH 8

static nserror browser_window_callback(hlcache_handle *c,
		const hlcache_event *event, void *pw);
static void browser_window_refresh(void *p);
static bool browser_window_check_throbber(struct browser_window *bw);
static void browser_window_convert_to_download(struct browser_window *bw, 
		llcache_handle *stream);
static void browser_window_start_throbber(struct browser_window *bw);
static void browser_window_stop_throbber(struct browser_window *bw);
static void browser_window_set_icon(struct browser_window *bw);
static void browser_window_destroy_children(struct browser_window *bw);
static void browser_window_destroy_internal(struct browser_window *bw);
static void browser_window_set_scale_internal(struct browser_window *bw,
		float scale);
static void browser_window_find_target_internal(struct browser_window *bw,
		const char *target, int depth, struct browser_window *page,
		int *rdepth, struct browser_window **bw_target);

/**
 * Create and open a new browser window with the given page.
 *
 * \param  url	    URL to start fetching in the new window (copied)
 * \param  clone    The browser window to clone
 * \param  referer  The referring uri (copied), or 0 if none
 */

struct browser_window *browser_window_create(const char *url,
		struct browser_window *clone,
		const char *referer, bool history_add, bool new_tab)
{
	struct browser_window *bw;

	assert(clone || history_add);

	if ((bw = calloc(1, sizeof *bw)) == NULL) {
		warn_user("NoMemory", 0);
		return NULL;
	}

	/* Initialise common parts */
	browser_window_initialise_common(bw, clone);

	/* window characteristics */
	bw->browser_window_type = BROWSER_WINDOW_NORMAL;
	bw->scrolling = SCROLLING_YES;
	bw->border = true;
	bw->no_resize = true;
	bw->last_action = wallclock();

	bw->window = gui_create_browser_window(bw, clone, new_tab);

	/* gui window */
	if (bw->window == NULL) {
		browser_window_destroy(bw);
		return NULL;
	}

	if (url)
		browser_window_go(bw, url, referer, history_add);

	
	return bw;
}


/**
 * Initialise common parts of a browser window
 *
 * \param bw     The window to initialise
 * \param clone  The window to clone, or NULL if none
 */
void browser_window_initialise_common(struct browser_window *bw,
		struct browser_window *clone)
{
	assert(bw);

	if (!clone)
		bw->history = history_create();
	else
		bw->history = history_clone(clone->history);

	/* window characteristics */
	bw->sel = selection_create(bw);
	bw->refresh_interval = -1;

	bw->reformat_pending = false;
	bw->drag_type = DRAGGING_NONE;
	bw->scale = (float) option_scale / 100.0;

	/* initialise status text cache */
	bw->status_text = NULL;
	bw->status_text_len = 0;
	bw->status_match = 0;
	bw->status_miss = 0;
}


/**
 * Start fetching a page in a browser window.
 *
 * \param  bw	    browser window
 * \param  url	    URL to start fetching (copied)
 * \param  referer  the referring uri (copied), or 0 if none
 *
 * Any existing fetches in the window are aborted.
 */

void browser_window_go(struct browser_window *bw, const char *url,
		const char *referer, bool history_add)
{
	/* All fetches passing through here are verifiable
	 * (i.e are the result of user action) */
	browser_window_go_post(bw, url, 0, 0, history_add, referer,
			false, true, NULL);
}


/**
 * Start a download of the given URL from a browser window.
 *
 * \param  bw	    browser window
 * \param  url	    URL to start downloading (copied)
 * \param  referer  the referring uri (copied), or 0 if none
 */

void browser_window_download(struct browser_window *bw, const char *url,
		const char *referer)
{
	browser_window_go_post(bw, url, 0, 0, false, referer,
			true, true, NULL);
}


/**
 * Start fetching a page in a browser window.
 *
 * \param  bw	    browser window
 * \param  url	    URL to start fetching (copied)
 * \param  referer  the referring uri (copied), or 0 if none
 *
 * Any existing fetches in the window are aborted.
 */

void browser_window_go_unverifiable(struct browser_window *bw,
		const char *url, const char *referer, bool history_add,
		hlcache_handle *parent)
{
	/* All fetches passing through here are unverifiable
	 * (i.e are not the result of user action) */
	browser_window_go_post(bw, url, 0, 0, history_add, referer,
			false, false, parent);
}

/**
 * Start fetching a page in a browser window, POSTing form data.
 *
 * \param  bw		   browser window
 * \param  url		   URL to start fetching (copied)
 * \param  post_urlenc	   url encoded post data, or 0 if none
 * \param  post_multipart  multipart post data, or 0 if none
 * \param  add_to_history  add to window history
 * \param  referer	   the referring uri (copied), or 0 if none
 * \param  download	   download, rather than render the uri
 * \param  verifiable	   this transaction is verifiable
 * \param  parent	   Parent content, or NULL
 *
 * Any existing fetches in the window are aborted.
 *
 * If post_urlenc and post_multipart are 0 the url is fetched using GET.
 *
 * The page is not added to the window history if add_to_history is false.
 * This should be used when returning to a page in the window history.
 */

void browser_window_go_post(struct browser_window *bw, const char *url,
		char *post_urlenc,
		struct fetch_multipart_data *post_multipart,
		bool add_to_history, const char *referer, bool download,
		bool verifiable, hlcache_handle *parent)
{
	hlcache_handle *c;
	char *url2;
	char *fragment;
	url_func_result res;
	int depth = 0;
	struct browser_window *cur;
	uint32_t fetch_flags = 0;
	bool fetch_is_post = (post_urlenc != NULL || post_multipart != NULL);
	llcache_post_data post;
	hlcache_child_context child;
	nserror error;

	LOG(("bw %p, url %s", bw, url));
	assert(bw);
	assert(url);

	/* don't allow massively nested framesets */
	for (cur = bw; cur->parent; cur = cur->parent)
		depth++;
	if (depth > FRAME_DEPTH) {
		LOG(("frame depth too high."));
		return;
	}

	/* Set up retrieval parameters */
	if (verifiable)
		fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE;

	if (post_multipart != NULL) {
		post.type = LLCACHE_POST_MULTIPART;
		post.data.multipart = post_multipart;
	} else if (post_urlenc != NULL) {
		post.type = LLCACHE_POST_URL_ENCODED;
		post.data.urlenc = post_urlenc;
	}

	if (parent != NULL && content_get_type(parent) == CONTENT_HTML) {
		child.charset = html_get_encoding(parent);
		child.quirks = content_get_quirks(parent);
	}

	/* Normalize the request URL */
	res = url_normalize(url, &url2);
	if (res != URL_FUNC_OK) {
		LOG(("failed to normalize url %s", url));
		return;
	}

	/* Get download out of the way */
	if (download) {
		llcache_handle *l;

		fetch_flags |= LLCACHE_RETRIEVE_FORCE_FETCH;
		fetch_flags |= LLCACHE_RETRIEVE_STREAM_DATA;

		error = llcache_handle_retrieve(url2, fetch_flags, referer, 
				fetch_is_post ? &post : NULL,
				NULL, NULL, &l);
		if (error != NSERROR_OK)
			LOG(("Failed to fetch download: %d", error));

		free(url2);

		error = download_context_create(l, bw->window);
		if (error != NSERROR_OK) {
			LOG(("Failed creating download context: %d", error));
			llcache_handle_abort(l);
			llcache_handle_release(l);
		}

		return;
	}

	free(bw->frag_id);
	bw->frag_id = NULL;

	/* find any fragment identifier on end of URL */
	res = url_fragment(url2, &fragment);
	if (res == URL_FUNC_NOMEM) {
		free(url2);
		warn_user("NoMemory", 0);
		return;
	} else if (res == URL_FUNC_OK) {
		bool same_url = false;

		bw->frag_id = fragment;

		/* Compare new URL with existing one (ignoring fragments) */
		if (bw->current_content != NULL && 
				content_get_url(bw->current_content) != NULL) {
			res = url_compare(content_get_url(bw->current_content),
					url2, true, &same_url);
			if (res == URL_FUNC_NOMEM) {
				free(url2);
				warn_user("NoMemory", 0);
				return;
			} else if (res == URL_FUNC_FAILED) {
				same_url = false;
			}
		}

		/* if we're simply moving to another ID on the same page,
		 * don't bother to fetch, just update the window.
		 */
		if (same_url && fetch_is_post == false && 
				strchr(url2, '?') == 0) {
			free(url2);
			if (add_to_history)
				history_add(bw->history, bw->current_content,
						bw->frag_id);
			browser_window_update(bw, false);
			if (bw->current_content != NULL) {
				browser_window_refresh_url_bar(bw,
					content_get_url(bw->current_content),
					bw->frag_id);
			}
			return;
		}
	}

	browser_window_stop(bw);
	browser_window_remove_caret(bw);
	browser_window_destroy_children(bw);

	LOG(("Loading '%s'", url2));

	browser_window_set_status(bw, messages_get("Loading"));
	bw->history_add = add_to_history;

	error = hlcache_handle_retrieve(url2,
			fetch_flags | HLCACHE_RETRIEVE_MAY_DOWNLOAD, 
			referer,
			fetch_is_post ? &post : NULL,
			browser_window_callback, bw,
			parent != NULL ? &child : NULL,
			NULL, &c);
	if (error == NSERROR_NO_FETCH_HANDLER) {
		gui_launch_url(url2);
		free(url2);
		return;
	} else if (error != NSERROR_OK) {
		free(url2);
		browser_window_set_status(bw, messages_get("NoMemory"));
		warn_user("NoMemory", 0);
		return;
	}

	free(url2);

	bw->loading_content = c;
	browser_window_start_throbber(bw);
	browser_window_refresh_url_bar(bw, url, NULL);
}


/**
 * Callback for fetchcache() for browser window fetches.
 */

nserror browser_window_callback(hlcache_handle *c,
		const hlcache_event *event, void *pw)
{
	struct browser_window *bw = pw;

	switch (event->type) {
	case CONTENT_MSG_DOWNLOAD:
		assert(bw->loading_content == c);

		browser_window_convert_to_download(bw, event->data.download);

		if (bw->current_content != NULL) {
			browser_window_refresh_url_bar(bw,
				content_get_url(bw->current_content),
				bw->frag_id);
		}
		break;

	case CONTENT_MSG_LOADING:
		assert(bw->loading_content == c);

#ifdef WITH_THEME_INSTALL
		if (content_get_type(c) == CONTENT_THEME) {
			theme_install_start(c);
			bw->loading_content = NULL;
			browser_window_stop_throbber(bw);
		} else
#endif
		{
			bw->refresh_interval = -1;
			browser_window_set_status(bw, 
					content_get_status_message(c));
		}
		break;

	case CONTENT_MSG_READY:
	{
		int width, height;

		assert(bw->loading_content == c);

		if (bw->current_content != NULL) {
			content_status status = 
					content_get_status(bw->current_content);

			if (status == CONTENT_STATUS_READY ||
					status == CONTENT_STATUS_DONE)
				content_close(bw->current_content);

			hlcache_handle_release(bw->current_content);
		}

		bw->current_content = c;
		bw->loading_content = NULL;

		/* Format the new content to the correct dimensions */
		gui_window_get_dimensions(bw->window, &width, &height, true);
		content_reformat(c, width, height);

		browser_window_remove_caret(bw);

		bw->scroll = NULL;

		gui_window_new_content(bw->window);

		browser_window_refresh_url_bar(bw,
				content_get_url(bw->current_content),
				bw->frag_id);

		/* new content; set scroll_to_top */
		browser_window_update(bw, true);
		content_open(c, bw, 0, 0, 0, 0);
		browser_window_set_status(bw, content_get_status_message(c));

		/* history */
		if (bw->history_add && bw->history) {
			const char *url = content_get_url(c);

			history_add(bw->history, c, bw->frag_id);
			if (urldb_add_url(url)) {
				urldb_set_url_title(url, content_get_title(c));
				urldb_update_url_visit_data(url);
				urldb_set_url_content_type(url, 
						content_get_type(c));
				/* This is safe as we've just added the URL */
				global_history_add(urldb_get_url(url));
			}
		}

		/* text selection */
		if (content_get_type(c) == CONTENT_HTML)
			selection_init(bw->sel,
					html_get_box_tree(bw->current_content));
		if (content_get_type(c) == CONTENT_TEXTPLAIN)
			selection_init(bw->sel, NULL);

		/* frames */
		if (content_get_type(c) == CONTENT_HTML && 
				html_get_frameset(c) != NULL)
			browser_window_create_frameset(bw, 
					html_get_frameset(c));
		if (content_get_type(c) == CONTENT_HTML && 
				html_get_iframe(c) != NULL)
			browser_window_create_iframes(bw, html_get_iframe(c));
	}
		break;

	case CONTENT_MSG_DONE:
		assert(bw->current_content == c);

		browser_window_update(bw, false);
		browser_window_set_status(bw, content_get_status_message(c));
		browser_window_stop_throbber(bw);
		browser_window_set_icon(bw);

		history_update(bw->history, c);
		hotlist_visited(c);

		if (bw->refresh_interval != -1)
			schedule(bw->refresh_interval,
					browser_window_refresh, bw);
		break;

	case CONTENT_MSG_ERROR:
		browser_window_set_status(bw, event->data.error);

		/* Only warn the user about errors in top-level windows */
		if (bw->browser_window_type == BROWSER_WINDOW_NORMAL)
			warn_user(event->data.error, 0);

		if (c == bw->loading_content)
			bw->loading_content = NULL;
		else if (c == bw->current_content) {
			bw->current_content = NULL;
			browser_window_remove_caret(bw);
			bw->scroll = NULL;
			selection_init(bw->sel, NULL);
		}

		hlcache_handle_release(c);

		browser_window_stop_throbber(bw);
		break;

	case CONTENT_MSG_STATUS:
		browser_window_set_status(bw, content_get_status_message(c));
		break;

	case CONTENT_MSG_REFORMAT:
		if (c == bw->current_content &&
			content_get_type(c) == CONTENT_HTML) {
			/* reposition frames */
			if (html_get_frameset(c) != NULL)
				browser_window_recalculate_frameset(bw);
			/* reflow iframe positions */
			if (html_get_iframe(c) != NULL)
				browser_window_recalculate_iframes(bw);
			/* box tree may have changed, need to relabel */
			selection_reinit(bw->sel, html_get_box_tree(c));
		}

		if (bw->move_callback)
			bw->move_callback(bw, bw->caret_p);

		browser_window_update(bw, false);
		break;

	case CONTENT_MSG_REDRAW:
		gui_window_update_box(bw->window, &event->data);
		break;

	case CONTENT_MSG_REFRESH:
		bw->refresh_interval = event->data.delay * 100;
		break;

	default:
		assert(0);
	}

	return NSERROR_OK;
}


/**
 * Transfer the loading_content to a new download window.
 */

void browser_window_convert_to_download(struct browser_window *bw,
		llcache_handle *stream)
{
	nserror error;

	error = download_context_create(stream, bw->window);
	if (error != NSERROR_OK) {
		llcache_handle_abort(stream);
		llcache_handle_release(stream);

		return;
	}

	/* remove content from browser window */
	hlcache_handle_release(bw->loading_content);
	bw->loading_content = NULL;

	browser_window_stop_throbber(bw);
}


/**
 * Handle meta http-equiv refresh time elapsing by loading a new page.
 *
 * \param  p  browser window to refresh with new page
 */

void browser_window_refresh(void *p)
{
	struct browser_window *bw = p;
	bool history_add = true;
	const char *url;
	const char *refresh;

	assert(bw->current_content != NULL &&
		(content_get_status(bw->current_content) == 
				CONTENT_STATUS_READY ||
		content_get_status(bw->current_content) == 
				CONTENT_STATUS_DONE));

	/* Ignore if the refresh URL has gone
	 * (may happen if a fetch error occurred) */
	refresh = content_get_refresh_url(bw->current_content);
	if (refresh == NULL)
		return;

	/* mark this content as invalid so it gets flushed from the cache */
	content_invalidate_reuse_data(bw->current_content);

	url = content_get_url(bw->current_content);
	if (url != NULL && strcmp(url, refresh) == 0)
		history_add = false;

	/* Treat an (almost) immediate refresh in a top-level browser window as
	 * if it were an HTTP redirect, and thus make the resulting fetch
	 * verifiable.
	 *
	 * See fetchcache.c for why redirected fetches should be verifiable at
	 * all.
	 */
	if (bw->refresh_interval <= 100 && bw->parent == NULL) {
		browser_window_go(bw, refresh, url, history_add);
	} else {
		browser_window_go_unverifiable(bw, refresh, url, history_add, 
				bw->current_content);
	}
}


/**
 * Start the busy indicator.
 *
 * \param  bw  browser window
 */

void browser_window_start_throbber(struct browser_window *bw)
{
	bw->throbbing = true;

	while (bw->parent)
		bw = bw->parent;

	gui_window_start_throbber(bw->window);
}


/**
 * Stop the busy indicator.
 *
 * \param  bw  browser window
 */

void browser_window_stop_throbber(struct browser_window *bw)
{
	bw->throbbing = false;

	while (bw->parent)
		bw = bw->parent;

	if (!browser_window_check_throbber(bw))
		gui_window_stop_throbber(bw->window);
}

bool browser_window_check_throbber(struct browser_window *bw)
{
	int children, index;

	if (bw->throbbing)
		return true;

	if (bw->children) {
		children = bw->rows * bw->cols;
		for (index = 0; index < children; index++) {
			if (browser_window_check_throbber(&bw->children[index]))
				return true;
		}
	}
	if (bw->iframes) {
		for (index = 0; index < bw->iframe_count; index++) {
			if (browser_window_check_throbber(&bw->iframes[index]))
				return true;
		}
	}
	return false;
}

/**
 * when ready, set icon at top level
 * \param bw browser_window
 * current implementation ignores lower-levels' link rels completely
 */
void browser_window_set_icon(struct browser_window *bw)
{
	while (bw->parent)
		bw = bw->parent;

	if (bw->current_content != NULL && 
			content_get_type(bw->current_content) == CONTENT_HTML)
		gui_window_set_icon(bw->window,
				html_get_favicon(bw->current_content));
	else
		gui_window_set_icon(bw->window, NULL);
}

/**
 * Redraw browser window, set extent to content, and update title.
 *
 * \param  bw		  browser_window
 * \param  scroll_to_top  move view to top of page
 */

void browser_window_update(struct browser_window *bw, bool scroll_to_top)
{
	int x, y;

	if (bw->current_content == NULL)
		return;

	gui_window_set_title(bw->window, 
			content_get_title(bw->current_content));

	gui_window_update_extent(bw->window);

	if (scroll_to_top)
		gui_window_set_scroll(bw->window, 0, 0);

	/** \todo don't do this if the user has scrolled */
	/* if frag_id exists, then try to scroll to it */
	if (bw->frag_id && html_get_id_offset(bw->current_content, bw->frag_id,
			&x, &y)) {
		gui_window_set_scroll(bw->window, x, y);
	}

	gui_window_redraw_window(bw->window);
}


/**
 * Stop all fetching activity in a browser window.
 *
 * \param  bw  browser window
 */

void browser_window_stop(struct browser_window *bw)
{
	int children, index;

	if (bw->loading_content != NULL) {
		hlcache_handle_abort(bw->loading_content);
		hlcache_handle_release(bw->loading_content);
		bw->loading_content = NULL;
	}

	if (bw->current_content != NULL && content_get_status(
			bw->current_content) != CONTENT_STATUS_DONE) {
		nserror error;
		assert(content_get_status(bw->current_content) == 
				CONTENT_STATUS_READY);
		error = hlcache_handle_abort(bw->current_content);
		assert(error == NSERROR_OK);
	}

	schedule_remove(browser_window_refresh, bw);

	if (bw->children) {
		children = bw->rows * bw->cols;
		for (index = 0; index < children; index++)
			browser_window_stop(&bw->children[index]);
	}
	if (bw->iframes) {
		children = bw->iframe_count;
		for (index = 0; index < children; index++)
			browser_window_stop(&bw->iframes[index]);
	}

	if (bw->current_content != NULL) {
		browser_window_refresh_url_bar(bw, 
				content_get_url(bw->current_content), bw->frag_id);
	}

	browser_window_stop_throbber(bw);
}


/**
 * Reload the page in a browser window.
 *
 * \param  bw  browser window
 * \param  all whether to reload all objects associated with the page
 */

void browser_window_reload(struct browser_window *bw, bool all)
{
	hlcache_handle *c;
	unsigned int i;

	if (bw->current_content == NULL || bw->loading_content != NULL)
		return;

	if (all && content_get_type(bw->current_content) == CONTENT_HTML) {
		struct html_stylesheet *sheets;
		struct content_html_object *objects;
		unsigned int count;

		c = bw->current_content;

		/* invalidate objects */
		objects = html_get_objects(c, &count);

		for (i = 0; i != count; i++) {
			if (objects[i].content != NULL)
				content_invalidate_reuse_data(
						objects[i].content);
		}

		/* invalidate stylesheets */
		sheets = html_get_stylesheets(c, &count);

		for (i = STYLESHEET_START; i != count; i++) {
			if (sheets[i].type == HTML_STYLESHEET_EXTERNAL &&
					sheets[i].data.external != NULL) {
				content_invalidate_reuse_data(
						sheets[i].data.external);
			}
		}
	}

	content_invalidate_reuse_data(bw->current_content);

	browser_window_go(bw, content_get_url(bw->current_content), 0, false);
}


/**
 * Change the status bar of a browser window.
 *
 * \param  bw	 browser window
 * \param  text  new status text (copied)
 */

void browser_window_set_status(struct browser_window *bw, const char *text)
{
	int text_len;
	/* find topmost window */
	while (bw->parent)
		bw = bw->parent;

	if ((bw->status_text != NULL) && 
	    (strcmp(text, bw->status_text) == 0)) {
		/* status text is unchanged */
		bw->status_match++;
		return;
	}

	/* status text is changed */
 
	text_len = strlen(text);

	if ((bw->status_text == NULL) || (bw->status_text_len < text_len)) {
		/* no current string allocation or it is not long enough */
		free(bw->status_text);
		bw->status_text = strdup(text);
		bw->status_text_len = text_len;
	} else {
		/* current allocation has enough space */
		memcpy(bw->status_text, text, text_len + 1);
	}

	bw->status_miss++;
	gui_window_set_status(bw->window, bw->status_text);
}


/**
 * Change the shape of the mouse pointer
 *
 * \param  shape    shape to use
 */

void browser_window_set_pointer(struct gui_window *g, gui_pointer_shape shape)
{
	gui_window_set_pointer(g, shape);
}


/**
 * Close and destroy a browser window.
 *
 * \param  bw  browser window
 */

void browser_window_destroy(struct browser_window *bw)
{
	/* can't destoy child windows on their own */
	assert(!bw->parent);

	/* destroy */
	browser_window_destroy_internal(bw);
	free(bw);
}


/**
 * Close and destroy all child browser window.
 *
 * \param  bw  browser window
 */

void browser_window_destroy_children(struct browser_window *bw)
{
	int i;

	if (bw->children) {
		for (i = 0; i < (bw->rows * bw->cols); i++)
			browser_window_destroy_internal(&bw->children[i]);
		free(bw->children);
		bw->children = NULL;
		bw->rows = 0;
		bw->cols = 0;
	}
	if (bw->iframes) {
		for (i = 0; i < bw->iframe_count; i++)
			browser_window_destroy_internal(&bw->iframes[i]);
		free(bw->iframes);
		bw->iframes = NULL;
		bw->iframe_count = 0;
	}
}


/**
 * Release all memory associated with a browser window.
 *
 * \param  bw  browser window
 */

void browser_window_destroy_internal(struct browser_window *bw)
{
	assert(bw);

	LOG(("Destroying window"));

	if (bw->children != NULL || bw->iframes != NULL)
		browser_window_destroy_children(bw);

	if (bw->loading_content != NULL) {
		hlcache_handle_release(bw->loading_content);
		bw->loading_content = NULL;
	}

	if (bw->current_content != NULL) {
		content_status status = content_get_status(bw->current_content);
		if (status == CONTENT_STATUS_READY || 
				status == CONTENT_STATUS_DONE)
			content_close(bw->current_content);

		hlcache_handle_release(bw->current_content);
		bw->current_content = NULL;
	}

	schedule_remove(browser_window_refresh, bw);

	selection_destroy(bw->sel);
	history_destroy(bw->history);
	gui_window_destroy(bw->window);

	free(bw->name);
	free(bw->frag_id);
	free(bw->status_text);
	bw->status_text = NULL;
	LOG(("Status text cache match:miss %d:%d", 
	     bw->status_match, bw->status_miss));
}


/**
 * Returns the browser window that is responsible for the child.
 *
 * \param  bw	The browser window to find the owner of
 * \return the browser window's owner
 */

struct browser_window *browser_window_owner(struct browser_window *bw)
{
  	/* an iframe's parent is just the parent window */
  	if (bw->browser_window_type == BROWSER_WINDOW_IFRAME)
  		return bw->parent;

  	/* the parent of a frameset is either a NORMAL window or an IFRAME */
	while (bw->parent != NULL) {
		switch (bw->browser_window_type) {
 		case BROWSER_WINDOW_NORMAL:
  		case BROWSER_WINDOW_IFRAME:
  			return bw;
		case BROWSER_WINDOW_FRAME:
 		case BROWSER_WINDOW_FRAMESET:
  			bw = bw->parent;
			break;
		}
	}
	return bw;
}


/**
 * Reformat a browser window contents to a new width or height.
 *
 * \param  bw      the browser window to reformat
 * \param  width   new width
 * \param  height  new height
 */

void browser_window_reformat(struct browser_window *bw, int width, int height)
{
	hlcache_handle *c = bw->current_content;

	if (c == NULL)
		return;

	content_reformat(c, width / bw->scale, height / bw->scale);
}


/**
 * Sets the scale of a browser window
 *
 * \param bw	The browser window to scale
 * \param scale	The new scale
 * \param all	Scale all windows in the tree (ie work up aswell as down)
 */

void browser_window_set_scale(struct browser_window *bw, float scale, bool all)
{
	while (bw->parent && all)
		bw = bw->parent;

	browser_window_set_scale_internal(bw, scale);

	if (bw->parent)
		bw = bw->parent;

	browser_window_recalculate_frameset(bw);
}

void browser_window_set_scale_internal(struct browser_window *bw, float scale)
{
	int i;
	hlcache_handle *c;

	if (fabs(bw->scale-scale) < 0.0001)
		return;

	bw->scale = scale;
	c = bw->current_content;

	if (c != NULL) {
	  	if (content_can_reformat(c) == false) {
			browser_window_update(bw, false);
	  	} else {
			bw->reformat_pending = true;
			browser_reformat_pending = true;
	 	}
	}

	gui_window_set_scale(bw->window, scale);

	for (i = 0; i < (bw->cols * bw->rows); i++)
		browser_window_set_scale_internal(&bw->children[i], scale);
	for (i = 0; i < bw->iframe_count; i++)
		browser_window_set_scale_internal(&bw->iframes[i], scale);
}


/**
 * Update URL bar for a given browser window to given URL
 *
 * \param bw	Browser window to update URL bar for.
 * \param url	URL for content displayed by bw, excluding any fragment.
 * \param frag	Additional fragment. May be NULL if none.
 */

void browser_window_refresh_url_bar(struct browser_window *bw, const char *url,
		const char *frag)
{
	char *url_buf;

	assert(bw);
	assert(url);
	
	bw->visible_select_menu = NULL;

	if (frag == NULL) {
		/* With no fragment, we may as well pass url straight through
		 * saving a malloc, copy, free cycle.
		 */
		gui_window_set_url(bw->window, url);
	} else {
		url_buf = malloc(strlen(url) + 1 /* # */ +
				strlen(frag) + 1 /* \0 */);
		if (url_buf != NULL) {
			/* This sprintf is safe because of the above size
			 * calculation, thus we don't need snprintf
			 */
			sprintf(url_buf, "%s#%s", url, frag);
			gui_window_set_url(bw->window, url_buf);
			free(url_buf);
		} else {
			warn_user("NoMemory", 0);
		}
	}
}

/**
 * Locate a browser window in the specified stack according.
 *
 * \param bw  the browser_window to search all relatives of
 * \param target  the target to locate
 * \param new_window  always return a new window (ie 'Open Link in New Window')
 */

struct browser_window *browser_window_find_target(struct browser_window *bw,
		const char *target, browser_mouse_state mouse)
{
	struct browser_window *bw_target;
	struct browser_window *top;
	hlcache_handle *c;
	int rdepth;

	/* use the base target if we don't have one */
	c = bw->current_content;
	if (target == NULL && c != NULL && content_get_type(c) == CONTENT_HTML)
		target = html_get_base_target(c);
	if (target == NULL)
		target = TARGET_SELF;

	/* allow the simple case of target="_blank" to be ignored if requested
	 */
	if ((!(mouse & BROWSER_MOUSE_CLICK_2)) &&
			(!((mouse & BROWSER_MOUSE_CLICK_2) &&
			(mouse & BROWSER_MOUSE_MOD_2))) &&
			(!option_target_blank)) {
		/* not a mouse button 2 click
		 * not a mouse button 1 click with ctrl pressed
		 * configured to ignore target="_blank" */
		if ((target == TARGET_BLANK) || (!strcasecmp(target, "_blank")))
			return bw;
	}

	/* handle reserved keywords */
	if (((option_button_2_tab) && (mouse & BROWSER_MOUSE_CLICK_2)) ||
			((!option_button_2_tab) &&
			((mouse & BROWSER_MOUSE_CLICK_1) &&
			(mouse & BROWSER_MOUSE_MOD_2))) ||
			((option_button_2_tab) && ((target == TARGET_BLANK) ||
			(!strcasecmp(target, "_blank"))))) {
		/* open in new tab if:
		 * - button_2 opens in new tab and button_2 was pressed
		 * OR
		 * - button_2 doesn't open in new tabs and button_1 was
		 *   pressed with ctrl held
		 * OR
		 * - button_2 opens in new tab and the link target is "_blank"
		 */
		bw_target = browser_window_create(NULL, bw, NULL, false, true);
		if (!bw_target)
			return bw;
		return bw_target;
	} else if (((!option_button_2_tab) &&
			(mouse & BROWSER_MOUSE_CLICK_2)) ||
			((option_button_2_tab) &&
			((mouse & BROWSER_MOUSE_CLICK_1) &&
			(mouse & BROWSER_MOUSE_MOD_2))) ||
			((!option_button_2_tab) && ((target == TARGET_BLANK) ||
			(!strcasecmp(target, "_blank"))))) {
		/* open in new window if:
		 * - button_2 doesn't open in new tabs and button_2 was pressed
		 * OR
		 * - button_2 opens in new tab and button_1 was pressed with
		 *   ctrl held
		 * OR
		 * - button_2 doesn't open in new tabs and the link target is
		 *   "_blank"
		 */
		bw_target = browser_window_create(NULL, bw, NULL, false, false);
		if (!bw_target)
			return bw;
		return bw_target;
	} else if ((target == TARGET_SELF) || (!strcasecmp(target, "_self"))) {
		return bw;
	} else if ((target == TARGET_PARENT) ||
			(!strcasecmp(target, "_parent"))) {
		if (bw->parent)
			return bw->parent;
		return bw;
	} else if ((target == TARGET_TOP) || (!strcasecmp(target, "_top"))) {
		while (bw->parent)
			bw = bw->parent;
		return bw;
	}

	/* find frame according to B.8, ie using the following priorities:
	 *
	 *  1) current frame
	 *  2) closest to front
	 */
	rdepth = -1;
	bw_target = NULL;
	for (top = bw; top->parent; top = top->parent);
	browser_window_find_target_internal(top, target, 0, bw, &rdepth,
			&bw_target);
	if (bw_target)
		return bw_target;

	/* we require a new window using the target name */
	if (!option_target_blank)
		return bw;
	bw_target = browser_window_create(NULL, bw, NULL, false, false);
	if (!bw_target)
		return bw;

	/* frame names should begin with an alphabetic character (a-z,A-Z),
	 * however in practice you get things such as '_new' and '2left'. The
	 * only real effect this has is when giving out names as it can be
	 * assumed that an author intended '_new' to create a new nameless
	 * window (ie '_blank') whereas in the case of '2left' the intention
	 * was for a new named window. As such we merely special case windows
	 * that begin with an underscore. */
	if (target[0] != '_') {
		bw_target->name = strdup(target);
		if (!bw_target->name)
			warn_user("NoMemory", 0);
	}
	return bw_target;
}

void browser_window_find_target_internal(struct browser_window *bw,
		const char *target, int depth, struct browser_window *page,
		int *rdepth, struct browser_window **bw_target)
{
	int i;

	if ((bw->name) && (!strcasecmp(bw->name, target))) {
		if ((bw == page) || (depth > *rdepth)) {
			*rdepth = depth;
			*bw_target = bw;
		}
	}

	if ((!bw->children) && (!bw->iframes))
		return;

	depth++;

	if (bw->children != NULL) {
		for (i = 0; i < (bw->cols * bw->rows); i++) {
			if ((bw->children[i].name) &&
					(!strcasecmp(bw->children[i].name, 
					target))) {
				if ((page == &bw->children[i]) || 
						(depth > *rdepth)) {
					*rdepth = depth;
					*bw_target = &bw->children[i];
				}
			}
			if (bw->children[i].children)
				browser_window_find_target_internal(
						&bw->children[i],
						target, depth, page, 
						rdepth, bw_target);
		}
	}

	if (bw->iframes != NULL) {
		for (i = 0; i < bw->iframe_count; i++)
			browser_window_find_target_internal(&bw->iframes[i], 
					target, depth, page, rdepth, bw_target);
	}
}


/**
 * Handle mouse movements in a browser window.
 *
 * \param  bw	  browser window
 * \param  mouse  state of mouse buttons and modifier keys
 * \param  x	  coordinate of mouse
 * \param  y	  coordinate of mouse
 */

void browser_window_mouse_track(struct browser_window *bw,
		browser_mouse_state mouse, int x, int y)
{
	hlcache_handle *c = bw->current_content;

	if (c == NULL && bw->drag_type != DRAGGING_FRAME)
		return;

	/* detect end of drag operation in case the platform-specific code
	   doesn't call browser_mouse_drag_end() (RISC OS code does) */

	if (bw->drag_type != DRAGGING_NONE && !mouse) {
		browser_window_mouse_drag_end(bw, mouse, x, y);
	}

	if (bw->drag_type == DRAGGING_FRAME) {
		browser_window_resize_frame(bw, bw->x0 + x, bw->y0 + y);
	} else if (bw->drag_type == DRAGGING_PAGE_SCROLL) {
		/* mouse movement since drag started */
		int scrollx = bw->drag_start_x - x;
		int scrolly = bw->drag_start_y - y;

		/* new scroll offsets */
		scrollx += bw->drag_start_scroll_x;
		scrolly += bw->drag_start_scroll_y;

		bw->drag_start_scroll_x = scrollx;
		bw->drag_start_scroll_y = scrolly;

		gui_window_set_scroll(bw->window, scrollx, scrolly);
	} else {
		assert(c != NULL);
		content_mouse_track(c, bw, mouse, x, y);
	}
}


/**
 * Handle mouse clicks in a browser window.
 *
 * \param  bw	  browser window
 * \param  mouse  state of mouse buttons and modifier keys
 * \param  x	  coordinate of mouse
 * \param  y	  coordinate of mouse
 */

void browser_window_mouse_click(struct browser_window *bw,
		browser_mouse_state mouse, int x, int y)
{
	hlcache_handle *c = bw->current_content;

	if (!c)
		return;

	switch (content_get_type(c)) {
	case CONTENT_HTML:
	case CONTENT_TEXTPLAIN:
		content_mouse_action(c, bw, mouse, x, y);
		break;
	default:
		if (mouse & BROWSER_MOUSE_MOD_2) {
			if (mouse & BROWSER_MOUSE_DRAG_2)
				gui_drag_save_object(GUI_SAVE_OBJECT_NATIVE, c,
						bw->window);
			else if (mouse & BROWSER_MOUSE_DRAG_1)
				gui_drag_save_object(GUI_SAVE_OBJECT_ORIG, c,
						bw->window);
		}
		else if (mouse & (BROWSER_MOUSE_DRAG_1 |
				BROWSER_MOUSE_DRAG_2)) {
			browser_window_page_drag_start(bw, x, y);
			browser_window_set_pointer(bw->window, GUI_POINTER_MOVE);
		}
		break;
	}
}


/**
 * Handles the end of a drag operation in a browser window.
 *
 * \param  bw	  browser window
 * \param  mouse  state of mouse buttons and modifier keys
 * \param  x	  coordinate of mouse
 * \param  y	  coordinate of mouse
 */

// TODO: MOVE content specific stuff out

void browser_window_mouse_drag_end(struct browser_window *bw,
		browser_mouse_state mouse, int x, int y)
{
	
	if (bw->visible_select_menu != NULL) {
		form_select_mouse_drag_end(bw->visible_select_menu, mouse,
				x, y);
		return;
	}
	
	if (bw->scroll != NULL) {
		html_overflow_scroll_drag_end(bw->scroll, mouse, x, y);
		return;
	}
	
	switch (bw->drag_type) {
	case DRAGGING_SELECTION: {
		hlcache_handle *h = bw->current_content;
		if (h) {
			int dir = -1;
			size_t idx;

			if (selection_dragging_start(bw->sel))
				dir = 1;

			if (content_get_type(h) == CONTENT_HTML) {
				idx = html_selection_drag_end(h, mouse, x, y,
						dir);
				if (idx != 0)
					selection_track(bw->sel, mouse, idx);
			} else {
				assert(content_get_type(h) ==
						CONTENT_TEXTPLAIN);
				idx = textplain_offset_from_coords(h, x, y,
						dir);
				selection_track(bw->sel, mouse, idx);
			}
		}
		selection_drag_end(bw->sel);
	}
	break;

	default:
		break;
	}

	bw->drag_type = DRAGGING_NONE;
}


/**
 * Redraw a rectangular region of a browser window
 *
 * \param  bw	  browser window to be redrawn
 * \param  x	  x co-ord of top-left
 * \param  y	  y co-ord of top-left
 * \param  width  width of rectangle
 * \param  height height of rectangle
 */

void browser_window_redraw_rect(struct browser_window *bw, int x, int y,
		int width, int height)
{
	content_request_redraw(bw->current_content, x, y, width, height);
}


/**
 * Start drag scrolling the contents of the browser window
 *
 * \param bw  browser window
 * \param x   x ordinate of initial mouse position
 * \param y   y ordinate
 */

void browser_window_page_drag_start(struct browser_window *bw, int x, int y)
{
	bw->drag_type = DRAGGING_PAGE_SCROLL;

	bw->drag_start_x = x;
	bw->drag_start_y = y;

	gui_window_get_scroll(bw->window, &bw->drag_start_scroll_x,
			&bw->drag_start_scroll_y);

	gui_window_scroll_start(bw->window);
}


/**
 * Check availability of Back action for a given browser window
 *
 * \param bw  browser window
 * \return true if Back action is available
 */

bool browser_window_back_available(struct browser_window *bw)
{
	return (bw && bw->history && history_back_available(bw->history));
}


/**
 * Check availability of Forward action for a given browser window
 *
 * \param bw  browser window
 * \return true if Forward action is available
 */

bool browser_window_forward_available(struct browser_window *bw)
{
	return (bw && bw->history && history_forward_available(bw->history));
}


/**
 * Check availability of Reload action for a given browser window
 *
 * \param bw  browser window
 * \return true if Reload action is available
 */

bool browser_window_reload_available(struct browser_window *bw)
{
	return (bw && bw->current_content && !bw->loading_content);
}


/**
 * Check availability of Stop action for a given browser window
 *
 * \param bw  browser window
 * \return true if Stop action is available
 */

bool browser_window_stop_available(struct browser_window *bw)
{
	return (bw && (bw->loading_content ||
			(bw->current_content &&
			(content_get_status(bw->current_content) != 
			CONTENT_STATUS_DONE))));
}
