/*
 * Copyright 2006 Richard Wilson <info@tinct.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
 * Automated RISC OS message routing (implementation).
 */

#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include "oslib/os.h"
#include "oslib/wimp.h"

#include "utils/log.h"

#include "riscos/message.h"
#include "riscos/gui.h"

struct active_message {
	unsigned int message_code;
	int id;
	void (*callback)(wimp_message *message);
	struct active_message *next;
	struct active_message *previous;
};
struct active_message *current_messages = NULL;

static struct active_message *ro_message_add(unsigned int message_code,
		void (*callback)(wimp_message *message));
static void ro_message_free(int ref);


/**
 * Sends a message and registers a return route for a bounce.
 *
 * \param event		the message event type
 * \param message	the message to register a route back for
 * \param task		the task to send a message to, or 0 for broadcast
 * \param callback	the code to call on a bounce
 * \return true on success, false otherwise
 */
bool ro_message_send_message(wimp_event_no event, wimp_message *message,
		wimp_t task, void (*callback)(wimp_message *message))
{
	os_error *error;

	assert(message);

	/* send a message */
	error = xwimp_send_message(event, message, task);
	if (error) {
		NSLOG(netsurf, INFO, "xwimp_send_message: 0x%x: %s",
		      error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return false;
	}

	/* register the default bounce handler */
	if (callback) {
		assert(event == wimp_USER_MESSAGE_RECORDED);
		return ro_message_register_handler(message, message->action,
				callback);
	}
	return true;
}


/**
 * Sends a message and registers a return route for a bounce.
 *
 * \param event		the message event type
 * \param message	the message to register a route back for
 * \param to_w		the window to send the message to
 * \param to_i		the icon
 * \param callback	the code to call on a bounce
 * \param to_t		receives the task handle of the window's creator
 * \return true on success, false otherwise
 */
bool ro_message_send_message_to_window(wimp_event_no event, wimp_message *message,
		wimp_w to_w, wimp_i to_i, void (*callback)(wimp_message *message),
		wimp_t *to_t)
{
	os_error *error;

	assert(message);

	/* send a message */
	error = xwimp_send_message_to_window(event, message, to_w, to_i, to_t);
	if (error) {
		NSLOG(netsurf, INFO,
		      "xwimp_send_message_to_window: 0x%x: %s",
		      error->errnum,
		      error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return false;
	}

	/* register the default bounce handler */
	if (callback) {
		assert(event == wimp_USER_MESSAGE_RECORDED);
		return ro_message_register_handler(message, message->action,
				callback);
	}
	return true;
}


/**
 * Registers a return route for a message.
 *
 * This function must be called after wimp_send_message so that a
 * valid value is present in the my_ref field.
 *
 * \param message	the message to register a route back for
 * \param message_code	the message action code to route
 * \param callback	the code to call for a matched action
 * \return true on success, false on memory exhaustion
 */
bool ro_message_register_handler(wimp_message *message,
		unsigned int message_code,
		void (*callback)(wimp_message *message))
{
	struct active_message *add;

	assert(message);
	assert(callback);

	add = ro_message_add(message_code, callback);
	if (add)
		add->id = message->my_ref;
	return (add != NULL);
}


/**
 * Registers a route for a message code.
 *
 * \param message_code	the message action code to route
 * \param callback	the code to call for a matched action
 * \return true on success, false on memory exhaustion
 */
bool ro_message_register_route(unsigned int message_code,
		void (*callback)(wimp_message *message))
{
	assert(callback);

	return (ro_message_add(message_code, callback) != NULL);
}

struct active_message *ro_message_add(unsigned int message_code,
		void (*callback)(wimp_message *message))
{
	struct active_message *add;

	assert(callback);

	add = (struct active_message *)malloc(sizeof(*add));
	if (!add)
		return NULL;
	add->message_code = message_code;
	add->id = 0;
	add->callback = callback;
	add->next = current_messages;
	add->previous = NULL;
	current_messages = add;
	return add;
}


/**
 * Attempts to route a message.
 *
 * \param event  wimp event
 * \param message  the message to attempt to route
 * \return true if message was routed, false otherwise
 */
bool ro_message_handle_message(wimp_event_no event, wimp_message *message)
{
	struct active_message *test;

	assert(message);

	if (event == wimp_USER_MESSAGE_ACKNOWLEDGE) {
		/* handle message acknowledgement */
		bool handled = false;
		int ref = message->my_ref;

		if (ref == 0)
			return false;

		/* handle the message */
		for (test = current_messages; test; test = test->next) {
			if ((ref == test->id) &&
					(message->action == test->message_code)) {
				handled = true;
				if (test->callback)
					test->callback(message);
				break;
			}
		}

		/* remove all handlers for this id */
		ro_message_free(ref);
		return handled;
	} else {
		/* handle simple routing */
		for (test = current_messages; test; test = test->next) {
			if ((test->id == 0) &&
					(message->action == test->message_code)) {
				test->callback(message);
				return true;
			}
		}
	}
	return false;
}


void ro_message_free(int ref)
{
	struct active_message *test;
	struct active_message *next = current_messages;

	while ((test = next)) {
		next = test->next;
		if (ref == test->id) {
			if (test->previous)
				test->previous->next = test->next;
			if (test->next)
				test->next->previous = test->previous;
			if (current_messages == test)
				current_messages = test->next;
			free(test);
		}
	}
}
