#include "unittest/TestSuite.h"
#include "screenmanager/ScreenManager.h"

void testInit() {
	ScreenManager screenManager, * screenManagerPtr;
	bool success;
	
	memset(&screenManager, 0x00, sizeof(screenManager));
	stemobject_assign_vtable(screenManager, ScreenManager);
	success = ScreenManager_init(&screenManager);
	TestCase_assert(success, "Expected true but got false");
	TestCase_assert(screenManager.vtable == &ScreenManager_class, "Expected %p but got %p", &ScreenManager_class, screenManager.vtable);
	TestCase_assert(screenManager.eventDispatcher != NULL, "Expected non-NULL but got NULL");
	ScreenManager_dispose(&screenManager);
	
	screenManagerPtr = ScreenManager_create();
	TestCase_assert(screenManagerPtr != NULL, "Expected non-NULL but got NULL");
	if (screenManagerPtr == NULL) {return;} // Suppress clang warning
	TestCase_assert(screenManagerPtr->vtable == &ScreenManager_class, "Expected %p but got %p", &ScreenManager_class, screenManagerPtr->vtable);
	TestCase_assert(screenManagerPtr->eventDispatcher != NULL, "Expected non-NULL but got NULL");
	ScreenManager_dispose(screenManagerPtr);
}

static void testTransitions() {
	ScreenManager * screenManager;
	Screen screen1, screen2, screen3;
	
	stemobject_assign_vtable(screen1, Screen);
	Screen_init(&screen1);
	stemobject_assign_vtable(screen2, Screen);
	Screen_init(&screen2);
	screenManager = ScreenManager_create();
	ScreenManager_addScreen(screenManager, &screen1);
	ScreenManager_addScreen(screenManager, &screen2);
	ScreenManager_addTransition(screenManager, &screen1, &screen2, "transition1");
	ScreenManager_addTransition(screenManager, &screen2, &screen1, "transition2");
	TestCase_assert(screenManager->currentScreen == NULL, "Expected NULL but got %p", screenManager->currentScreen);
	ScreenManager_setScreen(screenManager, &screen1);
	TestCase_assert(screenManager->currentScreen == &screen1, "Expected %p but got %p", &screen1, screenManager->currentScreen);
	ScreenManager_transition(screenManager, "transition1");
	TestCase_assert(screenManager->currentScreen == &screen2, "Expected %p but got %p", &screen2, screenManager->currentScreen);
	ScreenManager_transition(screenManager, "transition2");
	TestCase_assert(screenManager->currentScreen == &screen1, "Expected %p but got %p", &screen1, screenManager->currentScreen);
	ScreenManager_setScreen(screenManager, &screen2);
	TestCase_assert(screenManager->currentScreen == &screen2, "Expected %p but got %p", &screen2, screenManager->currentScreen);
	ScreenManager_dispose(screenManager);
	
	stemobject_assign_vtable(screen3, Screen);
	Screen_init(&screen3);
	screenManager = ScreenManager_create();
	ScreenManager_addScreen(screenManager, &screen1);
	ScreenManager_addScreen(screenManager, &screen2);
	ScreenManager_addScreen(screenManager, &screen3);
	ScreenManager_addTransition(screenManager, &screen1, &screen2, "transition");
	ScreenManager_addTransition(screenManager, &screen2, &screen3, "transition");
	ScreenManager_addTransition(screenManager, &screen3, &screen1, "transition1");
	ScreenManager_addTransition(screenManager, &screen3, &screen2, "transition2");
	TestCase_assert(screenManager->currentScreen == NULL, "Expected NULL but got %p", screenManager->currentScreen);
	ScreenManager_setScreen(screenManager, &screen1);
	TestCase_assert(screenManager->currentScreen == &screen1, "Expected %p but got %p", &screen1, screenManager->currentScreen);
	ScreenManager_transition(screenManager, "transition");
	TestCase_assert(screenManager->currentScreen == &screen2, "Expected %p but got %p", &screen2, screenManager->currentScreen);
	ScreenManager_transition(screenManager, "transition");
	TestCase_assert(screenManager->currentScreen == &screen3, "Expected %p but got %p", &screen3, screenManager->currentScreen);
	ScreenManager_transition(screenManager, "transition1");
	TestCase_assert(screenManager->currentScreen == &screen1, "Expected %p but got %p", &screen1, screenManager->currentScreen);
	ScreenManager_setScreen(screenManager, &screen3);
	TestCase_assert(screenManager->currentScreen == &screen3, "Expected %p but got %p", &screen3, screenManager->currentScreen);
	ScreenManager_transition(screenManager, "transition2");
	TestCase_assert(screenManager->currentScreen == &screen2, "Expected %p but got %p", &screen2, screenManager->currentScreen);
	ScreenManager_dispose(screenManager);
}

static void testInvalidTransitions() {
	ScreenManager * screenManager;
	Screen screen1, screen2;
	
	stemobject_assign_vtable(screen1, Screen);
	Screen_init(&screen1);
	stemobject_assign_vtable(screen2, Screen);
	Screen_init(&screen2);
	screenManager = ScreenManager_create();
	ScreenManager_setScreen(screenManager, &screen1);
	TestCase_assert(screenManager->currentScreen == NULL, "Expected NULL but got %p", screenManager->currentScreen);
	ScreenManager_addScreen(screenManager, &screen1);
	ScreenManager_setScreen(screenManager, &screen1);
	ScreenManager_addTransition(screenManager, &screen1, &screen2, "transition");
	ScreenManager_addTransition(screenManager, &screen2, &screen1, "transition");
	ScreenManager_addScreen(screenManager, &screen2);
	ScreenManager_transition(screenManager, "transition");
	TestCase_assert(screenManager->currentScreen == &screen1, "Expected %p but got %p", &screen1, screenManager->currentScreen);
	ScreenManager_setScreen(screenManager, &screen2);
	ScreenManager_transition(screenManager, "transition");
	TestCase_assert(screenManager->currentScreen == &screen2, "Expected %p but got %p", &screen2, screenManager->currentScreen);
	ScreenManager_dispose(screenManager);
}

typedef struct TestScreen TestScreen;
#define TestScreen_superclass Screen
#define TestScreen_ivars \
	Screen_ivars \
	unsigned int activateCalls; \
	unsigned int deactivateCalls; \
	unsigned int * callOrder; \
	Screen * screenParameter; \
	const char * transitionNameParameter;
#define TestScreen_vtable(self_type) \
	Screen_vtable(self_type)

stemobject_declare(TestScreen);

static bool TestScreen_init(TestScreen * self, unsigned int * callOrder);
static void TestScreen_activate(TestScreen * self, Screen * lastScreen, const char * transitionName);
static void TestScreen_deactivate(TestScreen * self, Screen * nextScreen, const char * transitionName);

#define stemobject_implementation TestScreen
stemobject_vtable_begin();
stemobject_vtable_entry(activate);
stemobject_vtable_entry(deactivate);
stemobject_vtable_end();

static bool TestScreen_init(TestScreen * self, unsigned int * callOrder) {
	call_super(init, self);
	self->activateCalls = 0;
	self->deactivateCalls = 0;
	self->callOrder = callOrder;
	self->screenParameter = NULL;
	self->transitionNameParameter = NULL;
	return true;
}

static void TestScreen_activate(TestScreen * self, Screen * lastScreen, const char * transitionName) {
	self->activateCalls++;
	*self->callOrder <<= 2;
	*self->callOrder |= 1;
	self->screenParameter = lastScreen;
	self->transitionNameParameter = transitionName;
}

static void TestScreen_deactivate(TestScreen * self, Screen * nextScreen, const char * transitionName) {
	self->deactivateCalls++;
	*self->callOrder <<= 2;
	*self->callOrder |= 2;
	self->screenParameter = nextScreen;
	self->transitionNameParameter = transitionName;
}

static void testScreens() {
	ScreenManager * screenManager;
	TestScreen screen1, screen2;
	unsigned int callOrder = 0;
	
	stemobject_assign_vtable(screen1, TestScreen);
	TestScreen_init(&screen1, &callOrder);
	stemobject_assign_vtable(screen2, TestScreen);
	TestScreen_init(&screen2, &callOrder);
	
	screenManager = ScreenManager_create();
	ScreenManager_addScreen(screenManager, &screen1);
	TestCase_assert(screen1.screenManager == screenManager, "Expected %p but got %p", screenManager, screen1.screenManager);
	ScreenManager_setScreen(screenManager, &screen1);
	TestCase_assert(screen1.activateCalls == 1, "Expected 1 but got %d", screen1.activateCalls);
	TestCase_assertPointerNULL(screen1.screenParameter);
	TestCase_assertPointerNULL(screen1.transitionNameParameter);
	
	callOrder = 0;
	ScreenManager_addScreen(screenManager, &screen2);
	ScreenManager_addTransition(screenManager, &screen1, &screen2, "transition");
	ScreenManager_transition(screenManager, "transition");
	TestCase_assert(screen1.deactivateCalls == 1, "Expected 1 but got %d", screen1.deactivateCalls);
	TestCase_assert(screen2.activateCalls == 1, "Expected 1 but got %d", screen2.activateCalls);
	TestCase_assert(callOrder == 0x9, "Expected 0x9 but got 0x%X", callOrder);
	TestCase_assertPointerEqual(screen1.screenParameter, &screen2);
	TestCase_assertStringEqual(screen1.transitionNameParameter, "transition");
	TestCase_assertPointerEqual(screen2.screenParameter, &screen1);
	TestCase_assertStringEqual(screen2.transitionNameParameter, "transition");
	
	callOrder = 0;
	ScreenManager_setScreen(screenManager, &screen1);
	TestCase_assert(screen2.deactivateCalls == 1, "Expected 1 but got %d", screen2.deactivateCalls);
	TestCase_assert(screen1.activateCalls == 2, "Expected 2 but got %d", screen1.activateCalls);
	TestCase_assert(callOrder == 0x9, "Expected 0x9 but got 0x%X", callOrder);
	TestCase_assertPointerEqual(screen2.screenParameter, &screen1);
	TestCase_assertPointerNULL(screen2.transitionNameParameter);
	TestCase_assertPointerEqual(screen1.screenParameter, &screen2);
	TestCase_assertPointerNULL(screen1.transitionNameParameter);
}

TEST_SUITE(ScreenManagerTest, testInit, testTransitions, testInvalidTransitions, testScreens)
