// 19 april 2019
#include "uipriv.h"
#include "testhooks.h"

enum {
	stateUninitialized,
	stateBeforeMain,
	stateInMain,
	stateQuitting,
	stateAfterMain,
	stateError,
};

static int state = stateUninitialized;

#define initialized() (state != stateUninitialized && state != stateError)

bool testHookInitShouldFail = false;

void uiprivTestHookSetInitShouldFailArtificially(bool shouldFail)
{
	testHookInitShouldFail = shouldFail;
}

bool uiInit(void *options, uiInitError *err)
{
	if (state != stateUninitialized) {
		uiprivProgrammerErrorMultipleCalls(uiprivFunc);
		state = stateError;
		return false;
	}
	if (options != NULL) {
		uiprivProgrammerErrorBadInitOptions(uiprivFunc);
		state = stateError;
		return false;
	}
	if (err == NULL) {
		uiprivProgrammerErrorNullPointer("uiInitError", uiprivFunc);
		state = stateError;
		return false;
	}
	if (err->Size != sizeof (uiInitError)) {
		uiprivProgrammerErrorWrongStructSize(err->Size, "uiInitError", uiprivFunc);
		state = stateError;
		return false;
	}

	if (testHookInitShouldFail) {
		state = stateError;
		return uiprivInitReturnErrorf(err, "general failure");
	}
	if (!uiprivSysInit(options, err)) {
		state = stateError;
		return false;
	}
	state = stateBeforeMain;
	return true;
}

bool uiprivInitReturnErrorf(uiInitError *err, const char *fmt, ...)
{
	int n;
	va_list ap;

	va_start(ap, fmt);
	n = uiprivVsnprintf(err->Message, 256, fmt, ap);
	va_end(ap);
	if (n < 0) {
		uiprivInternalError("encoding error returning initialization error; this means something is very very wrong with libui itself");
		abort();		// TODO handle this scenario more gracefully
	}
	if (n >= 256) {
		// the formatted message is too long; truncate it
		err->Message[252] = '.';
		err->Message[253] = '.';
		err->Message[254] = '.';
		err->Message[255] = '\0';
	}
	return false;
}

void uiMain(void)
{
	if (!uiprivCheckInitializedAndThread())
		return;
	if (state != stateBeforeMain) {
		uiprivProgrammerErrorMultipleCalls(uiprivFunc);
		return;
	}
	state = stateInMain;
	uiprivSysMain();
	state = stateAfterMain;
}

void uiQuit(void)
{
	if (!uiprivCheckInitializedAndThread())
		return;
	if (state == stateQuitting || state == stateAfterMain) {
		uiprivProgrammerErrorMultipleCalls(uiprivFunc);
		return;
	}
	if (state != stateInMain) {
		// the above handle the other states, so stateBeforeMain is what's left
		uiprivProgrammerErrorQuitBeforeMain(uiprivFunc);
		return;
	}
	state = stateQuitting;
	uiprivSysQuit();
}

void uiQueueMain(void (*f)(void *data), void *data)
{
	if (!initialized()) {
		uiprivProgrammerErrorNotInitialized(uiprivFunc);
		return;
	}
	uiprivSysQueueMain(f, data);
}

bool uiprivCheckInitializedAndThreadImpl(const char *func)
{
	// While it does seem risky to not lock this, if this changes during the execution of this function it means only that it was changed from a different thread, and since it can only change from false to true, an error will be reported anyway.
	if (!initialized()) {
		uiprivProgrammerErrorNotInitialized(func);
		return false;
	}
	if (!uiprivSysCheckThread()) {
		uiprivProgrammerErrorWrongThread(func);
		return false;
	}
	return true;
}