/*
 * Copyright 2004 John M Bell <jmb202@ecs.soton.ac.uk>
 * Copyright 2005 Adrian Lees <adrianl@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
 * Free text search (implementation)
 */

#include "utils/config.h"

#include <ctype.h>
#include <string.h>
#include "oslib/hourglass.h"
#include "oslib/wimp.h"
#include "utils/config.h"
#include "content/content.h"
#include "content/hlcache.h"
#include "desktop/browser.h"
#include "desktop/gui.h"
#include "desktop/search.h"
#include "desktop/selection.h"
#include "render/box.h"
#include "render/html.h"
#include "riscos/dialog.h"
#include "riscos/menus.h"
#include "riscos/wimp.h"
#include "riscos/wimp_event.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/utils.h"

#define RECENT_SEARCHES 8

struct search_static_data {
	char *recent_searches[RECENT_SEARCHES];
	bool search_insert;
	struct browser_window *search_window;
	search_flags_t flags;
	char *string;
};

static struct search_static_data search_data =
		{{NULL}, false, NULL, 0, NULL};

static wimp_MENU(RECENT_SEARCHES) menu_recent;
wimp_menu *recent_search_menu = (wimp_menu *)&menu_recent;
#define DEFAULT_FLAGS (wimp_ICON_TEXT | wimp_ICON_FILLED | \
		(wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) | \
		(wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT))


static void ro_gui_search_end(wimp_w w);
static bool ro_gui_search_next(wimp_w w);
static bool ro_gui_search_menu_prepare(wimp_w w, wimp_i i, wimp_menu *menu,
		wimp_pointer *pointer);
static bool ro_gui_search_prepare_menu(void);
static bool ro_gui_search_click(wimp_pointer *pointer);
static bool ro_gui_search_keypress(wimp_key *key);
static search_flags_t ro_gui_search_update_flags(void);
static void ro_gui_search_set_forward_state(bool active, void *p);
static void ro_gui_search_set_back_state(bool active, void *p);
static void ro_gui_search_set_status(bool found, void *p);
static void ro_gui_search_set_hourglass(bool active, void *p);
static void ro_gui_search_add_recent(const char *string, void *p);

static struct search_callbacks ro_gui_search_callbacks = {
	ro_gui_search_set_forward_state,
	ro_gui_search_set_back_state,
	ro_gui_search_set_status,
	ro_gui_search_set_hourglass,
	ro_gui_search_add_recent
};

void ro_gui_search_init(void)
{
	dialog_search = ro_gui_dialog_create("search");
	ro_gui_wimp_event_register_keypress(dialog_search,
			ro_gui_search_keypress);
	ro_gui_wimp_event_register_close_window(dialog_search,
			ro_gui_search_end);
	ro_gui_wimp_event_register_menu_prepare(dialog_search,
			ro_gui_search_menu_prepare);
	ro_gui_wimp_event_register_menu_gright(dialog_search,
			ICON_SEARCH_TEXT, ICON_SEARCH_MENU,
			recent_search_menu);
	ro_gui_wimp_event_register_text_field(dialog_search,
			ICON_SEARCH_STATUS);
	ro_gui_wimp_event_register_checkbox(dialog_search,
			ICON_SEARCH_CASE_SENSITIVE);
	ro_gui_wimp_event_register_mouse_click(dialog_search,
			ro_gui_search_click);
	ro_gui_wimp_event_register_ok(dialog_search, ICON_SEARCH_FIND_NEXT,
			ro_gui_search_next);
	ro_gui_wimp_event_register_cancel(dialog_search, ICON_SEARCH_CANCEL);
	ro_gui_wimp_event_set_help_prefix(dialog_search, "HelpSearch");

	recent_search_menu->title_data.indirected_text.text =
			(char*)messages_get("Search");
	ro_gui_menu_init_structure(recent_search_menu, RECENT_SEARCHES);
}

/**
 * Wrapper for the pressing of an OK button for wimp_event.
 *
 * \return false, to indicate the window should not be closed
 */
bool ro_gui_search_next(wimp_w w)
{
	search_data.search_insert = true;
	search_flags_t flags = SEARCH_FLAG_FORWARDS |
			ro_gui_search_update_flags();
	if (browser_window_search_verify_new(search_data.search_window,
			&ro_gui_search_callbacks, NULL))
		browser_window_search_step(
				search_data.search_window, flags,
				ro_gui_get_icon_string(dialog_search,
				ICON_SEARCH_TEXT));
	return false;
}


/**
 * Callback to prepare menus in the Search dialog.  At present, this
 * only has to handle the previous search pop-up.
 *
 * \param w		The window handle owning the menu.
 * \param i		The icon handle owning the menu.
 * \param *menu		The menu to be prepared.
 * \param *pointer	The associated mouse click event block, or NULL
 *			on an Adjust-click re-opening.
 * \return		true if the event was handled; false if not.
 */

bool ro_gui_search_menu_prepare(wimp_w w, wimp_i i, wimp_menu *menu,
		wimp_pointer *pointer)
{
	if (menu != recent_search_menu || i != ICON_SEARCH_MENU)
		return false;

	if (pointer != NULL)
		return ro_gui_search_prepare_menu();

	return true;
}


bool ro_gui_search_click(wimp_pointer *pointer)
{
	search_flags_t flags;
	switch (pointer->i) {
		case ICON_SEARCH_FIND_PREV:
			search_data.search_insert = true;
			flags = ~SEARCH_FLAG_FORWARDS &
					ro_gui_search_update_flags();
			if (browser_window_search_verify_new(
					search_data.search_window,
					&ro_gui_search_callbacks, NULL))
				browser_window_search_step(
						search_data.search_window,
						flags,
						ro_gui_get_icon_string(
						dialog_search,
						ICON_SEARCH_TEXT));
			return true;
		case ICON_SEARCH_CASE_SENSITIVE:
			flags = SEARCH_FLAG_FORWARDS |
					ro_gui_search_update_flags();
			if (browser_window_search_verify_new(
					search_data.search_window,
					&ro_gui_search_callbacks, NULL))
				browser_window_search_step(
						search_data.search_window,
						flags,
						ro_gui_get_icon_string(
						dialog_search,
						ICON_SEARCH_TEXT));
			return true;
		case ICON_SEARCH_SHOW_ALL:
			/* TODO: call browser_window_search_verify_new() ? */
			browser_window_search_show_all(
					ro_gui_get_icon_selected_state(
					pointer->w, pointer->i),
					search_data.search_window);
			return true;
	}
	return false;
}

/**
 * add search string to recent searches list
 * front is at liberty how to implement the bare notification
 * should normally store a strdup() of the string in
 * search_global_data.recent[];
 * core gives no guarantee of the integrity of the const char *
 * \param string search pattern
 * \param p the pointer sent to search_verify_new()
 */

void ro_gui_search_add_recent(const char *search, void *p)
{
	char *tmp;
	int i;

	if ((search == NULL) || (search[0] == '\0'))
		return;

	if (!search_data.search_insert) {
		free(search_data.recent_searches[0]);
		search_data.recent_searches[0] = strdup(search);
		ro_gui_search_prepare_menu();
		return;
	}

	if ((search_data.recent_searches[0] != NULL) &&
			(!strcmp(search_data.recent_searches[0], search)))
		return;

	tmp = strdup(search);
	if (!tmp) {
		warn_user("NoMemory", 0);
		return;
	}
	free(search_data.recent_searches[RECENT_SEARCHES - 1]);
	for (i = RECENT_SEARCHES - 1; i > 0; i--)
		search_data.recent_searches[i] = search_data.recent_searches[i - 1];
	search_data.recent_searches[0] = tmp;
	search_data.search_insert = false;

	ro_gui_set_icon_shaded_state(dialog_search, ICON_SEARCH_MENU, false);
	ro_gui_search_prepare_menu();
}

bool ro_gui_search_prepare_menu(void)
{
	int i;
	int suggestions = 0;

	for (i = 0; i < RECENT_SEARCHES; i++)
		if (search_data.recent_searches[i] != NULL)
			suggestions++;

	if (suggestions == 0)
		return false;

	for (i = 0; i < suggestions; i++) {
		recent_search_menu->entries[i].menu_flags &= ~wimp_MENU_LAST;
		recent_search_menu->entries[i].data.indirected_text.text =
				search_data.recent_searches[i];
		recent_search_menu->entries[i].data.indirected_text.size =
				strlen(search_data.recent_searches[i]) + 1;
	}
	recent_search_menu->entries[suggestions - 1].menu_flags |=
			wimp_MENU_LAST;

	return true;
}

/**
 * Open the search dialog
 *
 * \param bw the browser window to search
 */
void ro_gui_search_prepare(struct browser_window *bw)
{
	hlcache_handle *h;

	assert(bw != NULL);

	h = bw->current_content;

	/* only handle html/textplain contents */
	if ((!h) || (content_get_type(h) != CONTENT_HTML &&
			content_get_type(h) != CONTENT_TEXTPLAIN))
		return;

	/* if the search dialogue is reopened over a new window, we may
	   need to cancel the previous search */
	ro_gui_search_set_forward_state(true, bw);
	ro_gui_search_set_back_state(true, bw);

	browser_window_search_create_context(bw,
			&ro_gui_search_callbacks, NULL);

	search_data.search_window = bw;

	ro_gui_set_icon_string(dialog_search, ICON_SEARCH_TEXT, "", true);
	ro_gui_set_icon_selected_state(dialog_search,
				ICON_SEARCH_CASE_SENSITIVE, false);
	ro_gui_set_icon_selected_state(dialog_search,
				ICON_SEARCH_SHOW_ALL, false);

	ro_gui_search_set_status(true, NULL);

	ro_gui_wimp_event_memorise(dialog_search);
	search_data.search_insert = true;
}

/**
 * Handle keypresses in the search dialog
 *
 * \param key wimp_key block
 * \return true if keypress handled, false otherwise
 */
bool ro_gui_search_keypress(wimp_key *key)
{
	bool state;
	search_flags_t flags;

	switch (key->c) {
		case 1: { /* ctrl a */
			bool sel = !ro_gui_get_icon_selected_state(key->w,
					ICON_SEARCH_SHOW_ALL);
			ro_gui_set_icon_selected_state(key->w,
					ICON_SEARCH_SHOW_ALL, sel);
			/* TODO: call browser_window_search_verify_new() ? */
			browser_window_search_show_all(sel,
						search_data.search_window);
		}
		break;
		case 9: /* ctrl i */
			state = ro_gui_get_icon_selected_state(dialog_search,
					ICON_SEARCH_CASE_SENSITIVE);
			ro_gui_set_icon_selected_state(dialog_search,
					ICON_SEARCH_CASE_SENSITIVE, !state);
			flags = SEARCH_FLAG_FORWARDS |
					ro_gui_search_update_flags();
			if (browser_window_search_verify_new(
					search_data.search_window,
					&ro_gui_search_callbacks, NULL))
				browser_window_search_step(
						search_data.search_window,
						flags,
						ro_gui_get_icon_string(
						dialog_search,
						ICON_SEARCH_TEXT));
			return true;
		case IS_WIMP_KEY | wimp_KEY_UP:
			search_data.search_insert = true;
			flags = ~SEARCH_FLAG_FORWARDS &
					ro_gui_search_update_flags();
			if (browser_window_search_verify_new(
					search_data.search_window,
					&ro_gui_search_callbacks, NULL))
				browser_window_search_step(
						search_data.search_window,
						flags,
						ro_gui_get_icon_string(
						dialog_search,
						ICON_SEARCH_TEXT));
			return true;
		case IS_WIMP_KEY | wimp_KEY_DOWN:
			search_data.search_insert = true;
			flags = SEARCH_FLAG_FORWARDS |
					ro_gui_search_update_flags();
			if (browser_window_search_verify_new(
					search_data.search_window,
					&ro_gui_search_callbacks, NULL))
				browser_window_search_step(
						search_data.search_window,
						flags,
						ro_gui_get_icon_string(
						dialog_search,
						ICON_SEARCH_TEXT));
			return true;

		default:
			if (key->c == 21) {
				/* ctrl+u means the user's starting
				 * a new search */
				browser_window_search_destroy_context(
						search_data.search_window);
				ro_gui_search_set_forward_state(true,
						search_data.search_window);
				ro_gui_search_set_back_state(true,
						search_data.search_window);
				search_data.search_insert = true;
			}
			if (key->c == 8  || /* backspace */
			    key->c == 21 || /* ctrl u */
			    (key->c >= 0x20 && key->c <= 0x7f)) {
				flags = SEARCH_FLAG_FORWARDS |
						ro_gui_search_update_flags();
				browser_window_search_destroy_context(
						search_data.search_window);
				ro_gui_search_set_forward_state(true,
						search_data.search_window);
				ro_gui_search_set_back_state(true,
						search_data.search_window);
				if (browser_window_search_verify_new(
						search_data.search_window,
						&ro_gui_search_callbacks,
						NULL))
					browser_window_search_step(
							search_data.
							search_window,
							flags,
							ro_gui_get_icon_string(
							dialog_search,
							ICON_SEARCH_TEXT));
				return true;
			}
			break;
	}

	return false;
}

/**
 * Ends the search
 * \param w the search window handle (not used)
 */
void ro_gui_search_end(wimp_w w)
{
	browser_window_search_destroy_context(search_data.search_window);
	ro_gui_search_set_forward_state(true, search_data.search_window);
	ro_gui_search_set_back_state(true, search_data.search_window);
}

/**
* Change the displayed search status.
* \param found  search pattern matched in text
* \param p the pointer sent to search_verify_new() / search_create_context()
*/

void ro_gui_search_set_status(bool found, void *p)
{
	ro_gui_set_icon_string(dialog_search, ICON_SEARCH_STATUS, found ? "" :
			messages_get("NotFound"), true);
}

/**
* display hourglass while searching
* \param active start/stop indicator
* \param p the pointer sent to search_verify_new() / search_create_context()
*/

void ro_gui_search_set_hourglass(bool active, void *p)
{
	if (active)
		xhourglass_on();

	else
		xhourglass_off();
}

/**
* activate search forwards button in gui
* \param active activate/inactivate
* \param p the pointer sent to search_verify_new() / search_create_context()
*/

void ro_gui_search_set_forward_state(bool active, void *p)
{
	ro_gui_set_icon_shaded_state(dialog_search, ICON_SEARCH_FIND_NEXT,
			!active);
}

/**
* activate search forwards button in gui
* \param active activate/inactivate
* \param p the pointer sent to search_verify_new() / search_create_context()
*/

void ro_gui_search_set_back_state(bool active, void *p)
{
	ro_gui_set_icon_shaded_state(dialog_search, ICON_SEARCH_FIND_PREV,
			!active);
}

/**
* retrieve state of 'case sensitive', 'show all' checks in gui
*/
search_flags_t ro_gui_search_update_flags(void)
{
	search_flags_t flags;
	flags = 0 | (ro_gui_get_icon_selected_state(dialog_search,
			ICON_SEARCH_CASE_SENSITIVE) ?
			SEARCH_FLAG_CASE_SENSITIVE : 0) |
			(ro_gui_get_icon_selected_state(dialog_search,
			ICON_SEARCH_SHOW_ALL) ? SEARCH_FLAG_SHOWALL : 0);
	return flags;
}

