#include "unittest/TestSuite.h"
#include "stemobject/StemObject.h"
#include <stdlib.h>

typedef struct TestObject1 TestObject1;
#define TestObject1_superclass StemObject

#define TestObject1_ivars \
	StemObject_ivars \
	int ivar1;

#define TestObject1_vtable(self_type) \
	StemObject_vtable(self_type) \
	void (* function1)(self_type * self, int amount);

stemobject_declare(TestObject1);

TestObject1 * TestObject1_create(void);
bool TestObject1_init(TestObject1 * self);
void TestObject1_initCopy(TestObject1 * self, TestObject1 * original);
void TestObject1_dispose(TestObject1 * self);
TestObject1 * TestObject1_copy(TestObject1 * self);
void TestObject1_function1(TestObject1 * self, int amount);


#define stemobject_implementation TestObject1

v_begin();
v_func(dispose);
v_func(function1);
v_end();

TestObject1 * TestObject1_create(void) {
	stemobject_create_implementation(init)
}

bool TestObject1_init(TestObject1 * self) {
	call_super(init, self);
	self->ivar1 = 10;
	return true;
}

void TestObject1_initCopy(TestObject1 * self, TestObject1 * original) {
	call_super(init, self);
	self->ivar1 = original->ivar1;
}

void TestObject1_dispose(TestObject1 * self) {
	call_super(dispose, self);
}

TestObject1 * TestObject1_copy(TestObject1 * self) {
	stemobject_copy_implementation(initCopy)
}

void TestObject1_function1(TestObject1 * self, int amount) {
	self->ivar1 += amount;
}

#undef stemobject_implementation


typedef struct TestObject2 TestObject2;
#define TestObject2_superclass TestObject1

#define TestObject2_ivars \
	TestObject1_ivars \
	int ivar2;

#define TestObject2_vtable(self_type) \
	TestObject1_vtable(self_type) \
	void (* function2)(self_type * self);

stemobject_declare(TestObject2);

TestObject2 * TestObject2_create(void);
bool TestObject2_init(TestObject2 * self);
void TestObject2_initCopy(TestObject2 * self, TestObject2 * original);
void TestObject2_dispose(TestObject2 * self);
TestObject2 * TestObject2_copy(TestObject2 * self);
void TestObject2_function1(TestObject2 * self, int amount);
void TestObject2_function2(TestObject2 * self);


#define stemobject_implementation TestObject2

v_begin();
v_func(dispose);
v_func(function1);
v_func(function2);
v_end();

TestObject2 * TestObject2_create(void) {
	stemobject_create_implementation(init)
}

bool TestObject2_init(TestObject2 * self) {
	call_super(init, self);
	self->ivar2 = 20;
	return true;
}

void TestObject2_initCopy(TestObject2 * self, TestObject2 * original) {
	call_super(initCopy, self, (TestObject1 *) original);
	self->ivar2 = original->ivar2;
}

void TestObject2_dispose(TestObject2 * self) {
	call_super(dispose, self);
}

TestObject2 * TestObject2_copy(TestObject2 * self) {
	stemobject_copy_implementation(initCopy)
}

void TestObject2_function1(TestObject2 * self, int amount) {
	self->ivar1 += amount * 5;
	call_super(function1, self, amount);
}

void TestObject2_function2(TestObject2 * self) {
	self->ivar2++;
}

#undef stemobject_implementation


static void testInit(void) {
	StemObject objectStruct;
	bool success;
	
	stemobject_assign_vtable(objectStruct, StemObject);
	success = StemObject_init(&objectStruct);
	TestCase_assert(success, "Expected true but got false");
	TestCase_assert(!objectStruct.protected_ivar(allocated), "Expected false but got true");
	TestCase_assert(objectStruct.vtable == &StemObject_class, "Expected %p but got %p", &StemObject_class, objectStruct.vtable);
	call_virtual(dispose, &objectStruct);
}

static void assertObject1(TestObject1 * object1) {
	TestCase_assert(object1 != NULL, "Expected non-NULL but got NULL");
	TestCase_assert(object1->vtable == &TestObject1_class, "Expected %p but got %p", &TestObject1_class, object1->vtable);
	TestCase_assert(object1->ivar1 == 10, "Expected 10 but got %d", object1->ivar1);
	call_virtual(function1, object1, 1);
	TestCase_assert(object1->ivar1 == 11, "Expected 11 but got %d", object1->ivar1);
}

static void assertObject2(TestObject2 * object2) {
	TestCase_assert(object2 != NULL, "Expected non-NULL but got NULL");
	TestCase_assert(object2->vtable == &TestObject2_class, "Expected %p but got %p", &TestObject2_class, object2->vtable);
	TestCase_assert(object2->ivar1 == 10, "Expected 10 but got %d", object2->ivar1);
	TestCase_assert(object2->ivar2 == 20, "Expected 20 but got %d", object2->ivar2);
	call_virtual(function1, object2, 1);
	TestCase_assert(object2->ivar1 == 16, "Expected 16 but got %d", object2->ivar1);
	call_virtual(function2, object2);
	TestCase_assert(object2->ivar2 == 21, "Expected 21 but got %d", object2->ivar2);
}

static void testVtable(void) {
	TestObject1 object1, * object1Ptr;
	TestObject2 object2, * object2Ptr;
	
	stemobject_assign_vtable(object1, TestObject1);
	TestCase_assertStringEqual(object1.vtable->vtableName, "TestObject1");
	TestObject1_init(&object1);
	assertObject1(&object1);
	call_virtual(dispose, &object1);
	
	object1Ptr = TestObject1_create();
	TestCase_assertStringEqual(object1Ptr->vtable->vtableName, "TestObject1");
	assertObject1(object1Ptr);
	call_virtual(dispose, object1Ptr);
	
	stemobject_assign_vtable(object2, TestObject2);
	TestCase_assertStringEqual(object2.vtable->vtableName, "TestObject2");
	TestObject2_init(&object2);
	assertObject2(&object2);
	call_virtual(dispose, &object2);
	
	object2Ptr = TestObject2_create();
	TestCase_assertStringEqual(object2Ptr->vtable->vtableName, "TestObject2");
	assertObject2(object2Ptr);
	call_virtual(dispose, object2Ptr);
}

static void testReflection(void) {
	StemObject stemObject;
	TestObject1 testObject1;
	TestObject2 testObject2;
	
	stemobject_assign_vtable(stemObject, StemObject);
	StemObject_init(&stemObject);
	stemobject_assign_vtable(testObject1, TestObject1);
	TestObject1_init(&testObject1);
	stemobject_assign_vtable(testObject2, TestObject2);
	TestObject2_init(&testObject2);
	
	TestCase_assert(StemObject_isExactClass(&stemObject, &StemObject_class), "Expected true but got false");
	TestCase_assert(!StemObject_isExactClass(&stemObject, &TestObject1_class), "Expected false but got true");
	TestCase_assert(!StemObject_isExactClass(&stemObject, &TestObject2_class), "Expected false but got true");
	TestCase_assert(StemObject_isClassOrSubclass(&stemObject, &StemObject_class), "Expected true but got false");
	TestCase_assert(!StemObject_isClassOrSubclass(&stemObject, &TestObject1_class), "Expected false but got true");
	TestCase_assert(!StemObject_isClassOrSubclass(&stemObject, &TestObject2_class), "Expected false but got true");
	
	TestCase_assert(!StemObject_isExactClass(&testObject1, &StemObject_class), "Expected false but got true");
	TestCase_assert(StemObject_isExactClass(&testObject1, &TestObject1_class), "Expected true but got false");
	TestCase_assert(!StemObject_isExactClass(&testObject1, &TestObject2_class), "Expected false but got true");
	TestCase_assert(StemObject_isClassOrSubclass(&testObject1, &StemObject_class), "Expected true but got false");
	TestCase_assert(StemObject_isClassOrSubclass(&testObject1, &TestObject1_class), "Expected true but got false");
	TestCase_assert(!StemObject_isClassOrSubclass(&testObject1, &TestObject2_class), "Expected false but got true");
	
	TestCase_assert(!StemObject_isExactClass(&testObject2, &StemObject_class), "Expected false but got true");
	TestCase_assert(!StemObject_isExactClass(&testObject2, &TestObject1_class), "Expected false but got true");
	TestCase_assert(StemObject_isExactClass(&testObject2, &TestObject2_class), "Expected true but got false");
	TestCase_assert(StemObject_isClassOrSubclass(&testObject2, &StemObject_class), "Expected true but got false");
	TestCase_assert(StemObject_isClassOrSubclass(&testObject2, &TestObject1_class), "Expected true but got false");
	TestCase_assert(StemObject_isClassOrSubclass(&testObject2, &TestObject2_class), "Expected true but got false");
}

static void testCopy(void) {
	TestObject1 * original1, * copy1;
	TestObject2 * original2, * copy2;
	
	original1 = TestObject1_create();
	original1->ivar1 = 123;
	copy1 = TestObject1_copy(original1);
	TestCase_assert(copy1 != NULL, "Expected non-NULL but got NULL");
	TestCase_assert(copy1->ivar1 == 123, "Expected 123 but got %d", copy1->ivar1);
	call_virtual(dispose, copy1);
	call_virtual(dispose, original1);
	
	original2 = TestObject2_create();
	original2->ivar1 = 456;
	original2->ivar2 = 789;
	copy2 = TestObject2_copy(original2);
	TestCase_assert(copy2 != NULL, "Expected non-NULL but got NULL");
	TestCase_assert(copy2->ivar1 == 456, "Expected 456 but got %d", copy2->ivar1);
	TestCase_assert(copy2->ivar2 == 789, "Expected 789 but got %d", copy2->ivar2);
	call_virtual(dispose, copy2);
	call_virtual(dispose, original2);
}

TEST_SUITE(StemObjectTest,
           testInit,
           testVtable,
           testReflection,
           testCopy)
