/*
 * Copyright 2010 Stephen Fryatt <stevef@netsurf-browser.org>
 *
 * 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
 * URL Suggestion Menu (implementation).
 */

#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <oslib/wimp.h>

#include "utils/messages.h"
#include "utils/nsurl.h"
#include "netsurf/url_db.h"

#include "riscos/menus.h"
#include "riscos/url_suggest.h"

struct url_suggest_item {
	const char		*url;	/*< The URL being stored.         */
	unsigned int		weight; /*< A weight assigned to the URL. */
	struct url_suggest_item	*next;  /*< The next URL in the list.     */
};

static bool ro_gui_url_suggest_callback(nsurl *url,
		const struct url_data *data);

static int suggest_entries;
static time_t suggest_time;
static struct url_suggest_item *suggest_list;

static wimp_MENU(URL_SUGGEST_MAX_URLS) url_suggest_menu_block;
wimp_menu *ro_gui_url_suggest_menu = (wimp_menu *) &url_suggest_menu_block;


/**
 * Initialise the URL suggestion menu.  This MUST be called before anything
 * tries to use the URL menu.
 *
 * \return true if initialisation was OK; else false.
 */

bool ro_gui_url_suggest_init(void)
{
	ro_gui_url_suggest_menu->title_data.indirected_text.text =
			(char *) messages_get("URLSuggest");
	ro_gui_menu_init_structure((wimp_menu *) ro_gui_url_suggest_menu,
			URL_SUGGEST_MAX_URLS);

	suggest_entries = 0;

	return true;
}


/**
 * Check if there is a URL suggestion menu available for use.
 *
 * \todo Ideally this should be able to decide if there's a menu
 *          available without actually having to build it all.
 *
 * \return true if the menu has entries; else false.
 */

bool ro_gui_url_suggest_get_menu_available(void)
{
	return ro_gui_url_suggest_prepare_menu();
}


/**
 * Builds the URL suggestion menu. This is called by ro_gui_menu_create() when
 * it is asked to display the url_suggest_menu.
 *
 * /return		true if the menu has entries; else false.
 */

bool ro_gui_url_suggest_prepare_menu(void)
{
	struct url_suggest_item	*list, *next;

	/* Fetch the URLs we want to include from URLdb. */

	suggest_entries = 0;
	suggest_list = NULL;
	suggest_time = time(NULL);

	urldb_iterate_entries(ro_gui_url_suggest_callback);

	/* If any menu entries were found, put them into the menu.  The list
	 * is in reverse order, last to first, so the menu is filled backwards.
	 * Entries from the list are freed as we go.
	 */

	assert(suggest_entries <= URL_SUGGEST_MAX_URLS);

	if (suggest_entries > 0) {
		int i = suggest_entries;

		list = suggest_list;
		suggest_list = NULL;

		while (list != NULL && i > 0) {
			i--;

			ro_gui_url_suggest_menu->entries[i].menu_flags = 0;
			ro_gui_url_suggest_menu->
					entries[i].data.indirected_text.text =
					(char *) list->url;
			ro_gui_url_suggest_menu->
					entries[i].data.indirected_text.size =
					strlen(list->url) + 1;

			next = list->next;
			free(list);
			list = next;
		}

		assert(i == 0);

		ro_gui_url_suggest_menu->entries[0].menu_flags |=
				wimp_MENU_TITLE_INDIRECTED;
		ro_gui_url_suggest_menu->
				entries[suggest_entries - 1].menu_flags |=
				wimp_MENU_LAST;

		return true;
	}

	return false;
}


/**
 * Callback function for urldb_iterate_entries
 *
 * \param  url		URL which matches
 * \param  data		Data associated with URL
 * \return 		true to continue iteration, false otherwise
 */

bool ro_gui_url_suggest_callback(nsurl *url, const struct url_data *data)
{
	int			count;
	unsigned int		weight;
	struct url_suggest_item	**list, *new;

	/* Ignore unvisited URLs, and those that don't apply to HTML or Text. */

	if (data->visits == 0 || (data->type != CONTENT_HTML &&
			data->type != CONTENT_TEXTPLAIN))
		return true;

	/* Calculate a weight for the URL. */

	weight = (suggest_time - data->last_visit) / data->visits;

	/* Hunt through those URLs already found to see if we want to add
	 * this one.  Smaller weights carry higher priority.
	 *
	 * The list is sorted into reverse order, so that lowest weight
	 * items are nearest the head.  Therefore, items are dropped from
	 * the head, making things simpler.
	 */

	list = &suggest_list;
	count = 0;

	while (*list != NULL && weight < (*list)->weight) {
		list = &((*list)->next);
		count++;
	}

	if (count > 0 || suggest_entries < URL_SUGGEST_MAX_URLS) {
		new = (struct url_suggest_item *)
				malloc(sizeof(struct url_suggest_item));

		if (new != NULL) {
			suggest_entries++;
			/* TODO: keeping pointers to URLdb data is bad.
			 *       should be nsurl_ref(url) or
			 *       take a copy of the string. */
			new->url = nsurl_access(url);
			new->weight = weight;
			new->next = *list;

			*list = new;
		}
	}

	/* If adding the URL gave us too many menu items, drop the lowest
	 * priority ones until the list is the right length again.
	 */

	while (suggest_list != NULL && suggest_entries > URL_SUGGEST_MAX_URLS) {
		struct url_suggest_item	*old = suggest_list;
		suggest_list = suggest_list->next;

		free(old);
		suggest_entries--;
	}

	return true;
}


/**
 * Process a selection from the URL Suggest menu.
 *
 * \param *selection	The menu selection.
 * \return		Pointer to the URL that was selected, or NULL for none.
 */

const char *ro_gui_url_suggest_get_selection(wimp_selection *selection)
{
	const char *url = NULL;

	if (selection->items[0] >= 0)
		url = ro_gui_url_suggest_menu->entries[selection->items[0]].
				data.indirected_text.text;

	return url;
}
