#include "unittest/TestSuite.h"
#include "utilities/Atom.h"
#include "utilities/EventDispatcher.h"

static bool callback1(Atom eventID, void * eventData, void * context) {
	*(int *) context += *(int *) eventData;
	TestCase_assert(eventID == Atom_fromString("event1"), "Expected %p \"event1\" but got %p \"%s\"", Atom_fromString("event1"), eventID, eventID);
	return true;
}

static bool callback2(Atom eventID, void * eventData, void * context) {
	*(int *) context += *(int *) eventData;
	TestCase_assert(eventID == Atom_fromString("event2"), "Expected %p \"event2\" but got %p \"%s\"", Atom_fromString("event2"), eventID, eventID);
	return false;
}

static bool callback3(Atom eventID, void * eventData, void * context) {
	*(int *) context += *(int *) eventData;
	return false;
}

static void testCallbacks() {
	EventDispatcher * dispatcher;
	int callback1Context = 0, callback2Context = 0;
	int incrementation = 2;
	bool handled;
	Atom event1Atom, event2Atom;
	
	dispatcher = EventDispatcher_create();
	
	event1Atom = Atom_fromString("event1");
	event2Atom = Atom_fromString("event2");
	handled = EventDispatcher_dispatchEvent(dispatcher, event1Atom, &incrementation);
	TestCase_assert(!handled, "Event handled when no callbacks are registered");
	
	EventDispatcher_registerForEvent(dispatcher, event1Atom, callback1, &callback1Context);
	handled = EventDispatcher_dispatchEvent(dispatcher, event1Atom, &incrementation);
	TestCase_assert(handled, "Event not handled when appropriate callback was registered");
	TestCase_assert(callback1Context == 2, "Expected 2 but got %d", callback1Context);
	
	incrementation = 3;
	EventDispatcher_dispatchEvent(dispatcher, event2Atom, &incrementation);
	TestCase_assert(callback1Context == 2, "Callback called for wrong type of event");
	
	EventDispatcher_registerForEvent(dispatcher, event2Atom, callback2, &callback2Context);
	handled = EventDispatcher_dispatchEvent(dispatcher, event2Atom, &incrementation);
	TestCase_assert(callback2Context == 3, "Expected 3 but got %d", callback2Context);
	TestCase_assert(!handled, "Expected false but got true");
	
	EventDispatcher_unregisterForEvent(dispatcher, event1Atom, callback1, NULL);
	handled = EventDispatcher_dispatchEvent(dispatcher, event1Atom, &incrementation);
	TestCase_assert(handled, "Event not still handled after unregistering callbacks with the wrong context");
	
	EventDispatcher_unregisterForEvent(dispatcher, event1Atom, callback1, &callback1Context);
	handled = EventDispatcher_dispatchEvent(dispatcher, event1Atom, &incrementation);
	TestCase_assert(!handled, "Event still handled after unregistering callbacks");
	
	incrementation = 1;
	handled = EventDispatcher_dispatchEvent(dispatcher, event2Atom, &incrementation);
	TestCase_assert(callback2Context == 4, "Expected 4 but got %d", callback2Context);
	TestCase_assert(!handled, "Expected false but got true");
	
	EventDispatcher_dispose(dispatcher);
}

struct removeDispatchContext {
	EventDispatcher * dispatcher;
	unsigned int callCount1;
	unsigned int callCount2;
	unsigned int lastCall;
};

static bool removedDispatchCallback(Atom eventID, void * eventData, void * context) {
	struct removeDispatchContext * contextStruct = context;
	contextStruct->callCount2++;
	contextStruct->lastCall = 2;
	return true;
}

static bool removeDispatchCallback(Atom eventID, void * eventData, void * context) {
	struct removeDispatchContext * contextStruct = context;
	contextStruct->callCount1++;
	contextStruct->lastCall = 1;
	EventDispatcher_unregisterForEvent(contextStruct->dispatcher, ATOM("event"), removedDispatchCallback, contextStruct);
	return true;
}

static void testRemoveDuringDispatch(void) {
	Atom eventAtom = ATOM("event");
	EventDispatcher * dispatcher = EventDispatcher_create();
	struct removeDispatchContext contextStruct = {dispatcher, 0, 0, 0};
	EventDispatcher_registerForEvent(dispatcher, eventAtom, removeDispatchCallback, &contextStruct);
	EventDispatcher_registerForEvent(dispatcher, eventAtom, removedDispatchCallback, &contextStruct);
	EventDispatcher_dispatchEvent(dispatcher, eventAtom, NULL);
	TestCase_assertUIntEqual(contextStruct.callCount1, 1);
	TestCase_assertUIntEqual(contextStruct.callCount2, 1);
	TestCase_assertUIntEqual(contextStruct.lastCall, 2);
	EventDispatcher_dispatchEvent(dispatcher, eventAtom, NULL);
	TestCase_assertUIntEqual(contextStruct.callCount1, 2);
	TestCase_assertUIntEqual(contextStruct.callCount2, 1);
	TestCase_assertUIntEqual(contextStruct.lastCall, 1);
	EventDispatcher_dispose(dispatcher);
}

static void testUnregisterAll(void) {
	Atom event1Atom = ATOM("event1"), event2Atom = ATOM("event2");
	int eventCount1 = 0, eventCount2 = 0, eventData = 1;
	EventDispatcher * dispatcher = EventDispatcher_create();
	EventDispatcher_registerForEvent(dispatcher, event1Atom, callback1, &eventCount1);
	EventDispatcher_registerForEvent(dispatcher, event1Atom, callback3, &eventCount2);
	EventDispatcher_registerForEvent(dispatcher, event2Atom, callback2, &eventCount2);
	EventDispatcher_dispatchEvent(dispatcher, event1Atom, &eventData);
	TestCase_assertIntEqual(eventCount1, 1);
	TestCase_assertIntEqual(eventCount2, 1);
	EventDispatcher_dispatchEvent(dispatcher, event2Atom, &eventData);
	TestCase_assertIntEqual(eventCount2, 2);
	
	EventDispatcher_unregisterAllForEventID(dispatcher, event1Atom);
	EventDispatcher_dispatchEvent(dispatcher, event1Atom, &eventData);
	TestCase_assertIntEqual(eventCount1, 1);
	TestCase_assertIntEqual(eventCount2, 2);
	EventDispatcher_dispatchEvent(dispatcher, event2Atom, &eventData);
	TestCase_assertIntEqual(eventCount2, 3);
	EventDispatcher_dispose(dispatcher);
	
	eventCount1 = eventCount2 = 0;
	dispatcher = EventDispatcher_create();
	EventDispatcher_registerForEvent(dispatcher, event1Atom, callback1, &eventCount1);
	EventDispatcher_registerForEvent(dispatcher, event1Atom, callback3, &eventCount1);
	EventDispatcher_registerForEvent(dispatcher, event2Atom, callback3, &eventCount2);
	EventDispatcher_dispatchEvent(dispatcher, event1Atom, &eventData);
	TestCase_assertIntEqual(eventCount1, 2);
	EventDispatcher_dispatchEvent(dispatcher, event2Atom, &eventData);
	TestCase_assertIntEqual(eventCount2, 1);
	
	EventDispatcher_unregisterAllForCallback(dispatcher, callback3);
	EventDispatcher_dispatchEvent(dispatcher, event1Atom, &eventData);
	TestCase_assertIntEqual(eventCount1, 3);
	EventDispatcher_dispatchEvent(dispatcher, event2Atom, &eventData);
	TestCase_assertIntEqual(eventCount2, 1);
	EventDispatcher_dispose(dispatcher);
	
	eventCount1 = eventCount2 = 0;
	dispatcher = EventDispatcher_create();
	EventDispatcher_registerForEvent(dispatcher, event1Atom, callback3, &eventCount2);
	EventDispatcher_registerForEvent(dispatcher, event1Atom, callback3, &eventCount1);
	EventDispatcher_registerForEvent(dispatcher, event2Atom, callback3, &eventCount2);
	EventDispatcher_dispatchEvent(dispatcher, event1Atom, &eventData);
	TestCase_assertIntEqual(eventCount1, 1);
	TestCase_assertIntEqual(eventCount2, 1);
	EventDispatcher_dispatchEvent(dispatcher, event2Atom, &eventData);
	TestCase_assertIntEqual(eventCount2, 2);
	
	EventDispatcher_unregisterAllForContext(dispatcher, &eventCount2);
	EventDispatcher_dispatchEvent(dispatcher, event1Atom, &eventData);
	TestCase_assertIntEqual(eventCount1, 2);
	TestCase_assertIntEqual(eventCount2, 2);
	EventDispatcher_dispatchEvent(dispatcher, event2Atom, &eventData);
	TestCase_assertIntEqual(eventCount2, 2);
	EventDispatcher_dispose(dispatcher);
}

TEST_SUITE(EventDispatcherTest,
           testCallbacks,
           testRemoveDuringDispatch,
           testUnregisterAll)
