#include "Main.h"
#include "../Agave/Component/ifc_wa5component.h"
#include <vector>
#include "api.h"
#include "LazyServiceFactory.h"

extern LARGE_INTEGER freq;
std::vector<ifc_wa5component*> systemComponents;
std::vector<LazyServiceFactory*> lazyFactories;

enum
{
	W5S_LOAD = 0,
	W5S_LAZYLOAD = 1,
};

static uint32_t magic_word = 0xdeadbeefUL;
/* layout (binary)
0xdeadbeef - 32 bits
service guid - 128 bits
service fourcc - 32 bits
length of service name - 16bits
service name - see previous
length of test string - 16 bits
test string - see previous
repeat as necessary
*/
static int w5s_load_binary_manifest(const wchar_t *filename, const wchar_t *w5s_filename)
{
	HANDLE manifest = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
	if (manifest != INVALID_HANDLE_VALUE)
	{
		for(;;)
		{
			uint32_t manifest_magic_word;
			GUID service_guid;
			FOURCC service_fourcc;

			DWORD bytesRead=0;
			ReadFile(manifest, &manifest_magic_word, sizeof(manifest_magic_word), &bytesRead, NULL);
			if (bytesRead == 0) // EOF
			{
				CloseHandle(manifest);
				return W5S_LAZYLOAD;
			}

			if (bytesRead != sizeof(manifest_magic_word) || memcmp(&manifest_magic_word, &magic_word, sizeof(magic_word)))
				break;

			bytesRead=0;
			ReadFile(manifest, &service_guid, sizeof(service_guid), &bytesRead, NULL);
			if (bytesRead != sizeof(service_guid))
				break;

			bytesRead=0;
			ReadFile(manifest, &service_fourcc, sizeof(service_fourcc), &bytesRead, NULL);
			if (bytesRead != sizeof(service_fourcc))
				break;

			uint16_t service_name_length;
			bytesRead=0;
			ReadFile(manifest, &service_name_length, sizeof(service_name_length), &bytesRead, NULL);
			if (bytesRead != sizeof(service_name_length))
				break;

			char *service_name = 0;
			if (service_name_length)
			{
				service_name = (char *)calloc(service_name_length + 1, sizeof(char));
				if (service_name)
				{
					bytesRead=0;
					ReadFile(manifest, service_name, service_name_length, &bytesRead, NULL);
					if (bytesRead != service_name_length)
					{
						free(service_name);
						break;
					}
				}
			}

			uint16_t service_test_string_length;
			bytesRead=0;
			ReadFile(manifest, &service_test_string_length, sizeof(service_test_string_length), &bytesRead, NULL);
			if (bytesRead != sizeof(service_test_string_length))
				break;

			char *service_test_string = 0;
			if (service_test_string_length)
			{
				service_test_string = (char *)calloc(service_test_string_length + 1, sizeof(char));
				if (service_name)
				{
					bytesRead=0;
					ReadFile(manifest, service_test_string, service_test_string_length, &bytesRead, NULL);
					if (bytesRead != service_test_string_length)
					{
						free(service_name);
						free(service_test_string);
						break;
					}
				}
			}

			// if we got here, we're OK :)
			LazyServiceFactory *factory = new LazyServiceFactory(service_fourcc, service_guid, service_name, service_test_string, w5s_filename);
			lazyFactories.push_back(factory);
			WASABI_API_SVC->service_register(factory);
		}

		// file seems to be malformed, go ahead and load w5s.
		// any lazy factories we already loaded will self-destruct when the real services load
		CloseHandle(manifest);
		return W5S_LOAD; 
	}

	return W5S_LOAD;
}

void w5s_load(const wchar_t *filename)
{
	HMODULE hLib = LoadLibraryW(filename);
	if (hLib == NULL)
	{
		auto err = GetLastError();
	}
		
	if (hLib)
	{
		typedef ifc_wa5component *(*W5SGetter)();
		W5SGetter pr = (W5SGetter)GetProcAddress(hLib,"GetWinamp5SystemComponent");
		if (pr)
		{
			ifc_wa5component *mod = pr();
			if (mod)
			{
				if (g_safeMode)
				{
					try
					{
						int retval = 0;
						mod->_dispatch(15, &retval);
						if (!retval)
						{
							FreeLibrary(hLib);
							return;
						}
					}
					catch(...)
					{
						FreeLibrary(hLib);
						return;
					}
				}
				systemComponents.push_back(mod);
				mod->hModule = hLib;
				mod->RegisterServices(WASABI_API_SVC);
			}
		}
	}
}

void w5s_init()
{
	WIN32_FIND_DATAW d = {0};
	wchar_t dirstr[MAX_PATH] = {0};

	// pre-load so we're definitely available to other services which need this
	PathCombineW(dirstr, SYSPLUGINDIR, L"wasabi2.w5s");
	w5s_load(dirstr);

	PathCombineW(dirstr, SYSPLUGINDIR, L"*.W5S");
	HANDLE h = FindFirstFileW(dirstr, &d);
	if (h != INVALID_HANDLE_VALUE)
	{
		do
		{
			// due to how this plug-in works, is better to do a filename check to not load in
			// safe mode as it otherwise causes the FreeLibrary(..) call to crash Winamp :o(
			if (g_safeMode)
			{
				if (!wcsnicmp(d.cFileName, L"UnicodeTaskbarFix.w5s", 21)) continue;
				if (!wcsnicmp(d.cFileName, L"fpl.w5s", 7)) continue;
				if (!wcsnicmp(d.cFileName, L"mpcpl.w5s", 9)) continue;
			}

			if (lstrcmpiW(L"wasabi2.w5s", d.cFileName))
			{
				wchar_t manifeststr[MAX_PATH] = {0}, namestr[MAX_PATH] = {0};
				PathCombineW(manifeststr, SYSPLUGINDIR, d.cFileName);
				PathRemoveExtensionW(manifeststr);
				PathAddExtensionW(manifeststr, L".wbm");
				PathCombineW(namestr, SYSPLUGINDIR, d.cFileName);
				if (w5s_load_binary_manifest(manifeststr, namestr) == W5S_LOAD)
				{
					w5s_load(namestr);
				}
			}
		}
		while (FindNextFileW(h, &d));
		FindClose(h);
	}

	Wasabi_FindSystemServices();
}

void w5s_deinit()
{
	Wasabi_ForgetSystemServices();

	for ( ifc_wa5component *l_wa5_component : systemComponents )
	{
		l_wa5_component->DeregisterServices( WASABI_API_SVC );
		l_wa5_component = 0;
	}

	systemComponents.clear();

	//lazyFactories.deleteAll();
	for ( auto obj : lazyFactories )
	{
		delete obj;
	}

	lazyFactories.clear();
}