#include <string>
#include "sourcehook_test.h"
#include "testevents.h"

// TEST4
// Tests of plugin management system

namespace
{
	StateList g_States;
	SourceHook::ISourceHook *g_SHPtr;
	SourceHook::Plugin g_PLID;

	MAKE_STATE(State_Func1_Called);
	MAKE_STATE(State_Func2_Called);
	MAKE_STATE(State_Func3_Called);

	MAKE_STATE(State_Func1H_Called);
	MAKE_STATE(State_Func2H_Called);
	MAKE_STATE(State_Func3H_Called);

	MAKE_STATE_2(State_PluginInUse, int, bool);

	class Test
	{
	public:
		virtual void Func1()
		{
			ADD_STATE(State_Func1_Called);
		}

		virtual void Func2()
		{
			ADD_STATE(State_Func2_Called);
		}

		virtual void Func3()
		{
			ADD_STATE(State_Func3_Called);
		}
	};

	SH_DECL_HOOK0_void(Test, Func1, SH_NOATTRIB, 0);
	SH_DECL_HOOK0_void(Test, Func2, SH_NOATTRIB, 0);
	SH_DECL_HOOK0_void(Test, Func3, SH_NOATTRIB, 0);

	void Handler_Func1()
	{
		ADD_STATE(State_Func1H_Called);
	}
	void Handler_Func2()
	{
		ADD_STATE(State_Func2H_Called);
	}
	void Handler_Func3()
	{
		ADD_STATE(State_Func3H_Called);
	}
}

bool TestPlugSys(std::string &error)
{
	GET_SHPTR(g_SHPtr);
	g_PLID = 1;

	Test inst;
	Test *pInst = &inst;

	// 1)
	//   Add hooks, then issue a complete shutdown
	SH_ADD_HOOK_STATICFUNC(Test, Func1, pInst, Handler_Func1, false);
	SH_ADD_HOOK_STATICFUNC(Test, Func2, pInst, Handler_Func2, true);
	SH_ADD_HOOK_STATICFUNC(Test, Func3, pInst, Handler_Func3, false);

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1H_Called,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func2H_Called,
		new State_Func3H_Called,
		new State_Func3_Called,
		NULL), "Part 1.1");

	Test_CompleteShutdown(g_SHPtr);

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func3_Called,
		NULL), "Part 1.2");

	// 2)
	//   Add hooks from "different plugins", then shutdown the plugins

	g_PLID = 1;
	SH_ADD_HOOK_STATICFUNC(Test, Func1, pInst, Handler_Func1, false);
	g_PLID = 2;
	SH_ADD_HOOK_STATICFUNC(Test, Func2, pInst, Handler_Func2, true);
	g_PLID = 3;
	SH_ADD_HOOK_STATICFUNC(Test, Func3, pInst, Handler_Func3, false);

	g_PLID = 1;
	SH_ADD_HOOK_STATICFUNC(Test, Func2, pInst, Handler_Func2, true);
	SH_ADD_HOOK_STATICFUNC(Test, Func3, pInst, Handler_Func3, false);

	g_PLID = 2;
	SH_ADD_HOOK_STATICFUNC(Test, Func1, pInst, Handler_Func1, false);
	SH_ADD_HOOK_STATICFUNC(Test, Func3, pInst, Handler_Func3, false);

	g_PLID = 3;
	SH_ADD_HOOK_STATICFUNC(Test, Func1, pInst, Handler_Func1, false);
	SH_ADD_HOOK_STATICFUNC(Test, Func2, pInst, Handler_Func2, true);

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1H_Called,
		new State_Func1H_Called,
		new State_Func1H_Called,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func2H_Called,
		new State_Func2H_Called,
		new State_Func2H_Called,
		new State_Func3H_Called,
		new State_Func3H_Called,
		new State_Func3H_Called,
		new State_Func3_Called,
		NULL), "Part 2.1");

	ADD_STATE(State_PluginInUse(1, Test_IsPluginInUse(g_SHPtr, 1)));
	ADD_STATE(State_PluginInUse(2, Test_IsPluginInUse(g_SHPtr, 2)));
	ADD_STATE(State_PluginInUse(3, Test_IsPluginInUse(g_SHPtr, 3)));
	ADD_STATE(State_PluginInUse(4, Test_IsPluginInUse(g_SHPtr, 4)));

	CHECK_STATES((&g_States,
		new State_PluginInUse(1, true),
		new State_PluginInUse(2, true),
		new State_PluginInUse(3, true),
		new State_PluginInUse(4, false),
		NULL), "Part 2.2");

	// Unload plugins one by one
	Test_UnloadPlugin(g_SHPtr, 3);

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1H_Called,
		new State_Func1H_Called,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func2H_Called,
		new State_Func2H_Called,
		new State_Func3H_Called,
		new State_Func3H_Called,
		new State_Func3_Called,
		NULL), "Part 2.3.1");

	ADD_STATE(State_PluginInUse(1, Test_IsPluginInUse(g_SHPtr, 1)));
	ADD_STATE(State_PluginInUse(2, Test_IsPluginInUse(g_SHPtr, 2)));
	ADD_STATE(State_PluginInUse(3, Test_IsPluginInUse(g_SHPtr, 3)));
	ADD_STATE(State_PluginInUse(4, Test_IsPluginInUse(g_SHPtr, 4)));

	CHECK_STATES((&g_States,
		new State_PluginInUse(1, true),
		new State_PluginInUse(2, true),
		new State_PluginInUse(3, false),
		new State_PluginInUse(4, false),
		NULL), "Part 2.3.2");

	Test_UnloadPlugin(g_SHPtr, 2);

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1H_Called,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func2H_Called,
		new State_Func3H_Called,
		new State_Func3_Called,
		NULL), "Part 2.4.1");

	ADD_STATE(State_PluginInUse(1, Test_IsPluginInUse(g_SHPtr, 1)));
	ADD_STATE(State_PluginInUse(2, Test_IsPluginInUse(g_SHPtr, 2)));
	ADD_STATE(State_PluginInUse(3, Test_IsPluginInUse(g_SHPtr, 3)));
	ADD_STATE(State_PluginInUse(4, Test_IsPluginInUse(g_SHPtr, 4)));

	CHECK_STATES((&g_States,
		new State_PluginInUse(1, true),
		new State_PluginInUse(2, false),
		new State_PluginInUse(3, false),
		new State_PluginInUse(4, false),
		NULL), "Part 2.4.2");

	Test_UnloadPlugin(g_SHPtr, 1);

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func3_Called,
		NULL), "Part 2.5.1");

	ADD_STATE(State_PluginInUse(1, Test_IsPluginInUse(g_SHPtr, 1)));
	ADD_STATE(State_PluginInUse(2, Test_IsPluginInUse(g_SHPtr, 2)));
	ADD_STATE(State_PluginInUse(3, Test_IsPluginInUse(g_SHPtr, 3)));
	ADD_STATE(State_PluginInUse(4, Test_IsPluginInUse(g_SHPtr, 4)));

	CHECK_STATES((&g_States,
		new State_PluginInUse(1, false),
		new State_PluginInUse(2, false),
		new State_PluginInUse(3, false),
		new State_PluginInUse(4, false),
		NULL), "Part 2.5.2");


	// 3)
	//   Add hooks from "different plugins", then pause the plugins

	g_PLID = 1;
	SH_ADD_HOOK_STATICFUNC(Test, Func1, pInst, Handler_Func1, false);
	g_PLID = 2;
	SH_ADD_HOOK_STATICFUNC(Test, Func2, pInst, Handler_Func2, true);
	g_PLID = 3;
	SH_ADD_HOOK_STATICFUNC(Test, Func3, pInst, Handler_Func3, false);

	g_PLID = 1;
	SH_ADD_HOOK_STATICFUNC(Test, Func2, pInst, Handler_Func2, true);
	SH_ADD_HOOK_STATICFUNC(Test, Func3, pInst, Handler_Func3, false);

	g_PLID = 2;
	SH_ADD_HOOK_STATICFUNC(Test, Func1, pInst, Handler_Func1, false);
	SH_ADD_HOOK_STATICFUNC(Test, Func3, pInst, Handler_Func3, false);

	g_PLID = 3;
	SH_ADD_HOOK_STATICFUNC(Test, Func1, pInst, Handler_Func1, false);
	SH_ADD_HOOK_STATICFUNC(Test, Func2, pInst, Handler_Func2, true);

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1H_Called,
		new State_Func1H_Called,
		new State_Func1H_Called,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func2H_Called,
		new State_Func2H_Called,
		new State_Func2H_Called,
		new State_Func3H_Called,
		new State_Func3H_Called,
		new State_Func3H_Called,
		new State_Func3_Called,
		NULL), "Part 3.1");

	ADD_STATE(State_PluginInUse(1, Test_IsPluginInUse(g_SHPtr, 1)));
	ADD_STATE(State_PluginInUse(2, Test_IsPluginInUse(g_SHPtr, 2)));
	ADD_STATE(State_PluginInUse(3, Test_IsPluginInUse(g_SHPtr, 3)));
	ADD_STATE(State_PluginInUse(4, Test_IsPluginInUse(g_SHPtr, 4)));

	CHECK_STATES((&g_States,
		new State_PluginInUse(1, true),
		new State_PluginInUse(2, true),
		new State_PluginInUse(3, true),
		new State_PluginInUse(4, false),
		NULL), "Part 3.2");

	// Unload plugins one by one
	Test_PausePlugin(g_SHPtr, 3);

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1H_Called,
		new State_Func1H_Called,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func2H_Called,
		new State_Func2H_Called,
		new State_Func3H_Called,
		new State_Func3H_Called,
		new State_Func3_Called,
		NULL), "Part 3.3.1");

	ADD_STATE(State_PluginInUse(1, Test_IsPluginInUse(g_SHPtr, 1)));
	ADD_STATE(State_PluginInUse(2, Test_IsPluginInUse(g_SHPtr, 2)));
	ADD_STATE(State_PluginInUse(3, Test_IsPluginInUse(g_SHPtr, 3)));
	ADD_STATE(State_PluginInUse(4, Test_IsPluginInUse(g_SHPtr, 4)));

	CHECK_STATES((&g_States,
		new State_PluginInUse(1, true),
		new State_PluginInUse(2, true),
		new State_PluginInUse(3, true),
		new State_PluginInUse(4, false),
		NULL), "Part 3.3.2");

	Test_PausePlugin(g_SHPtr, 2);

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1H_Called,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func2H_Called,
		new State_Func3H_Called,
		new State_Func3_Called,
		NULL), "Part 3.4.1");

	ADD_STATE(State_PluginInUse(1, Test_IsPluginInUse(g_SHPtr, 1)));
	ADD_STATE(State_PluginInUse(2, Test_IsPluginInUse(g_SHPtr, 2)));
	ADD_STATE(State_PluginInUse(3, Test_IsPluginInUse(g_SHPtr, 3)));
	ADD_STATE(State_PluginInUse(4, Test_IsPluginInUse(g_SHPtr, 4)));

	CHECK_STATES((&g_States,
		new State_PluginInUse(1, true),
		new State_PluginInUse(2, true),
		new State_PluginInUse(3, true),
		new State_PluginInUse(4, false),
		NULL), "Part 3.4.2");

	Test_PausePlugin(g_SHPtr, 1);

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func3_Called,
		NULL), "Part 3.5.1");

	ADD_STATE(State_PluginInUse(1, Test_IsPluginInUse(g_SHPtr, 1)));
	ADD_STATE(State_PluginInUse(2, Test_IsPluginInUse(g_SHPtr, 2)));
	ADD_STATE(State_PluginInUse(3, Test_IsPluginInUse(g_SHPtr, 3)));
	ADD_STATE(State_PluginInUse(4, Test_IsPluginInUse(g_SHPtr, 4)));

	CHECK_STATES((&g_States,
		new State_PluginInUse(1, true),
		new State_PluginInUse(2, true),
		new State_PluginInUse(3, true),
		new State_PluginInUse(4, false),
		NULL), "Part 3.5.2");

	Test_UnpausePlugin(g_SHPtr, 1);
	Test_UnpausePlugin(g_SHPtr, 2);
	Test_UnpausePlugin(g_SHPtr, 3);

	ADD_STATE(State_PluginInUse(1, Test_IsPluginInUse(g_SHPtr, 1)));
	ADD_STATE(State_PluginInUse(2, Test_IsPluginInUse(g_SHPtr, 2)));
	ADD_STATE(State_PluginInUse(3, Test_IsPluginInUse(g_SHPtr, 3)));
	ADD_STATE(State_PluginInUse(4, Test_IsPluginInUse(g_SHPtr, 4)));

	CHECK_STATES((&g_States,
		new State_PluginInUse(1, true),
		new State_PluginInUse(2, true),
		new State_PluginInUse(3, true),
		new State_PluginInUse(4, false),
		NULL), "Part 3.6");

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1H_Called,
		new State_Func1H_Called,
		new State_Func1H_Called,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func2H_Called,
		new State_Func2H_Called,
		new State_Func2H_Called,
		new State_Func3H_Called,
		new State_Func3H_Called,
		new State_Func3H_Called,
		new State_Func3_Called,
		NULL), "Part 3.7");

	// 4) Shutdown :)
	Test_CompleteShutdown(g_SHPtr);

	pInst->Func1();
	pInst->Func2();
	pInst->Func3();

	CHECK_STATES((&g_States,
		new State_Func1_Called,
		new State_Func2_Called,
		new State_Func3_Called,
		NULL), "Part 4");

	return true;
}