#include "WimpLib:EventSender.h"

#include "WimpLib:Array.h"
#include "WimpLib:Exception.h"
#include "WimpLib:Log.h"
#include "WimpLib:mem.h"
#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

/*
 * The list of listeners is maintained as a sorted array for dichotomic search.
 */

typedef struct Listener
{
	unsigned int        priority;
	int                 Type;
	const void*         pSender;
	void*               pListener;
	Listener_FOnEvent   pFOnEvent;
} Listener;

struct EventSender
{
	Array*              Listeners;
	unsigned int        ModifStamp;   // used to track add/remove of listeners while notifying listeners
};

// Constructor
EventSender* throw_New_EventSender(void)
{
	EventSender* This = throw_mem_alloc(sizeof(*This));

	try
	{
		This->ModifStamp = 0;
		This->Listeners = New_Array(sizeof(Listener), 128);
	}
	catch
	{
		mem_free(This);
		throw_current();
	}
	catch_end

	return This;
}

// Destructor
void Delete_EventSender(EventSender* This)
{
	if (This)
	{
		if (This->Listeners)
		{
			for (int i = 0; i < Array_Count(This->Listeners); i++)
			{
				const Listener* pL = Array_Get(This->Listeners, i);
				Log("Unreleased Listener %p\n", pL->pFOnEvent);
			}
		}
		Delete_Array(This->Listeners);
		mem_free(This);
	}
}

/**
 * Compare two listeners for insertion/location in the array of listeners.
 *
 * @param  pa  First listener.
 * @param  pb  Second listener.
 *
 * @returns < 0 if a < b, 0 if a = b, > 0 if a > b.
 */
static int EventSender_Compare(const Listener* pa, const Listener* pb)
{
	int val;

	val = pa->priority - pb->priority;
	if (!val) val = pa->Type - pb->Type;
	if (!val) val = ((char*) pa->pSender    - (char*) pb->pSender);
	if (!val) val = ((char*) pa->pListener  - (char*) pb->pListener);
	if (!val) val = ((char*) pa->pFOnEvent  - (char*) pb->pFOnEvent);

	return val;
}

/*
 * Returns where the listener should be located in the list of listeners
 * if it is/should be present, that is in range [0, count].
 *
 * @param  pi      Listener to locate.
 * @param  bExact  if true return -1 if not present.
 *
 * @returns  Index in Listeners array or -1.
 */
static int EventSender_Find(const EventSender* This, const Listener* pi, bool bExact)
{
	int i = 0;
	int k = Array_Count(This->Listeners) - 1;
	int j = (i + k) / 2;

	if (k >= 0)
	{
		int val = EventSender_Compare(Array_Get(This->Listeners, j), pi);

		while (i < j)
		{
			if (val >= 0) k = j; // Allow for duplicates
			else i = j;

			j = (i + k) / 2;
			val = EventSender_Compare(Array_Get(This->Listeners, j), pi);
		}

		if (bExact)
		{
			if (!val) return j;

			val = EventSender_Compare(Array_Get(This->Listeners, k), pi);
			if (!val) return k;

			return -1;
		}
		else
		{
			if (val >= 0) return j;

			val = EventSender_Compare(Array_Get(This->Listeners, k), pi);
			if (val >= 0) return k;

			return k + 1;
		}
	}

	return bExact ? -1 : 0;
}

/**
 * Adds a listener to the list of listeners.
 *
 * @param  priority    Priority of the listener.
 * @param  pSender     Origin of the event to listen to, NULL for any.
 * @param  Type        Type of event to listen to, 0xffffffff for any.
 * @param  pListener   Pointer to the listener's data.
 * @param  fn          Pointer to the function to call for notifying the listener.
 */
void throw_EventSender_AddListener
	( EventSender*          This
	, EListenerPriority     priority
	, const void*           pSender
	, int                   Type
	, void*                 pListener
	, Listener_FOnEvent     fn
	)
{
	Listener Info = {priority, Type, pSender, pListener, fn};
	int pos;

	pos = EventSender_Find(This, &Info, false);

	Array_Insert(This->Listeners, pos, &Info);

#ifdef TEST_EventSender
	printf("Added prio %d for (%d, %p) list (%p, %p)\n"
		, priority, Type, pSender, pListener, fn);
#endif

	This->ModifStamp++;
}

/**
 * Removes a listener from the list of listeners.
 */
void EventSender_RemoveListener
	( EventSender*          This
	, EListenerPriority     priority
	, const void*           pSender
	, int                   Type
	, void*                 pListener
	, Listener_FOnEvent     fn
	)
{
	Listener Info = {priority, Type, pSender, pListener, fn};
	int pos;

	pos = EventSender_Find(This, &Info, true);

	if (pos >= 0)
	{
		Array_Remove(This->Listeners, pos);

#ifdef TEST_EventSender
	printf("Remov prio %d for (%d, %p) list (%p, %p)\n"
		, priority, Type, pSender, pListener, fn);
#endif
		This->ModifStamp++;
	}
}

/**
 * Notify all concerned listeners of an event.
 * Concerned listeners are:
 * - listeners grabbing any event type
 * - listeners grabbing the correct event type.
 * Listeners are notified by order of priority but if a listener returns:
 * - EListenerAction_StopEvent, the notification stops there.
 * - EListenerAction_RestartEvent, the notification restarts from the top.
 *
 * @param  pSender     Origin of the event.
 * @param  Type        Type of the event.
 * @param  pData       Pointer to the data of the event.
 */
void EventSender_NotifyListeners
	( EventSender*    This
	, const void*     pSender
	, int             Type
	, const void*     pData
	)
{
	Listener Info = {EListenerPriority_PreFilter_Top, EListenerType_Global, NULL, NULL, NULL};
	Event e = {pSender, Type, pData};
	unsigned int Action;
	int pos;

#ifdef TEST_EventSender
	printf("Start Notif for (%d, %p, %p)\n", Type, pSender, pData);
#endif

	while(true)
	{
		// Check modifications of list while looping in there
		// in which case we abort the loop end re-enter the procedure
		int count = Array_Count(This->Listeners);
		unsigned int stamp = This->ModifStamp;

		Action = EListenerAction_ContinueEvent;

		while (Info.priority <= EListenerPriority_PostFilter_Bottom)
		{
			pos = EventSender_Find(This, &Info, false);

			// Loop on handlers of a given type and priority
			for(;(stamp == This->ModifStamp) && (Action == EListenerAction_ContinueEvent) && (pos < count); pos++)
			{
				Listener* pi = Array_Get(This->Listeners, pos);

				if ((Info.priority != pi->priority)
				||  (Info.Type != pi->Type))
					break;

				Info = *pi;

				if (!pi->pSender || (pi->pSender == pSender))
				{
#ifdef TEST_EventSender
	printf("Notif prio %d for (%d, %p, %p) list (%p, %p)\n"
		, pi->priority, pi->Type, pi->pSender, pData, pi->pListener, pi->pFOnEvent);
#endif
					Action = pi->pFOnEvent(pi->pListener, &e);
				}
			}

			if ((stamp != This->ModifStamp) || (Action != EListenerAction_ContinueEvent))
				break;

			if (Info.Type == EListenerType_Global)
			{
				// Same priority level, specific handlers;
				Info.Type = Type;
			}
			else
			{
				// Next priority level, global handlers
				Info.priority++;
				Info.Type = EListenerType_Global;
			}
			Info.pSender = NULL;
			Info.pListener = NULL;
			Info.pFOnEvent = NULL;
		}

		// If Restart forced, move back to beginning of list
		if (Action == EListenerAction_RestartEvent)
		{
			Info.priority = EListenerPriority_PreFilter_Top;
			Info.Type = EListenerType_Global;
			Info.pSender = NULL;
			Info.pListener = NULL;
			Info.pFOnEvent = NULL;

#ifdef TEST_EventSender
	printf("Restart Event Action\n");
#endif

			continue;
		}

		// Exit if action is Stop or if list of listeners was not modified
		if ((stamp == This->ModifStamp) || (Action == EListenerAction_StopEvent))
		{
#ifdef TEST_EventSender
	if (Action == EListenerAction_StopEvent)
		printf("Action Stop\n");
#endif

			break;
		}

		// Move to next listener
		pos = EventSender_Find(This, &Info, true);
		if ((pos != -1) // May have been deleted
		&&  ((pos + 1) < Array_Count(This->Listeners)))
			Info = *(Listener*) Array_Get(This->Listeners, pos + 1);

#ifdef TEST_EventSender
	printf("Modified, Action Continue\n");
#endif
	}

#ifdef TEST_EventSender
	printf("End Notif for (%d, %p, %p)\n", Type, pSender, pData);
#endif
}

#ifdef TEST_EventSender
static EventSender* pEd = NULL;

static EListenerAction lstN(void* pListener, const Event* pEvent)
{
	EventSender_NotifyListeners(pEd, NULL, 99, (void*) 0x8000);

	return EListenerAction_ContinueEvent;
}

static EListenerAction lstC(void* pListener, const Event* pEvent)
{
	return EListenerAction_ContinueEvent;
}

static EListenerAction lstS(void* pListener, const Event* pEvent)
{
	return EListenerAction_StopEvent;
}

static EListenerAction lstR(void* pListener, const Event* pEvent)
{
	static int counter = 1;

	if (counter)
	{
		counter = 0;
		return EListenerAction_RestartEvent;
	}
	return EListenerAction_ContinueEvent;
}

const char ProgramName[] = "Test";

static EListenerAction lstD(void* pListener, const Event* pEvent)
{
	EventSender_RemoveListener(pListener, EListenerPriority_PreFilter, NULL, 1, (void*) 999, lstC);
	return EListenerAction_ContinueEvent;
}

void main(void)
{
	exception_initialise();
	mem_init(0);

	pEd = throw_New_EventSender();

	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, (void*) 10, 4, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, (void*) 10, 2, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, (void*) 10, 3, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, (void*) 10, 5, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, (void*) 40, 1, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, (void*) 30, 1, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, (void*) 50, 1, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, (void*) 10, 1, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, NULL, 1, (void*) 400, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, NULL, 1, (void*) 500, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, NULL, 1, (void*) 300, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Normal, NULL, 1, (void*) 200, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_PreFilter_Top, NULL, EListenerType_Global, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_PreFilter, NULL, EListenerType_Global, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Top, NULL, EListenerType_Global, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Bottom, NULL, 1, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_PostFilter, NULL, 1, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_PostFilter_Bottom, NULL, 1, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_PreFilter, NULL, 1, (void*) 999, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_Top, NULL, 1, pEd, lstD);
	throw_EventSender_AddListener(pEd, EListenerPriority_PostFilter_Bottom, NULL, 1, pEd+1, lstS);
	throw_EventSender_AddListener(pEd, EListenerPriority_PostFilter_Bottom, NULL, 1, pEd+2, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_PostFilter_Bottom, NULL, 9, (void*) 100, lstC);
	throw_EventSender_AddListener(pEd, EListenerPriority_PostFilter_Bottom, NULL, 9, (void*) 100, lstR);
	throw_EventSender_AddListener(pEd, EListenerPriority_PostFilter_Bottom, NULL, 9, (void*) 100, lstN);

	for (int i = 0; i < Array_Count(pEd->Listeners); i++)
	{
		Listener* pi = Array_Get(pEd->Listeners, i);

		printf("Notif prio %d for (%d, %p) list (%p, %p)\n"
			, pi->priority, pi->Type, pi->pSender, pi->pListener, pi->pFOnEvent);
	}
	EventSender_NotifyListeners(pEd, (void*) 3, 1, NULL);
	EventSender_NotifyListeners(pEd, (void*) 3, 9, NULL);
	EventSender_NotifyListeners(pEd, (void*) 40, 1, NULL);
}
#endif
#ifdef __cplusplus
}
#endif
