#include <wx/thread.h>

#include "flexilog.h"

#include "countable.h"
#include "smartptr.h"

/******************************
****    ASSIGNMENT TEST    ****
******************************/

class AssignmentTestObjectAtomic: public VRUT::CountableCounterTemplate<VRUT::wxAtomicReferenceCounter>
{
public:
	volatile int i;
};

class AssignmentTestObjectMutex: public VRUT::CountableCounterTemplate<VRUT::wxMutexReferenceCounter>
{
public:
	volatile int i;
};

class AssignmentTestObjectInt: public VRUT::CountableCounterTemplate<VRUT::IntReferenceCounter>
{
public:
	volatile int i;
};


void RunAssignmentSpeedTest()
{
	const int number_of_assignments = 100000000;
	const int number_of_assignments_mutex = number_of_assignments / 100;

	VRUT::smart_pointer_v2<AssignmentTestObjectAtomic> a_obj1 = new AssignmentTestObjectAtomic();
	VRUT::smart_pointer_v2<AssignmentTestObjectAtomic> a_obj2 = new AssignmentTestObjectAtomic();
	VRUT::smart_pointer_v2<AssignmentTestObjectAtomic> a_tmp;

	VRUT::smart_pointer_v2<AssignmentTestObjectMutex> m_obj1 = new AssignmentTestObjectMutex();
	VRUT::smart_pointer_v2<AssignmentTestObjectMutex> m_obj2 = new AssignmentTestObjectMutex();
	VRUT::smart_pointer_v2<AssignmentTestObjectMutex> m_tmp;

	VRUT::smart_pointer_v2<AssignmentTestObjectInt> i_obj1 = new AssignmentTestObjectInt();
	VRUT::smart_pointer_v2<AssignmentTestObjectInt> i_obj2 = new AssignmentTestObjectInt();
	VRUT::smart_pointer_v2<AssignmentTestObjectInt> i_tmp;

	wxULongLong time_begin, time_end;

	LOG("RunAssignmentSpeedTest start");

	// Atomic
	time_begin = VRUTGetTime();
	for (int i = 0; i < number_of_assignments; i++)
	{
		a_tmp = a_obj2;			// One increment
		a_obj2 = a_obj1;		// One decrement, one increment
		a_obj1 = a_tmp;			// One decrement, one increment
		a_tmp = NULL;			// One decrement
	}
	time_end = VRUTGetTime();
	LOG(wxString::Format("RunAssignmentSpeedTest Atomic, number of operations: %i thousand, time: %f ms", 6*number_of_assignments/1000, float((time_end-time_begin).ToDouble())));

	// Mutex
	time_begin = VRUTGetTime();
	for (int i = 0; i < number_of_assignments_mutex; i++)
	{
		m_tmp = m_obj2;			// One increment
		m_obj2 = m_obj1;		// One decrement, one increment
		m_obj1 = m_tmp;			// One decrement, one increment
		m_tmp = NULL;			// One decrement
	}
	time_end = VRUTGetTime();
	LOG(wxString::Format("RunAssignmentSpeedTest Mutex, number of operations: %i thousand, time: %f ms", 6*number_of_assignments_mutex/1000, float((time_end-time_begin).ToDouble())));

	// Int
	time_begin = VRUTGetTime();
	for (int i = 0; i < number_of_assignments; i++)
	{
		i_tmp = i_obj2;			// One increment
		i_obj2 = i_obj1;		// One decrement, one increment
		i_obj1 = i_tmp;			// One decrement, one increment
		i_tmp = NULL;			// One decrement
	}
	time_end = VRUTGetTime();
	LOG(wxString::Format("RunAssignmentSpeedTest Int, number of operations: %i thousand, time: %f ms", 6*number_of_assignments/1000, float((time_end-time_begin).ToDouble())));

	LOG("RunAssignmentSpeedTest end");
}

/**********************************
****    MULTITHREADING TEST    ****
**********************************/

class MultithreadingTestObjectAtomic: public VRUT::CountableCounterTemplate<VRUT::wxAtomicReferenceCounter>
{
public:
	MultithreadingTestObjectAtomic(): VRUT::CountableCounterTemplate<VRUT::wxAtomicReferenceCounter>()
	{
		LOG(wxString("MultithreadingTestObject created"));
	}

	virtual ~MultithreadingTestObjectAtomic()
	{
		LOG(wxString("MultithreadingTestObject destroyed"));
	}

	volatile int i;

	inline VRUT::CounterType GetNumberOfReferences()	{	return counter.GetCount();	}
};

class MultithreadingTestObjectMutex: public VRUT::CountableCounterTemplate<VRUT::wxMutexReferenceCounter>
{
public:
	MultithreadingTestObjectMutex(): VRUT::CountableCounterTemplate<VRUT::wxMutexReferenceCounter>()
	{
		LOG(wxString("MultithreadingTestObject created"));
	}

	virtual ~MultithreadingTestObjectMutex()
	{
		LOG(wxString("MultithreadingTestObject destroyed"));
	}

	volatile int i;

	inline VRUT::CounterType GetNumberOfReferences()	{	return counter.GetCount();	}
};

template <class TestObjectType>
class MultithreadingTestThread: public wxThread
{
public:
	int thread_id;
	int array_size;
	VRUT::smart_pointer_v2<TestObjectType> test_object;

	MultithreadingTestThread() : wxThread(wxTHREAD_JOINABLE), thread_id(-1), array_size(0) {}

protected:
	virtual void *Entry() override
	{
		LOG(wxString::Format("MultithreadingTestThread %i starts", thread_id));

		VRUT::smart_pointer_v2<TestObjectType> *my_array = new VRUT::smart_pointer_v2<TestObjectType>[array_size];

		LOG(wxString::Format("MultithreadingTestThread %i: array state: empty (start), ref count: %i", thread_id, int(test_object->GetNumberOfReferences())));
		for (int i = 0; i < array_size; i++)
		{
			my_array[i] = test_object;
		}
		LOG(wxString::Format("MultithreadingTestThread %i: array state: full, ref count: %i", thread_id, int(test_object->GetNumberOfReferences())));
		for (int i = 0; i < array_size; i++)
		{
			my_array[i] = NULL;
		}
		LOG(wxString::Format("MultithreadingTestThread %i: array state: empty (end), ref count: %i", thread_id, int(test_object->GetNumberOfReferences())));

		delete[] my_array;			// The array is full of pointers to null

		LOG(wxString::Format("MultithreadingTestThread %i ends", thread_id));
		return NULL;
	}
};

void RunMultithreadingSpeedTest()
{
	const int number_of_threads = 16;
	const int array_size = 100000;

	LOG(wxString("RunMultithreadingSpeedTest starts"));

	/////////// Atomic
	VRUT::smart_pointer_v2<MultithreadingTestObjectAtomic> test_obj_atomic = new MultithreadingTestObjectAtomic();
	LOG(wxString::Format("RunMultithreadingSpeedTest Atomic starts: ref count: %i (should be 1)", int(test_obj_atomic->GetNumberOfReferences())));
	
	MultithreadingTestThread<MultithreadingTestObjectAtomic> *threads_atomic = new MultithreadingTestThread<MultithreadingTestObjectAtomic>[number_of_threads];
	for (int i = 0; i < number_of_threads; i++)
	{
		threads_atomic[i].thread_id = i;
		threads_atomic[i].array_size = array_size;
		threads_atomic[i].test_object = test_obj_atomic;
		threads_atomic[i].Create();
	}

	// Run threads
	for (int i = 0; i < number_of_threads; i++)		threads_atomic[i].Run();
	// Wait for threadsgrep
	
	for (int i = 0; i < number_of_threads; i++)		threads_atomic[i].Wait(
#if wxCHECK_VERSION(2, 9, 2)
																		   wxTHREAD_WAIT_DEFAULT
#endif																		   
																		   );

	delete[] threads_atomic;
	LOG(wxString::Format("RunMultithreadingSpeedTest Atomic ends: ref count: %i (should be 1)", int(test_obj_atomic->GetNumberOfReferences())));

	/////////// Mutex
	VRUT::smart_pointer_v2<MultithreadingTestObjectMutex> test_obj_mutex = new MultithreadingTestObjectMutex();
	LOG(wxString::Format("RunMultithreadingSpeedTest Mutex starts: ref count: %i (should be 1)", int(test_obj_atomic->GetNumberOfReferences())));
	
	MultithreadingTestThread<MultithreadingTestObjectMutex> *threads_mutex = new MultithreadingTestThread<MultithreadingTestObjectMutex>[number_of_threads];
	for (int i = 0; i < number_of_threads; i++)
	{
		threads_mutex[i].thread_id = i;
		threads_mutex[i].array_size = array_size;
		threads_mutex[i].test_object = test_obj_mutex;
		threads_mutex[i].Create();
	}

	// Run threads
	for (int i = 0; i < number_of_threads; i++)		threads_mutex[i].Run();
	// Wait for threads
	for (int i = 0; i < number_of_threads; i++)		threads_mutex[i].Wait(
#if wxCHECK_VERSION(2, 9, 2)
																		  wxTHREAD_WAIT_DEFAULT
#endif
																		  );

	delete[] threads_mutex;
	LOG(wxString::Format("RunMultithreadingSpeedTest Mutex ends: ref count: %i (should be 1)", int(test_obj_mutex->GetNumberOfReferences())));

	LOG("RunMultithreadingSpeedTest ends: MultithreadingTestObject destructors should follow");
}

#define TEST_COMPILER_ALLOWS
//#define TEST_COMPILER_ERRORS
#if (defined TEST_COMPILER_ALLOWS) || (defined TEST_COMPILER_ERRORS)

class A: public VRUT::Countable
{
public:
	int a;
};

class B: public A
{
public:
	int b;
};

class C: public B
{
public:
	int c;
};

class D: public VRUT::Countable
{
public:
	int d;
};

class E		// No countable
{
public:
	int e;
};

#ifdef TEST_COMPILER_ALLOWS
template <template <typename> class SmartPointerType> void testCompilerAllows_subtest1()
{
	SmartPointerType<A> a1, a2;
	SmartPointerType<B> b1, b2;
	SmartPointerType<C> c1, c2;
	SmartPointerType<D> d1, d2;

	a1 = new A();
	b1 = new B();
	c1 = new C();
	d1 = new D();
	a1 = new B();
	a2 = a1;
	b2 = b1;
	c2 = c1;
	d2 = d1;
		
	SmartPointerType<A> a3(a1);
	SmartPointerType<B> b3(b1);
	SmartPointerType<C> c3(c1);
	SmartPointerType<A> a4(c2);
	SmartPointerType<D> d3(d1);
	
	b1 = new B(*b2);

	a1 = b2;
	a2 = c1;

	a1 = NULL;
	b1 = NULL;
	c1 = NULL;
	// And by the way, no memory leak should occur
}

void testCompilerAllows()
{
	// Test constructs which should be allowed by compiler
	testCompilerAllows_subtest1<VRUT::smart_pointer_v2>();
	testCompilerAllows_subtest1<VRUT::const_smart_pointer>();

	VRUT::smart_pointer_v2<A> sp_a;
	VRUT::smart_pointer_v2<B> sp_b;
	VRUT::smart_pointer_v2<C> sp_c;
	VRUT::smart_pointer_v2<D> sp_d;

	VRUT::const_smart_pointer<A> csp_a;
	VRUT::const_smart_pointer<B> csp_b;
	VRUT::const_smart_pointer<C> csp_c;
	VRUT::const_smart_pointer<D> csp_d;

	sp_a = new A();
	sp_b = new B();
	sp_c = new C();
	sp_d = new D();
	csp_a = new A();
	csp_b = new B();
	csp_c = new C();
	csp_d = new D();

	sp_b->b = 10;
	sp_a = sp_c.getPtr();
	sp_a = sp_b.getPtr();
	sp_b.assign_with_dynamic_cast(sp_a);
	sp_c.assign_with_dynamic_cast(sp_a.getPtr());

	csp_c = sp_c;
	csp_d = sp_d;
	csp_b.assign_with_dynamic_cast(csp_a);
	csp_d.assign_with_dynamic_cast(sp_a.getPtr());			// This will fail, but the result is that csp_d points to NULL, no exception

	VRUT::const_smart_pointer<A> csp_a2(sp_a);

	// Test old smart_pointer
	testCompilerAllows_subtest1<VRUT::smart_pointer>();
	VRUT::smart_pointer<A> osp_a1 = new A, osp_a2 = new B;
	VRUT::smart_pointer<B> osp_b1 = new B, osp_b2 = new B;

	osp_b1 = osp_a2;		// There should be dynamic_cast
	// And by the way, no memory leak should occur
}
#endif
#ifdef TEST_COMPILER_ERRORS
template <template <typename> class SmartPointerType> void testCompilerErrors_subtest1()
{
	SmartPointerType<A> a1, a2;		// This is OK
	SmartPointerType<B> b1, b2;		// This is OK
	SmartPointerType<C> c1, c2;		// This is OK
	SmartPointerType<D> d1, d2;		// This is OK

	// The following constructs should be marked as errors by compiler
	SmartPointerType<E> e1;
	SmartPointerType<int> i1;
	b2 = a1;
	c1 = a2;
	d2 = c1;
}

void testCompilerErrors()
{
	testCompilerErrors_subtest1<VRUT::smart_pointer_v2>();
	testCompilerErrors_subtest1<VRUT::const_smart_pointer>();

	VRUT::smart_pointer_v2<A> sp_a;			// This is OK
	VRUT::smart_pointer_v2<B> sp_b;			// This is OK
	VRUT::smart_pointer_v2<C> sp_c;			// This is OK
	VRUT::smart_pointer_v2<D> sp_d;			// This is OK
	VRUT::const_smart_pointer<A> csp_a;		// This is OK

	// Test constructs which should be marked as errors by compiler
	sp_b = sp_a.getPtr();
	sp_c = sp_a.getPtr();
	sp_d = sp_c.getPtr();
	csp_a->a = 10;
	sp_a = csp_a;
}
#endif
#endif
