#include "common.h"
#include "scan.h"

typedef PLUGININFOEX * (__cdecl * Miranda_Plugin_Info_Ex) ( DWORD mirandaVersion );

struct AlternateShortName
{
    const char* from;
    const char* to;
};

static const AlternateShortName alternate_shortname_map[] =
{
	{ "Version Informations", "Version Information" },
	{ "Jabber Protocol", "JabberG Protocol" },
	{ "Jabber Protocol (Unicode)", "JabberG Protocol (Unicode)" },

	{ "PopUp Interoperability", "PopUp Plus (Unicode)" },

	//{ "Messaging Style Conversation", "nConvers++" }, // will this conflict with other nConvers'?
	{ "MimQQ-libeva", "MirandaQQ (libeva Version)" },

	// grr
	{ "Updater", __PLUGIN_NAME },
	{ "Updater (Unicode)", __PLUGIN_NAME },
};

char* findAlternateShortName(const char* name)
{
    for (int i = 0; i < SIZEOF(alternate_shortname_map); ++i)
    {
        if (strcmp(name, alternate_shortname_map[i].from) == 0)
            return mir_strdup(alternate_shortname_map[i].to);
    }

	if (!strstr(name, "Unicode"))
	{
		char *buf = (char*)mir_alloc(256);
		mir_snprintf(buf, 256, "%s (Unicode)", name);
		return buf;
	}

    return NULL;
}

bool valDllName(TCHAR* name)
{
	TCHAR *p = _tcsrchr(name, '.');
	return p && _tcsicmp(p, _T(".dll")) == 0;
}

void ScanPlugins(FilenameMap *fn_map, UpdateList *update_list) 
{
	if (!XMLDataAvailable(MC_PLUGINS)) return;

	TCHAR plugins_folder[MAX_PATH], dll_path[MAX_PATH];
	TCHAR *dll_name;
	Miranda_Plugin_Info_Ex dll_info_func_ex;
	DWORD mirandaVersion = (DWORD)CallService(MS_SYSTEM_GETVERSION, 0, 0);
	PLUGININFOEX *pluginInfo;

	GetRootDir(plugins_folder);
	_tcscat(plugins_folder, _T("\\Plugins"));

	_tcscpy(dll_path, plugins_folder);
	_tcscat(dll_path, _T("\\"));

	// set dll_name to point into the dll_path string, at the point where we can write the plugin name
	// to end up with the full dll path
	dll_name = dll_path + _tcslen(dll_path);

	// add filemask
	_tcscat(plugins_folder, _T("\\*.dll"));

	WIN32_FIND_DATA findData;
	HANDLE hFileSearch = FindFirstFile(plugins_folder, &findData);
	if (hFileSearch != INVALID_HANDLE_VALUE) 
	{
		do {
			HMODULE hModule = NULL;
			bool notLoaded = false;
			if (valDllName(findData.cFileName))
			{
				_tcscpy(dll_name, findData.cFileName);
				if (hModule = GetModuleHandle(dll_path)) 
				{
					hModule = LoadLibrary(dll_path);
					notLoaded = true;
				}
			}
			if (hModule) 
            {
				dll_info_func_ex = (Miranda_Plugin_Info_Ex)GetProcAddress(hModule, "MirandaPluginInfoEx");
				if (dll_info_func_ex && (pluginInfo = (PLUGININFOEX*)dll_info_func_ex(mirandaVersion)) != NULL)
                {
					// *** This is a dodgy and unfair hack...
					// In order to disable new plugins that may be unintentionally installed with an update,
					// updater will check for the 'plugindisabled' setting for each dll. The problem is that
					// this setting may not be there for running plugins - and isn't there for new ones. So,
					// we'll disable anything new when the setting isn't found anyway - but we write the
					// value below for all plugins so that we can expect to find it
					char *lowname = _strlwr(mir_t2a(findData.cFileName));
					if(DBGetContactSettingByte(0, "PluginDisable", lowname, 255) == 255) // setting not present
						DBWriteContactSettingByte(0, "PluginDisable", lowname, 0);
					mir_free(lowname);

					bool found = false;
					char* alternateName = findAlternateShortName(pluginInfo->shortName);
					if (alternateName) 
                    {
                        int file_id = FindFileID(alternateName, MC_PLUGINS, update_list);
						if (file_id != -1) 
                        {
							found = true;
							if (FindFileInList(pluginInfo->shortName) >= 0)
								RegisterForFileListing(file_id, pluginInfo->shortName, pluginInfo->version, true, MC_PLUGINS);
							else
								RegisterForFileListing(file_id, alternateName, pluginInfo->version, true, MC_PLUGINS);

                            if (fn_map)
                            {
                                FileNameStruct* fns = fn_map->find((FileNameStruct*)&file_id);
                                if (fns == NULL)
                                {
                                    fns = new FileNameStruct(file_id);
                                    fn_map->insert(fns);
                                }
                                fns->list.insert(mir_tstrdup(findData.cFileName));
                            }
						}
						mir_free(alternateName);
					} 

                    if (!found) 
                    {
						int file_id = FindFileID(pluginInfo->shortName, MC_PLUGINS, update_list);
						if (file_id != -1) 
                        {
							RegisterForFileListing(file_id, pluginInfo, true);
                            if (fn_map)
                            {
                                FileNameStruct* fns = fn_map->find((FileNameStruct*)&file_id);
                                if (fns == NULL)
                                {
                                    fns = new FileNameStruct(file_id);
                                    fn_map->insert(fns);
                                }
                                fns->list.insert(mir_tstrdup(findData.cFileName));
                            }
						}
					}
				}
				if (notLoaded) FreeLibrary(hModule);
			}
		} while(FindNextFile(hFileSearch, &findData));
		FindClose(hFileSearch);
	}
}

typedef struct LangpackData_tag {
	DWORD version;
	char *fl_name;
} LangpackData;


bool GetLangpackData(const TCHAR *filename, LangpackData *ld) 
{
    char line[1024], *ver, *conv;

    FILE *fp = _tfopen(filename, _T("r"));
    if (fp == NULL) return false;

    while (fgets(line, sizeof(line), fp))
    {
        conv = strstr(line, "; FLID:");
		if (conv) 
        {
            conv += 7;

            while (*conv == ' ') conv++;
            
            ver = strchr(conv, 0) + 1;
            while (ver >= conv) if (*ver != ' ') break; else *ver-- = 0;

            ver = strrchr(conv, ' ');
            if (ver) 
            { 
                *ver = 0; 
                VersionFromString(++ver, &ld->version);
            }
            ld->fl_name = mir_strdup(conv);
            fclose(fp);
            return true;
        }
    }
    fclose(fp);
    return false;
}

void ScanLangpacks(FilenameMap *fn_map, UpdateList *update_list) 
{
	if (!XMLDataAvailable(MC_LOCALIZATION)) return;

	TCHAR mir_folder[MAX_PATH], langpack_path[MAX_PATH], *langpack_name;

	GetRootDir(mir_folder); _tcscat(mir_folder, _T("\\"));
	_tcscpy(langpack_path, mir_folder);

	// set langpack_name to point into the langpack_name string, at the point where we can write the file name
	// to end up with the full path
	langpack_name = langpack_path + _tcslen(langpack_path);

	// add filemask
	_tcscat(mir_folder, _T("langpack_*.txt"));

	WIN32_FIND_DATA findData;
	HANDLE hFileSearch = FindFirstFile(mir_folder, &findData);
	if (hFileSearch != INVALID_HANDLE_VALUE) 
    {
		do {
			_tcscpy(langpack_name, findData.cFileName);
			LangpackData ld = {0};
			if (GetLangpackData(langpack_path, &ld)) 
            {
                int file_id = FindFileID(ld.fl_name, MC_LOCALIZATION, update_list);
				if (file_id != -1) 
                {
					RegisterForFileListing(file_id, ld.fl_name, ld.version, true, MC_LOCALIZATION);
					if (fn_map) 
                    {
                        FileNameStruct* fns = fn_map->find((FileNameStruct*)&file_id);
                        if (fns == NULL)
                        {
                            fns = new FileNameStruct(file_id);
                            fn_map->insert(fns);
                        }
                        fns->list.insert(mir_tstrdup(findData.cFileName));
                    }
				}
				mir_free(ld.fl_name);
			}
		} while(FindNextFile(hFileSearch, &findData));
		FindClose(hFileSearch);
	}
}

bool RearrangeDllsWorker(char *shortName, StrList &filenames, TCHAR *basedir) 
{
	bool dll_enabled = false;
	BYTE disabled_val;

	TCHAR file_path[MAX_PATH];

	Miranda_Plugin_Info_Ex dll_info_func_ex;
	DWORD mirandaVersion = (DWORD)CallService(MS_SYSTEM_GETVERSION, 0, 0);
	PLUGININFOEX *pluginInfo;
	HMODULE hModule;

	// add filemask
	mir_sntprintf(file_path, SIZEOF(file_path), _T("%s\\*.dll"), basedir);

	WIN32_FIND_DATA findData;
	HANDLE hFileSearch = FindFirstFile(file_path, &findData);
	if (hFileSearch != INVALID_HANDLE_VALUE) 
	{
		do 
		{
			mir_sntprintf(file_path, SIZEOF(file_path), _T("%s\\%s"), basedir, findData.cFileName);
			if (valDllName(findData.cFileName) && (hModule = LoadLibrary(file_path))) 
			{
				dll_info_func_ex = (Miranda_Plugin_Info_Ex)GetProcAddress(hModule, "MirandaPluginInfoEx");
				if (dll_info_func_ex && (pluginInfo = (PLUGININFOEX *)dll_info_func_ex(mirandaVersion)) != NULL) 
                {
					bool found = !_stricmp(pluginInfo->shortName, shortName);
					if (!found)
					{
						char* alternateName = findAlternateShortName(pluginInfo->shortName);
						found = alternateName && !_stricmp(alternateName, shortName);
						mir_free(alternateName);
					}

					if (found) 
                    {
						bool moved = false;
						TCHAR* newname = NULL;
						for (int j = 0; j < filenames.getCount(); j++) 
                        {
							TCHAR new_filename[MAX_PATH];
                            TCHAR* fileName = filenames[j];
							mir_sntprintf(new_filename, SIZEOF(new_filename), _T("%s\\%s"), basedir, fileName);

							// disable any new plugins (i.e. not installed before) that somehome got into the
							// dowloaded archives (e.g. loadavatars comes with loadavatarsw - installing both is not good!)
                            char *temp_str = _strlwr(mir_t2a(fileName));
							disabled_val = DBGetContactSettingByte(0, "PluginDisable", temp_str, 255);
							if (disabled_val == 255) { // assume this means setting not in db (should be 1 or 0)
								DBWriteContactSettingByte(0, "PluginDisable", temp_str, 1);
								disabled_val = 1;
							}
                            mir_free(temp_str);

							dll_enabled |= (disabled_val == 0);						

							if (!moved) 
							{
								if (_tcsicmp(findData.cFileName, fileName))
									MoveFile(file_path, new_filename);

                                mir_free(newname); newname = mir_tstrdup(new_filename);
								moved = true;
							} else
								CopyFile(newname, new_filename, FALSE);
						}
                        mir_free(newname);
						FreeLibrary(hModule);
						break;
					}
				}
				FreeLibrary(hModule);
			} 
		} 
		while(FindNextFile(hFileSearch, &findData));
		FindClose(hFileSearch);
	}
	return dll_enabled;
}


bool RearrangeDlls(char *shortName, StrList &filenames) 
{
	bool dll_enabled = false;

	TCHAR dir[MAX_PATH];
	mir_sntprintf(dir, SIZEOF(dir), _T("%s\\Plugins"), options.temp_folder);

	return 
		RearrangeDllsWorker(shortName, filenames, options.temp_folder) ||
		RearrangeDllsWorker(shortName, filenames, dir);
}

bool RearrangeLangpacks(char *shortName, StrList &filenames) 
{
	
	TCHAR file_path[MAX_PATH], updates_folder[MAX_PATH], new_filename[MAX_PATH], *langpack_name;
	// do exactly the same thing again, for the updates/plugins folder... :(

	mir_sntprintf(file_path, SIZEOF(file_path), _T("%s\\"), options.temp_folder);
	langpack_name = file_path + _tcslen(file_path);

	// add filemask
	mir_sntprintf(updates_folder, SIZEOF(updates_folder), _T("%s\\langpack_*.txt"), options.temp_folder);

	WIN32_FIND_DATA findData;
	HANDLE hFileSearch = FindFirstFile(updates_folder, &findData);
	if(hFileSearch != INVALID_HANDLE_VALUE) {
		do {
			_tcscpy(langpack_name, findData.cFileName);

			LangpackData ld = {0};
			if (GetLangpackData(file_path, &ld)) 
            {
				if (_stricmp(ld.fl_name, shortName) == 0) 
                {
					bool moved = false;
					TCHAR *newname = NULL;
					for (int j = 0; j < filenames.getCount(); j++) 
                    {
                        TCHAR *fileName = filenames[j];
						mir_sntprintf(new_filename, SIZEOF(new_filename), _T("%s\\%s"), options.temp_folder, fileName);
						
                        if (!moved) 
                        {
							if (_tcscmp(findData.cFileName, fileName) != 0)
								MoveFile(file_path, new_filename);
                            mir_free(newname); newname = mir_tstrdup(new_filename);
							moved = true;
						} else
							CopyFile(newname, new_filename, FALSE);
					}
                    mir_free(newname);
					break;
				}
			}
		} while(FindNextFile(hFileSearch, &findData));
		FindClose(hFileSearch);
	}

	return true;
}