#include "GamesList.h"
#include "InstallerUtil.h"
#include "InstallerMain.h"
#include <stdio.h>

game_database_t g_games = 
{
	NULL, 0, 
	{NULL, 0, GAME_LIST_NO_GAMES}, 
	{NULL, 0, GAME_LIST_NO_GAMES}, 
	{NULL, 0, GAME_LIST_NO_GAMES}
};

valve_game_t valve_game_list[] = 
{
	{_T("counter-strike source"),		_T("cstrike"),	SOURCE_ENGINE_2004},
	{_T("day of defeat source"),		_T("dod"),		SOURCE_ENGINE_2004},
	{_T("half-life 2 deathmatch"),		_T("hl2mp"),	SOURCE_ENGINE_2004},
	{_T("half-life deathmatch source"),	_T("hl1mp"),	SOURCE_ENGINE_2004},
	{_T("team fortress 2"),				_T("tf"),		SOURCE_ENGINE_2007},
	{NULL,								NULL,			0},
};

valve_game_t valve_server_list[] = 
{
	{_T("source dedicated server"),		NULL,			SOURCE_ENGINE_2004},
	{_T("source 2007 dedicated server"), NULL,			SOURCE_ENGINE_2007},
	{NULL,								NULL,			0},
};

int IsValidFolder(const TCHAR *path)
{
	DWORD attr;
	TCHAR gameinfo_file[MAX_PATH];

	UTIL_PathFormat(gameinfo_file, sizeof(gameinfo_file), _T("%s\\gameinfo.txt"), path);

	if ((attr = GetFileAttributes(gameinfo_file)) == INVALID_FILE_ATTRIBUTES)
	{
		return GAMEINFO_DOES_NOT_EXIST;
	}

	if ((attr & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY)
	{
		return GAMEINFO_IS_READ_ONLY;
	}

	if ((attr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
	{
		return GAMEINFO_DOES_NOT_EXIST;
	}

	return GAMEINFO_IS_USABLE;
}

void DisplayBadFolderDialog(HWND hDlg, int reason)
{
	TCHAR message_string[255];
	UINT resource;

	if (reason == GAMEINFO_DOES_NOT_EXIST)
	{
		resource = IDS_NO_GAMEINFO;
	}
	else if (reason == GAMEINFO_IS_READ_ONLY)
	{
		resource = IDS_READONLY_GAMEINFO;
	}
	else
	{
		return;
	}

	if (LoadString(g_hInstance,
		resource,
		message_string,
		sizeof(message_string) / sizeof(TCHAR)
		) == 0)
	{
		return;
	}

	MessageBox(hDlg, 
		message_string, 
		_T("SourceMod Installer"),
		MB_OK|MB_ICONWARNING);
}

game_list_t *MakeGameList(const TCHAR *name)
{
	game_list_t *gl = (game_list_t *)malloc(sizeof(game_list_t));

	UTIL_Format(gl->root_name, 
		sizeof(gl->root_name) / sizeof(TCHAR),
		_T("%s"),
		name);
	gl->game_count = 0;
	gl->games = NULL;

	return gl;
}

void AttachGameListToGroup(game_group_t *group, game_list_t *gl)
{
	if (group->lists == NULL)
	{
		group->lists = (game_list_t **)malloc(sizeof(game_list_t *));
	}
	else
	{
		group->lists = (game_list_t **)realloc(group->lists,
			sizeof(game_list_t *) * (group->list_count + 1));
	}

	group->lists[group->list_count] = gl;
	group->list_count++;
}

void AttachModToGameList(game_list_t *gl, unsigned int mod_id)
{
	if (gl->games == NULL)
	{
		gl->games = (unsigned int *)malloc(sizeof(unsigned int));
	}
	else
	{
		gl->games = (unsigned int *)realloc(gl->games, 
			sizeof(unsigned int) * (gl->game_count + 1));
	}

	gl->games[gl->game_count] = mod_id;
	gl->game_count++;
}

unsigned int AddModToList(game_database_t *db, const game_info_t *mod_info)
{
	/* Check if a matching game already exists */
	for (unsigned int i = 0; i < db->game_count; i++)
	{
		if (tstrcasecmp(mod_info->game_path, db->game_list[i].game_path) == 0)
		{
			return i;
		}
	}

	if (db->game_list == NULL)
	{
		db->game_list = (game_info_t *)malloc(sizeof(game_info_t));
	}
	else
	{
		db->game_list = (game_info_t *)realloc(db->game_list, 
			sizeof(game_info_t) * (db->game_count + 1));
	}

	memcpy(&db->game_list[db->game_count], mod_info, sizeof(game_info_t));
	db->game_count++;

	return db->game_count - 1;
}

bool TryToAddMod(const TCHAR *path, int eng_type, game_database_t *db, unsigned int *id)
{
	FILE *fp;
	TCHAR gameinfo_path[MAX_PATH];

	UTIL_PathFormat(gameinfo_path,
		sizeof(gameinfo_path),
		_T("%s\\gameinfo.txt"),
		path);

	if ((fp = _tfopen(gameinfo_path, _T("rt"))) == NULL)
	{
		return false;
	}

	int pos;
	char buffer[512];
	char key[256], value[256];
	while (!feof(fp) && fgets(buffer, sizeof(buffer), fp) != NULL)
	{
		if ((pos = BreakStringA(buffer, key, sizeof(key))) == -1)
		{
			continue;
		}
		if ((pos = BreakStringA(&buffer[pos], value, sizeof(value))) == -1)
		{
			continue;
		}
		if (strcmp(key, "game") == 0)
		{
			game_info_t mod;
			unsigned int got_id;

			AnsiToUnicode(value, mod.name, sizeof(mod.name));
			UTIL_Format(mod.game_path, sizeof(mod.game_path), _T("%s"), path);
			mod.source_engine = eng_type;

			got_id = AddModToList(db, &mod);

			if (id != NULL)
			{
				*id = got_id;
			}

			fclose(fp);

			return true;
		}
	}

	fclose(fp);

	return false;
}

void AddModsFromFolder(const TCHAR *path,
					   int eng_type,
					   game_database_t *db,
					   game_list_t *gl)
{
	HANDLE hFind;
	WIN32_FIND_DATA fd;
	TCHAR temp_path[MAX_PATH];
	TCHAR search_path[MAX_PATH];
	unsigned int mod_id;

	UTIL_Format(search_path,
		sizeof(search_path),
		_T("%s\\*.*"),
		path);

	if ((hFind = FindFirstFile(search_path, &fd)) == INVALID_HANDLE_VALUE)
	{
		return;
	}

	do 
	{
		if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY)
		{
			continue;
		}

		if (tstrcasecmp(fd.cFileName, _T(".")) == 0
			|| tstrcasecmp(fd.cFileName, _T("..")) == 0)
		{
			continue;
		}

		UTIL_PathFormat(temp_path,
			sizeof(temp_path),
			_T("%s\\%s"),
			path,
			fd.cFileName);
		if (TryToAddMod(temp_path, eng_type, db, &mod_id))
		{
			AttachModToGameList(gl, mod_id);
		}
	} while (FindNextFile(hFind, &fd));

	FindClose(hFind);
}

void GetSteamGames(game_database_t *db)
{
	HKEY hkPath;
	DWORD dwLen, dwType;
	HANDLE hFind;
	WIN32_FIND_DATA fd;
	TCHAR temp_path[MAX_PATH];
	TCHAR steam_path[MAX_PATH];
	TCHAR steamapps_path[MAX_PATH];

	if (RegOpenKeyEx(HKEY_CURRENT_USER,
		_T("Software\\Valve\\Steam"),
		0,
		KEY_READ,
		&hkPath) != ERROR_SUCCESS)
	{
		db->listen.error_code = GAME_LIST_CANT_READ;
		db->dedicated.error_code = GAME_LIST_CANT_READ;
		return;
	}

	dwLen = sizeof(steam_path) / sizeof(TCHAR);
	if (RegQueryValueEx(hkPath,
		_T("SteamPath"),
		NULL,
		&dwType,
		(LPBYTE)steam_path,
		&dwLen) != ERROR_SUCCESS)
	{
		RegCloseKey(hkPath);
		db->listen.error_code = GAME_LIST_CANT_READ;
		db->dedicated.error_code = GAME_LIST_CANT_READ;
		return;
	}

	UTIL_PathFormat(steamapps_path,
		sizeof(steamapps_path) / sizeof(TCHAR),
		_T("%s\\steamapps\\*.*"),
		steam_path);

	if ((hFind = FindFirstFile(steamapps_path, &fd)) == INVALID_HANDLE_VALUE)
	{
		RegCloseKey(hkPath);
		db->listen.error_code = GAME_LIST_CANT_READ;
		db->dedicated.error_code = GAME_LIST_CANT_READ;
		return;
	}

	do 
	{
		if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY)
		{
			continue;
		}

		if (tstrcasecmp(fd.cFileName, _T(".")) == 0
			|| tstrcasecmp(fd.cFileName, _T("..")) == 0)
		{
			continue;
		}

		/* If we get a folder called "SourceMods," look for third party mods */
		if (tstrcasecmp(fd.cFileName, _T("SourceMods")) == 0)
		{
			game_list_t *gl = MakeGameList(_T("Third-Party Games"));

			UTIL_PathFormat(temp_path,
				sizeof(temp_path) / sizeof(TCHAR),
				_T("%s\\steamapps\\%s"),
				steam_path,
				fd.cFileName);

			AddModsFromFolder(temp_path, SOURCE_ENGINE_UNKNOWN, db, gl);

			if (gl->game_count)
			{
				AttachGameListToGroup(&db->listen, gl);
			}
			else
			{
				free(gl);
			}
		}
		else
		{
			/* Look for listenserver games */
			game_list_t *gl = MakeGameList(fd.cFileName);

			for (unsigned int i = 0; valve_game_list[i].folder != NULL; i++)
			{
				unsigned int mod_id;
				UTIL_PathFormat(temp_path,
					sizeof(temp_path) / sizeof(TCHAR),
					_T("%s\\steamapps\\%s\\%s\\%s"),
					steam_path,
					fd.cFileName,
					valve_game_list[i].folder,
					valve_game_list[i].subfolder);
				if (TryToAddMod(temp_path, valve_game_list[i].eng_type, db, &mod_id))
				{
					AttachModToGameList(gl, mod_id);
				}
			}

			if (gl->game_count)
			{
				AttachGameListToGroup(&db->listen, gl);
			}
			else
			{
				free(gl);
			}

			/* Look for dedicated games */
			gl = MakeGameList(fd.cFileName);

			for (unsigned int i = 0; valve_server_list[i].folder != NULL; i++)
			{
				UTIL_PathFormat(temp_path,
					sizeof(temp_path) / sizeof(TCHAR),
					_T("%s\\steamapps\\%s\\%s"),
					steam_path,
					fd.cFileName,
					valve_server_list[i].folder);
				AddModsFromFolder(temp_path, valve_server_list[i].eng_type, db, gl);
			}

			if (gl->game_count)
			{
				AttachGameListToGroup(&db->dedicated, gl);
			}
			else
			{
				free(gl);
			}
		}

	} while (FindNextFile(hFind, &fd));

	FindClose(hFind);
	RegCloseKey(hkPath);
}

void GetStandaloneGames(game_database_t *db)
{
	HKEY hkPath;
	DWORD dwLen, dwType, dwAttr;
	TCHAR temp_path[MAX_PATH];
	TCHAR hlds_path[MAX_PATH];
	game_list_t *games_standalone;

	if (RegOpenKeyEx(HKEY_CURRENT_USER,
		_T("Software\\Valve\\HLServer"),
		0,
		KEY_READ,
		&hkPath) != ERROR_SUCCESS)
	{
		db->standalone.error_code = GAME_LIST_CANT_READ;
		return;
	}

	dwLen = sizeof(hlds_path) / sizeof(TCHAR);
	if (RegQueryValueEx(hkPath,
		_T("InstallPath"),
		NULL,
		&dwType,
		(LPBYTE)hlds_path,
		&dwLen) != ERROR_SUCCESS)
	{
		RegCloseKey(hkPath);
		db->standalone.error_code = GAME_LIST_CANT_READ;
		return;
	}

	/* Make sure there is a "srcds.exe" file */
	UTIL_PathFormat(temp_path,
		sizeof(temp_path) / sizeof(TCHAR), 
		_T("%s\\srcds.exe"), 
		hlds_path);
	dwAttr = GetFileAttributes(temp_path);
	if (dwAttr == INVALID_FILE_ATTRIBUTES)
	{
		db->standalone.error_code = GAME_LIST_HALFLIFE1;
		return;
	}

	games_standalone = MakeGameList(_T("Standalone"));

	/* If there is an "orangebox" sub folder, we can make a better guess 
	 * at the engine state.
	 */
	UTIL_PathFormat(temp_path,
		sizeof(temp_path) / sizeof(TCHAR), 
		_T("%s\\orangebox"), 
		hlds_path);
	dwAttr = GetFileAttributes(temp_path);
	if (dwAttr != INVALID_FILE_ATTRIBUTES
		&& ((dwAttr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY))
	{
		AddModsFromFolder(temp_path, SOURCE_ENGINE_2007, db, games_standalone);
	}

	/* Add everything from the server */
	AddModsFromFolder(hlds_path, SOURCE_ENGINE_2004, db, games_standalone);

	if (games_standalone->game_count)
	{
		AttachGameListToGroup(&db->standalone, games_standalone);
	}
	else
	{
		free(games_standalone);
	}

	RegCloseKey(hkPath);
}

void DisplayBadGamesDialog(HWND hWnd, int reason)
{
	TCHAR message[256];
	UINT idc = 0;

	if (reason == GAME_LIST_CANT_READ)
	{
		idc = IDS_GAME_FAIL_READ;
	}
	else if (reason == GAME_LIST_HALFLIFE1)
	{
		idc = IDS_GAME_FAIL_HL1;
	}
	else if (reason == GAME_LIST_NO_GAMES)
	{
		idc = IDS_GAME_FAIL_NONE;
	}
	else
	{
		return;
	}

	if (LoadString(g_hInstance,
		idc,
		message,
		sizeof(message) / sizeof(TCHAR)) == 0)
	{
		return;
	}

	MessageBox(hWnd, 
		message, 
		_T("SourceMod Installer"),
		MB_OK|MB_ICONWARNING);
}

int _ModIdCompare(const void *item1, const void *item2)
{
	unsigned int mod_id1 = *(unsigned int *)item1;
	unsigned int mod_id2 = *(unsigned int *)item2;

	return tstrcasecmp(g_games.game_list[mod_id1].name, g_games.game_list[mod_id2].name);
}

int _GroupCompare(const void *item1, const void *item2)
{
	game_list_t *g1 = *(game_list_t **)item1;
	game_list_t *g2 = *(game_list_t **)item2;

	return tstrcasecmp(g1->root_name, g2->root_name);
}

void SortGameGroup(game_group_t *group)
{
	qsort(group->lists, group->list_count, sizeof(game_list_t *), _GroupCompare);

	for (unsigned int i = 0; i < group->list_count; i++)
	{
		qsort(group->lists[i]->games,
			  group->lists[i]->game_count,
			  sizeof(unsigned int),
			  _ModIdCompare);
	}
}

void BuildGameDB()
{
	ReleaseGameDB();
	GetStandaloneGames(&g_games);
	GetSteamGames(&g_games);
	SortGameGroup(&g_games.dedicated);
	SortGameGroup(&g_games.listen);
	SortGameGroup(&g_games.standalone);
}

void ReleaseGameGroup(game_group_t *group)
{
	for (unsigned int i = 0; i < group->list_count; i++)
	{
		free(group->lists[i]->games);
		free(group->lists[i]);
	}
	free(group->lists);
}

void ReleaseGameDB()
{
	ReleaseGameGroup(&g_games.dedicated);
	ReleaseGameGroup(&g_games.listen);
	ReleaseGameGroup(&g_games.standalone);
	free(g_games.game_list);
	memset(&g_games, 0, sizeof(g_games));
}