/* Event handling functions */

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

#include "elinks.h"

#include "main/event.h"
#include "util/error.h"
#include "util/hash.h"
#include "util/memory.h"
#include "util/snprintf.h"
#include "util/string.h"


/* First, we should set some terminology:
 *
 * o event -> Message being [triggerred] by some ELinks part and [catched]
 *   by various random other ones.
 *
 * o event hook -> Device being deployed by various ELinks parts,
 *   associated with certain [event It [catches] that event by having a
 *   handler executed when the [event] is triggerred.
 *
 * o event chain -> Line of [event hook]s associated with a given [event
 *   The [hook]s are ordered by [priority Each hook returns whenever should
 *   the chain continue or that no other events in the chain should be
 *   triggered (TODO).
 */

struct event_handler {
	/* The function to be called with the event data. */
	event_hook_T callback;

	/* The @priority of this handler. */
	int priority;

	/* Handler specific data. */
	void *data;
};

struct event {
	/* The event name has to be unique. */
	unsigned char *name;

	/* There are @count event @handlers all ordered by priority. */
	struct event_handler *handlers;
	unsigned int count;

	/* The unique event id and position in events. */
	int id;
};

static struct event *events = NULL;
static unsigned int eventssize = 0;
static struct hash *event_hash = NULL;

/* TODO: This should be tuned to the number of events.  When we will have a lot
 * of them, this should be some big enough number to reduce unnecessary
 * slavery of CPU on the startup. Then after all main modules will be
 * initialized and their events will be registered, we could call something
 * like adjust_events_list() which will tune it to the exactly needed number.
 * This should be also called after each new plugin loaded. */
#define EVENT_GRANULARITY 0x07

#define realloc_events(ptr, size) \
	mem_align_alloc(ptr, (size), (size) + 1, struct event, EVENT_GRANULARITY)

static inline int
invalid_event_id(register int id)
{
	return (id < 0 || id >= eventssize || id == EVENT_NONE);
}

int
register_event(unsigned char *name)
{
	int id = get_event_id(name);
	struct event *event;
	int namelen;

	if (id != EVENT_NONE) return id;

	event = events;
	if (!realloc_events(&events, eventssize)) return EVENT_NONE;

	/* If @events got relocated update the hash. */
	if (event != events) {
		for (id = 0; id < eventssize; id++) {
			struct hash_item *item;
			int len = strlen(events[id].name);

			item = get_hash_item(event_hash, events[id].name, len);

			if (item) item->value = &events[id];
		}
	}

	event = &events[eventssize];

	namelen = strlen(name);
	event->name = memacpy(name, namelen);
	if (!event->name) return EVENT_NONE;

	if (!add_hash_item(event_hash, event->name, namelen, event)) {
		mem_free(event->name);
		event->name = NULL;
		return EVENT_NONE;
	}

	event->handlers = NULL;
	event->count = 0;
	event->id = eventssize++;

	return event->id;
}

int
get_event_id(unsigned char *name)
{
	struct hash_item *item;
	int namelen;

	assertm(name && name[0], "Empty or missing event name");
	if_assert_failed return EVENT_NONE;

	if (!event_hash) return EVENT_NONE;

	namelen = strlen(name);
	item = get_hash_item(event_hash, name, namelen);
	if (item) {
		struct event *event = item->value;

		assertm(event, "Hash item with no value");
		if_assert_failed return EVENT_NONE;

		return event->id;
	}

	return EVENT_NONE;
}

unsigned char *
get_event_name(int id)
{
	if (invalid_event_id(id)) return NULL;

	return events[id].name;
}

static void
trigger_event_va(int id, va_list ap_init)
{
	int i;
	struct event_handler *ev_handler;

	if (invalid_event_id(id)) return;

	ev_handler = events[id].handlers;
	for (i = 0; i < events[id].count; i++, ev_handler++) {
		enum evhook_status ret;
		va_list ap;

		VA_COPY(ap, ap_init);
		ret = ev_handler->callback(ap, ev_handler->data);
		va_end(ap);

		if (ret == EVENT_HOOK_STATUS_LAST) return;
	}
}

void
trigger_event(int id, ...)
{
	va_list ap;

	va_start(ap, id);
	trigger_event_va(id, ap);
}

void
trigger_event_name(unsigned char *name, ...)
{
	va_list ap;
	int id = get_event_id(name);

	va_start(ap, name);
	trigger_event_va(id, ap);
}

static inline void
move_event_handler(struct event *event, int to, int from)
{
	int d = int_max(to, from);

	memmove(&event->handlers[to], &event->handlers[from],
		(event->count - d) * sizeof(*event->handlers));
}

int
register_event_hook(int id, event_hook_T callback, int priority, void *data)
{
	struct event *event;
	int i;

	assert(callback);
	if_assert_failed return EVENT_NONE;

	if (invalid_event_id(id)) return EVENT_NONE;

	event = &events[id];

	for (i = 0; i < event->count; i++)
		if (event->handlers[i].callback == callback) break;

	if (i == event->count) {
		struct event_handler *eh;

		eh = mem_realloc(event->handlers,
				 (event->count + 1) * sizeof(*event->handlers));

		if (!eh) return EVENT_NONE;

		event->handlers = eh;
		event->count++;
	} else {
		move_event_handler(event, i, i + 1);
	}

	for (i = 0; i < event->count - 1; i++)
		if (event->handlers[i].priority < priority) break;

	move_event_handler(event, i + 1, i);

	event->handlers[i].callback = callback;
	event->handlers[i].priority = priority;
	event->handlers[i].data = data;

	return id;
}

void
unregister_event_hook(int id, event_hook_T callback)
{
	struct event *event;

	assert(callback);
	if_assert_failed return;

	if (invalid_event_id(id)) return;

	event = &events[id];
	if (event->handlers) {
		int i;

		for (i = 0; i < event->count; i++) {
			if (event->handlers[i].callback != callback)
				continue;

			move_event_handler(event, i, i + 1);
			event->count--;
			if (!event->count) {
				mem_free(event->handlers);
				event->handlers = NULL;
			} else {
				struct event_handler *eh;

				eh = mem_realloc(event->handlers,
						 event->count * sizeof(*event->handlers));
				if (eh) event->handlers = eh;
			}

			break;
		}
	}
}

void
register_event_hooks(struct event_hook_info *hooks)
{
	int i;

	for (i = 0; hooks[i].name; i++) {
		int id = register_event(hooks[i].name);

		if (id == EVENT_NONE) continue;

		register_event_hook(id, hooks[i].callback, hooks[i].priority,
		                    hooks[i].data);
	}
}

void
unregister_event_hooks(struct event_hook_info *hooks)
{
	int i;

	for (i = 0; hooks[i].name; i++) {
		int id = get_event_id(hooks[i].name);

		if (id == EVENT_NONE) continue;

		unregister_event_hook(id, hooks[i].callback);
	}
}

void
init_event(void)
{
	event_hash = init_hash(8, strhash);
}

void
done_event(void)
{
	int i;

	if (event_hash)	free_hash(event_hash);

	for (i = 0; i < eventssize; i++) {
		mem_free_if(events[i].handlers);
		mem_free(events[i].name);
	}

	mem_free_set(&events, NULL);
	eventssize = 0;
}
