-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--


local pairs = pairs;
local t_insert = table.insert;
local t_remove = table.remove;
local t_sort = table.sort;
local setmetatable = setmetatable;
local next = next;

local _ENV = nil;
-- luacheck: std none

local function new()
	-- Map event name to ordered list of handlers (lazily built): handlers[event_name] = array_of_handler_functions
	local handlers = {};
	-- Array of wrapper functions that wrap all events (nil if empty)
	local global_wrappers;
	-- Per-event wrappers: wrappers[event_name] = wrapper_function
	local wrappers = {};
	-- Event map: event_map[handler_function] = priority_number
	local event_map = {};
	-- Debug hook, if any
	local active_debug_hook = nil;
	-- Called on-demand to build handlers entries
	local function _rebuild_index(self, event)
		local _handlers = event_map[event];
		if not _handlers or next(_handlers) == nil then return; end
		local index = {};
		for handler in pairs(_handlers) do
			t_insert(index, handler);
		end
		t_sort(index, function(a, b) return _handlers[a] > _handlers[b]; end);
		self[event] = index;
		return index;
	end;
	setmetatable(handlers, { __index = _rebuild_index });
	local function add_handler(event, handler, priority)
		local map = event_map[event];
		if map then
			map[handler] = priority or 0;
		else
			map = {[handler] = priority or 0};
			event_map[event] = map;
		end
		handlers[event] = nil;
	end;
	local function remove_handler(event, handler)
		local map = event_map[event];
		if map then
			map[handler] = nil;
			handlers[event] = nil;
			if next(map) == nil then
				event_map[event] = nil;
			end
		end
	end;
	local function get_handlers(event)
		return handlers[event];
	end;
	local function add_handlers(self)
		for event, handler in pairs(self) do
			add_handler(event, handler);
		end
	end;
	local function remove_handlers(self)
		for event, handler in pairs(self) do
			remove_handler(event, handler);
		end
	end;
	local function _fire_event(event_name, event_data)
		local h = handlers[event_name];
		if h and not active_debug_hook then
			for i=1,#h do
				local ret = h[i](event_data);
				if ret ~= nil then return ret; end
			end
		elseif h and active_debug_hook then
			for i=1,#h do
				local ret = active_debug_hook(h[i], event_name, event_data);
				if ret ~= nil then return ret; end
			end
		end
	end;
	local function fire_event(event_name, event_data)
		-- luacheck: ignore 432/event_name 432/event_data
		local w = wrappers[event_name] or global_wrappers;
		if w then
			local curr_wrapper = #w;
			local function c(event_name, event_data)
				curr_wrapper = curr_wrapper - 1;
				if curr_wrapper == 0 then
					if global_wrappers == nil or w == global_wrappers then
						return _fire_event(event_name, event_data);
					end
					w, curr_wrapper = global_wrappers, #global_wrappers;
					return w[curr_wrapper](c, event_name, event_data);
				else
					return w[curr_wrapper](c, event_name, event_data);
				end
			end
			return w[curr_wrapper](c, event_name, event_data);
		end
		return _fire_event(event_name, event_data);
	end
	local function add_wrapper(event_name, wrapper)
		local w;
		if event_name == false then
			w = global_wrappers;
			if not w then
				w = {};
				global_wrappers = w;
			end
		else
			w = wrappers[event_name];
			if not w then
				w = {};
				wrappers[event_name] = w;
			end
		end
		w[#w+1] = wrapper;
	end
	local function remove_wrapper(event_name, wrapper)
		local w;
		if event_name == false then
			w = global_wrappers;
		else
			w = wrappers[event_name];
		end
		if not w then return; end
		for i = #w, 1, -1 do
			if w[i] == wrapper then
				t_remove(w, i);
			end
		end
		if #w == 0 then
			if event_name == false then
				global_wrappers = nil;
			else
				wrappers[event_name] = nil;
			end
		end
	end

	local function set_debug_hook(new_hook)
		local old_hook = active_debug_hook;
		active_debug_hook = new_hook;
		return old_hook;
	end

	return {
		add_handler = add_handler;
		remove_handler = remove_handler;
		add_handlers = add_handlers;
		remove_handlers = remove_handlers;
		get_handlers = get_handlers;
		wrappers = {
			add_handler = add_wrapper;
			remove_handler = remove_wrapper;
		};

		add_wrapper = add_wrapper;
		remove_wrapper = remove_wrapper;

		set_debug_hook = set_debug_hook;

		fire_event = fire_event;
		_handlers = handlers;
		_event_map = event_map;
	};
end

return {
	new = new;
};
