// 8 june 2019
#include "uipriv.h"

struct controlType {
	uint32_t id;
	char *name;
	uiControlVtable vtable;
	uiControlOSVtable *osVtable;
	size_t implDataSize;
};

static int controlTypeCmp(const void *a, const void *b)
{
	const struct controlType *ca = (const struct controlType *) a;
	const struct controlType *cb = (const struct controlType *) b;

	if (ca->id < cb->id)
		return -1;
	if (ca->id > cb->id)
		return 1;
	return 0;
}

struct uiControl {
	uint32_t controlID;
	uint32_t typeID;
	struct controlType *type;
	void *implData;
	uiControl *parent;
};

static uiprivArray controlTypes = uiprivArrayStaticInit(struct controlType, 32, "uiControl type information");

#define controlTypeID UINT32_C(0x1F2E3C4D)

uint32_t uiControlType(void)
{
	if (!uiprivCheckInitializedAndThread())
		return 0;
	return controlTypeID;
}

static uint32_t nextControlID = UINT32_C(0x80000000);

uint32_t uiRegisterControlType(const char *name, uiControlVtable *vtable, uiControlOSVtable *osVtable, size_t implDataSize)
{
	struct controlType *ct;

	if (!uiprivCheckInitializedAndThread())
		return 0;
	if (vtable == NULL) {
		uiprivProgrammerErrorNullPointer("uiControlVtable", uiprivFunc);
		return 0;
	}
	if (vtable->Size != sizeof (uiControlVtable)) {
		uiprivProgrammerErrorWrongStructSize(vtable->Size, "uiControlVtable", uiprivFunc);
		return 0;
	}
#define checkMethod(method) \
	if (vtable->method == NULL) { \
		uiprivProgrammerErrorRequiredControlMethodMissing(name, "uiControlVtable", #method, uiprivFunc); \
		return 0; \
	}
	checkMethod(Init)
	checkMethod(Free)
#undef checkMethod

	if (osVtable == NULL) {
		uiprivProgrammerErrorNullPointer("uiControlOSVtable", uiprivFunc);
		return 0;
	}
	if (!uiprivOSVtableValid(osVtable, uiprivFunc))
		return 0;

	ct = (struct controlType *) uiprivArrayAppend(&controlTypes, 1);
	ct->id = nextControlID;
	nextControlID++;
	ct->name = uiprivStrdup(name);
	ct->vtable = *vtable;
	ct->osVtable = uiprivCloneOSVtable(osVtable);
	ct->implDataSize = implDataSize;
	return ct->id;
}

void *uiCheckControlType(void *c, uint32_t type)
{
	uiControl *cc = (uiControl *) c;
	struct controlType *got, *want;
	struct controlType key;

	if (!uiprivCheckInitializedAndThread())
		return NULL;
	if (c == NULL) {
		uiprivProgrammerErrorNullPointer("uiControl", uiprivFunc);
		return NULL;
	}
	if (cc->controlID != controlTypeID) {
		uiprivProgrammerErrorNotAControl(uiprivFunc);
		return NULL;
	}

	// now grab the type information for c itself
	// do this even if we were asked if this is a uiControl; we want to make absolutely sure this is a *real* uiControl
	memset(&key, 0, sizeof (struct controlType));
	key.id = cc->typeID;
	got = (struct controlType *) uiprivArrayBsearch(&controlTypes, &key, controlTypeCmp);
	if (got == NULL) {
		uiprivProgrammerErrorUnknownControlTypeUsed(cc->typeID, uiprivFunc);
		return NULL;
	}

	if (type == controlTypeID)
		// this is a real uiControl; no need to check further
		return c;

	// type isn't uiControlType(); make sure it is valid too
	memset(&key, 0, sizeof (struct controlType));
	key.id = type;
	want = (struct controlType *) uiprivArrayBsearch(&controlTypes, &key, controlTypeCmp);
	if (want == NULL) {
		uiprivProgrammerErrorUnknownControlTypeRequested(type, uiprivFunc);
		return NULL;
	}

	if (cc->typeID != type) {
		uiprivProgrammerErrorWrongControlType(got->name, want->name, uiprivFunc);
		return NULL;
	}
	return c;
}

#define callVtable(method, ...) ((*(method))(__VA_ARGS__))

uiControl *uiNewControl(uint32_t type, void *initData)
{
	uiControl *c;
	struct controlType *ct;
	struct controlType key;

	if (!uiprivCheckInitializedAndThread())
		return NULL;
	if (type == controlTypeID) {
		uiprivProgrammerErrorCannotCreateBaseControl(uiprivFunc);
		return NULL;
	}
	memset(&key, 0, sizeof (struct controlType));
	key.id = type;
	ct = (struct controlType *) uiprivArrayBsearch(&controlTypes, &key, controlTypeCmp);
	if (ct == NULL) {
		uiprivProgrammerErrorUnknownControlTypeRequested(type, uiprivFunc);
		return NULL;
	}

	c = uiprivNew(uiControl);
	c->controlID = controlTypeID;
	c->typeID = type;
	c->type = ct;
	if (ct->implDataSize != 0)
		c->implData = uiprivAlloc(ct->implDataSize, "uiControl implementation data");
	if (!callVtable(c->type->vtable.Init, c, c->implData, initData)) {
		uiprivProgrammerErrorInvalidControlInitData(ct->name, uiprivFunc);
		uiprivFree(c->implData);
		uiprivFree(c);
		return NULL;
	}
	return c;
}

void uiControlFree(uiControl *c)
{
	if (!uiprivCheckInitializedAndThread())
		return;
	if (c == NULL) {
		uiprivProgrammerErrorNullPointer("uiControl", uiprivFunc);
		return;
	}
	if (c->parent != NULL) {
		uiprivProgrammerErrorControlHasParent(uiprivFunc);
		return;
	}

	uiEventFire(uiControlOnFree(), c, NULL);
	uiEventInvalidateSender(uiControlOnFree(), c);

	callVtable(c->type->vtable.Free, c, c->implData);

	uiprivFree(c->implData);
	uiprivFree(c);
}

void *uiControlImplData(uiControl *c)
{
	if (!uiprivCheckInitializedAndThread())
		return NULL;
	if (c == NULL) {
		uiprivProgrammerErrorNullPointer("uiControl", uiprivFunc);
		return NULL;
	}
	return c->implData;
}