/*
 * ReactOS ATL
 *
 * Copyright 2005 Jacek Caban
 * Copyright 2009 Andrew Hill <ash77@reactos.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#pragma once

class IRegistrarBase : public IUnknown
{
public:
	virtual HRESULT STDMETHODCALLTYPE AddReplacement(LPCOLESTR key, LPCOLESTR item) = 0;
	virtual HRESULT STDMETHODCALLTYPE ClearReplacements() = 0;
};

namespace ATL
{

class CRegObject : public IRegistrarBase
{
public:
	typedef struct rep_list_str
	{
		LPOLESTR							key;
		LPOLESTR							item;
		int									key_len;
		struct rep_list_str					*next;
	} rep_list;

	typedef struct
	{
		LPOLESTR							str;
		DWORD								alloc;
		DWORD								len;
	} strbuf;

	rep_list								*m_rep;

public:
	CRegObject()
	{
		m_rep = NULL;
	}

	~CRegObject()
	{
		HRESULT								hResult;

		hResult = ClearReplacements();
		ATLASSERT(SUCCEEDED(hResult));
	}

	HRESULT STDMETHODCALLTYPE QueryInterface(const IID & /* riid */, void ** /* ppvObject */ )
	{
		ATLASSERT(_T("statically linked in CRegObject is not a com object. Do not callthis function"));
		return E_NOTIMPL;
	}

	ULONG STDMETHODCALLTYPE AddRef()
	{
		ATLASSERT(_T("statically linked in CRegObject is not a com object. Do not callthis function"));
		return 1;
	}

	ULONG STDMETHODCALLTYPE Release()
	{
		ATLASSERT(_T("statically linked in CRegObject is not a com object. Do not callthis function"));
		return 0;
	}

	HRESULT STDMETHODCALLTYPE AddReplacement(LPCOLESTR key, LPCOLESTR item)
	{
		int									len;
		rep_list							*new_rep;

		new_rep = reinterpret_cast<rep_list *>(HeapAlloc(GetProcessHeap(), 0, sizeof(rep_list)));
		if (new_rep == NULL)
			return E_OUTOFMEMORY;

		new_rep->key_len  = lstrlenW(key);
		new_rep->key = reinterpret_cast<OLECHAR *>(HeapAlloc(GetProcessHeap(), 0, (new_rep->key_len + 1) * sizeof(OLECHAR)));
		if (new_rep->key == NULL)
		{
			HeapFree(GetProcessHeap(), 0, new_rep);
			return E_OUTOFMEMORY;
		}
		memcpy(new_rep->key, key, (new_rep->key_len + 1) * sizeof(OLECHAR));

		len = lstrlenW(item) + 1;
		new_rep->item = reinterpret_cast<OLECHAR *>(HeapAlloc(GetProcessHeap(), 0, len * sizeof(OLECHAR)));
		if (new_rep->item == NULL)
		{
			HeapFree(GetProcessHeap(), 0, new_rep->key);
			HeapFree(GetProcessHeap(), 0, new_rep);
			return E_OUTOFMEMORY;
		}
		memcpy(new_rep->item, item, len * sizeof(OLECHAR));

		new_rep->next = m_rep;
		m_rep = new_rep;

		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE ClearReplacements()
	{
		rep_list							*iter;
		rep_list							*iter2;

		iter = m_rep;
		while (iter)
		{
			iter2 = iter->next;
			HeapFree(GetProcessHeap(), 0, iter->key);
			HeapFree(GetProcessHeap(), 0, iter->item);
			HeapFree(GetProcessHeap(), 0, iter);
			iter = iter2;
		}

		m_rep = NULL;
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE ResourceRegisterSz(LPCOLESTR resFileName, LPCOLESTR szID, LPCOLESTR szType)
	{
		return RegisterWithResource(resFileName, szID, szType, TRUE);
	}

	HRESULT STDMETHODCALLTYPE ResourceUnregisterSz(LPCOLESTR resFileName, LPCOLESTR szID, LPCOLESTR szType)
	{
		return RegisterWithResource(resFileName, szID, szType, FALSE);
	}

	HRESULT STDMETHODCALLTYPE FileRegister(LPCOLESTR fileName)
	{
		return RegisterWithFile(fileName, TRUE);
	}

	HRESULT STDMETHODCALLTYPE FileUnregister(LPCOLESTR fileName)
	{
		return RegisterWithFile(fileName, FALSE);
	}

	HRESULT STDMETHODCALLTYPE StringRegister(LPCOLESTR data)
	{
		return RegisterWithString(data, TRUE);
	}

	HRESULT STDMETHODCALLTYPE StringUnregister(LPCOLESTR data)
	{
		return RegisterWithString(data, FALSE);
	}

	HRESULT STDMETHODCALLTYPE ResourceRegister(LPCOLESTR resFileName, UINT nID, LPCOLESTR szType)
	{
		return ResourceRegisterSz(resFileName, MAKEINTRESOURCEW(nID), szType);
	}

	HRESULT STDMETHODCALLTYPE ResourceUnregister(LPCOLESTR resFileName, UINT nID, LPCOLESTR szType)
	{
		return ResourceRegisterSz(resFileName, MAKEINTRESOURCEW(nID), szType);
	}

protected:
	HRESULT STDMETHODCALLTYPE RegisterWithResource(LPCOLESTR resFileName, LPCOLESTR szID, LPCOLESTR szType, BOOL doRegister)
	{
		return resource_register(resFileName, szID, szType, doRegister);
	}

	HRESULT STDMETHODCALLTYPE RegisterWithFile(LPCOLESTR fileName, BOOL doRegister)
	{
		return file_register(fileName, doRegister);
	}

	HRESULT STDMETHODCALLTYPE RegisterWithString(LPCOLESTR data, BOOL doRegister)
	{
		return string_register(data, doRegister);
	}

private:
	inline LONG RegDeleteTreeX(HKEY parentKey, LPCTSTR subKeyName)
	{
		wchar_t								szBuffer[256];
		DWORD								dwSize;
		FILETIME							time;
		HKEY								childKey;
		LONG								lRes;

		ATLASSERT(parentKey != NULL);
		lRes = RegOpenKeyEx(parentKey, subKeyName, 0, KEY_READ | KEY_WRITE, &childKey);
		if (lRes != ERROR_SUCCESS)
			return lRes;

		dwSize = sizeof(szBuffer) / sizeof(szBuffer[0]);
		while (RegEnumKeyExW(parentKey, 0, szBuffer, &dwSize, NULL, NULL, NULL, &time) == ERROR_SUCCESS)
		{
			lRes = RegDeleteTreeX(childKey, szBuffer);
			if (lRes != ERROR_SUCCESS)
				return lRes;
			dwSize = sizeof(szBuffer) / sizeof(szBuffer[0]);
		}
		RegCloseKey(childKey);
		return RegDeleteKey(parentKey, subKeyName);
	}

	HRESULT strbuf_init(strbuf *buf)
	{
		buf->str = reinterpret_cast<LPOLESTR>(HeapAlloc(GetProcessHeap(), 0, 128 * sizeof(WCHAR)));
		if (buf->str == NULL)
			return E_OUTOFMEMORY;
		buf->alloc = 128;
		buf->len = 0;
		return S_OK;
	}

	HRESULT strbuf_write(LPCOLESTR str, strbuf *buf, int len)
	{
		LPOLESTR							newBuffer;

		if (len == -1)
			len = lstrlenW(str);
		if (buf->len + len + 1 >= buf->alloc)
		{
			buf->alloc = (buf->len + len) * 2;
			newBuffer = reinterpret_cast<LPOLESTR>(HeapReAlloc(GetProcessHeap(), 0, buf->str, buf->alloc * sizeof(WCHAR)));
			if (newBuffer == NULL)
				return E_OUTOFMEMORY;
			buf->str = newBuffer;
		}
		memcpy(buf->str + buf->len, str, len * sizeof(OLECHAR));
		buf->len += len;
		buf->str[buf->len] = '\0';
		return S_OK;
	}


	HRESULT file_register(LPCOLESTR fileName, BOOL do_register)
	{
		HANDLE								file;
		DWORD								filelen;
		DWORD								len;
		LPWSTR								regstrw;
		LPSTR								regstra;
		LRESULT								lres;
		HRESULT								hResult;

		file = CreateFileW(fileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
		if (file != INVALID_HANDLE_VALUE)
		{
			filelen = GetFileSize(file, NULL);
			regstra = reinterpret_cast<LPSTR>(HeapAlloc(GetProcessHeap(), 0, filelen));
			if (regstra == NULL)
				return E_OUTOFMEMORY;
			lres = ReadFile(file, regstra, filelen, NULL, NULL);
			if (lres == ERROR_SUCCESS)
			{
				len = MultiByteToWideChar(CP_ACP, 0, regstra, filelen, NULL, 0) + 1;
				regstrw = reinterpret_cast<LPWSTR>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR)));
				if (regstra == NULL)
				{
					HeapFree(GetProcessHeap(), 0, regstra);
					return E_OUTOFMEMORY;
				}
				MultiByteToWideChar(CP_ACP, 0, regstra, filelen, regstrw, len);
				regstrw[len - 1] = '\0';

				hResult = string_register(regstrw, do_register);

				HeapFree(GetProcessHeap(), 0, regstrw);
			}
			else
			{
				hResult = HRESULT_FROM_WIN32(lres);
			}
			HeapFree(GetProcessHeap(), 0, regstra);
			CloseHandle(file);
		}
		else
		{
			hResult = HRESULT_FROM_WIN32(GetLastError());
		}

		return hResult;
	}

	HRESULT resource_register(LPCOLESTR resFileName, LPCOLESTR szID, LPCOLESTR szType, BOOL do_register)
	{
		HINSTANCE							hins;
		HRSRC								src;
		HGLOBAL								regstra;
		LPWSTR								regstrw;
		DWORD								len;
		DWORD								reslen;
		HRESULT								hResult;

		hins = LoadLibraryExW(resFileName, NULL, LOAD_LIBRARY_AS_DATAFILE);
		if (hins)
		{
			src = FindResourceW(hins, szID, szType);
			if (src)
			{
				regstra = LoadResource(hins, src);
				reslen = SizeofResource(hins, src);
				if (regstra)
				{
					len = MultiByteToWideChar(CP_ACP, 0, reinterpret_cast<LPCSTR>(regstra), reslen, NULL, 0) + 1;
					regstrw = reinterpret_cast<LPWSTR>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR)));
					if (regstrw == NULL)
						return E_OUTOFMEMORY;
					MultiByteToWideChar(CP_ACP, 0, reinterpret_cast<LPCSTR>(regstra), reslen, regstrw, len);
					regstrw[len - 1] = '\0';

					hResult = string_register(regstrw, do_register);

					HeapFree(GetProcessHeap(), 0, regstrw);
				}
				else
					hResult = HRESULT_FROM_WIN32(GetLastError());
			}
			else
				hResult = HRESULT_FROM_WIN32(GetLastError());
			FreeLibrary(hins);
		}
		else
			hResult = HRESULT_FROM_WIN32(GetLastError());

		return hResult;
	}

	HRESULT string_register(LPCOLESTR data, BOOL do_register)
	{
		strbuf								buf;
		HRESULT								hResult;

		hResult = strbuf_init(&buf);
		if (FAILED(hResult))
			return hResult;
		hResult = do_preprocess(data, &buf);
		if (SUCCEEDED(hResult))
		{
			hResult = do_process_root_key(buf.str, do_register);
			if (FAILED(hResult) && do_register)
				do_process_root_key(buf.str, FALSE);
		}

		HeapFree(GetProcessHeap(), 0, buf.str);
		return hResult;
	}

	HRESULT do_preprocess(LPCOLESTR data, strbuf *buf)
	{
		LPCOLESTR							iter;
		LPCOLESTR							iter2;
		rep_list							*rep_iter;
		HRESULT								hResult;

		iter2 = data;
		iter = wcschr(data, '%');
		while (iter)
		{
			hResult = strbuf_write(iter2, buf, static_cast<int>(iter - iter2));
			if (FAILED(hResult))
				return hResult;

			iter2 = ++iter;
			if (!*iter2)
				return DISP_E_EXCEPTION;
			iter = wcschr(iter2, '%');
			if (!iter)
				return DISP_E_EXCEPTION;

			if (iter == iter2)
			{
				hResult = strbuf_write(_T("%"), buf, 1);
				if (FAILED(hResult))
					return hResult;
			}
			else
			{
				for (rep_iter = m_rep; rep_iter; rep_iter = rep_iter->next)
				{
					if (rep_iter->key_len == iter - iter2 && !_memicmp(iter2, rep_iter->key, rep_iter->key_len * sizeof(wchar_t)))
						break;
				}
				if (!rep_iter)
					return DISP_E_EXCEPTION;

				hResult = strbuf_write(rep_iter->item, buf, -1);
				if (FAILED(hResult))
					return hResult;
			}

			iter2 = ++iter;
			iter = wcschr(iter, '%');
		}

		hResult = strbuf_write(iter2, buf, -1);
		if (FAILED(hResult))
			return hResult;

		return S_OK;
	}

	HRESULT get_word(LPCOLESTR *str, strbuf *buf)
	{
		LPCOLESTR							iter;
		LPCOLESTR							iter2;
		HRESULT								hResult;

		iter2 = *str;
		buf->len = 0;
		buf->str[0] = '\0';

		while (iswspace (*iter2))
			iter2++;
		iter = iter2;
		if (!*iter)
		{
			*str = iter;
			return S_OK;
		}

		if (*iter == '}' || *iter == '=')
		{
			hResult = strbuf_write(iter++, buf, 1);
			if (FAILED(hResult))
				return hResult;
		}
		else if (*iter == '\'')
		{
			iter2 = ++iter;
			iter = wcschr(iter, '\'');
			if (!iter)
			{
				*str = iter;
				return DISP_E_EXCEPTION;
			}
			hResult = strbuf_write(iter2, buf, static_cast<int>(iter - iter2));
			if (FAILED(hResult))
				return hResult;
			iter++;
		}
		else
		{
			while (*iter && !iswspace(*iter))
				iter++;
			hResult = strbuf_write(iter2, buf, static_cast<int>(iter - iter2));
			if (FAILED(hResult))
				return hResult;
		}

		while (iswspace(*iter))
			iter++;
		*str = iter;
		return S_OK;
	}

	inline unsigned int HexToBin(char a)
	{
		if (a >= '0' && a <= '9')
			return a - '0';
		if (a >= 'A' && a <= 'F')
			return a - 'A' + 10;
		if (a >= 'a' && a <= 'f')
			return a - 'a' + 10;
		ATLASSERT(false);
		return 0;
	}

	HRESULT do_process_key(LPCOLESTR *pstr, HKEY parent_key, strbuf *buf, BOOL do_register)
	{
		LPCOLESTR							iter;
		HRESULT								hres;
		LONG								lres;
		HKEY								hkey;
		strbuf								name;
	    
		enum {
			NORMAL,
			NO_REMOVE,
			IS_VAL,
			FORCE_REMOVE,
			DO_DELETE
		} key_type = NORMAL; 

		static const wchar_t *wstrNoRemove = _T("NoRemove");
		static const wchar_t *wstrForceRemove = _T("ForceRemove");
		static const wchar_t *wstrDelete = _T("Delete");
		static const wchar_t *wstrval = _T("val");

		iter = *pstr;
		hkey = NULL;
		iter = *pstr;
		hres = get_word(&iter, buf);
		if (FAILED(hres))
			return hres;
		hres = strbuf_init(&name);
		if (FAILED(hres))
			return hres;

		while(buf->str[1] || buf->str[0] != '}')
		{
			key_type = NORMAL;
			if (!lstrcmpiW(buf->str, wstrNoRemove))
				key_type = NO_REMOVE;
			else if (!lstrcmpiW(buf->str, wstrForceRemove))
				key_type = FORCE_REMOVE;
			else if (!lstrcmpiW(buf->str, wstrval))
				key_type = IS_VAL;
			else if (!lstrcmpiW(buf->str, wstrDelete))
				key_type = DO_DELETE;

			if (key_type != NORMAL)
			{
				hres = get_word(&iter, buf);
				if (FAILED(hres))
					break;
			}
	    
			if (do_register)
			{
				if (key_type == IS_VAL)
				{
					hkey = parent_key;
					hres = strbuf_write(buf->str, &name, -1);
					if (FAILED(hres))
						return hres;
				}
				else if (key_type == DO_DELETE)
				{
					RegDeleteTreeX(parent_key, buf->str);
				}
				else
				{
					if (key_type == FORCE_REMOVE)
						RegDeleteTreeX(parent_key, buf->str);
					lres = RegCreateKey(parent_key, buf->str, &hkey);
					if (lres != ERROR_SUCCESS)
					{
						hres = HRESULT_FROM_WIN32(lres);
						break;
					}
				}
			}
			else if (key_type != IS_VAL && key_type != DO_DELETE)
			{
				hres = strbuf_write(buf->str, &name, -1);
				if (FAILED(hres))
					return hres;
				lres = RegOpenKey(parent_key, buf->str, &hkey);
				if (lres != ERROR_SUCCESS)
				{
				}
			}

			if (key_type != DO_DELETE && *iter == '=')
			{
				iter++;
				hres = get_word(&iter, buf);
				if (FAILED(hres))
					break;
				if (buf->len != 1)
				{
					hres = DISP_E_EXCEPTION;
					break;
				}
				if (do_register)
				{
					switch(buf->str[0])
					{
						case 's':
							hres = get_word(&iter, buf);
							if (FAILED(hres))
								break;
							lres = RegSetValueEx(hkey, name.len ? name.str :  NULL, 0, REG_SZ, (PBYTE)buf->str,
									(lstrlenW(buf->str) + 1) * sizeof(WCHAR));
							if (lres != ERROR_SUCCESS)
								hres = HRESULT_FROM_WIN32(lres);
							break;
						case 'e':
							hres = get_word(&iter, buf);
							if (FAILED(hres))
								break;
							lres = RegSetValueEx(hkey, name.len ? name.str :  NULL, 0, REG_EXPAND_SZ, (PBYTE)buf->str,
									(lstrlenW(buf->str) + 1) * sizeof(WCHAR));
							if (lres != ERROR_SUCCESS)
								hres = HRESULT_FROM_WIN32(lres);
							break;
						case 'd':
							{
								hres = get_word(&iter, buf);
								if (FAILED(hres))
									break;
								WCHAR *end;
								DWORD dw;
								if ((buf->str[0] == '0' && buf->str[1] == 'x') || (buf->str[0] == '&' && buf->str[1] == 'H'))
									dw = wcstoul(&buf->str[2], &end, 16);
								else
									dw = wcstol(&buf->str[0], &end, 10);
								lres = RegSetValueEx(hkey, name.len ? name.str :  NULL, 0, REG_DWORD, (PBYTE)&dw, sizeof(dw));
								if (lres != ERROR_SUCCESS)
									hres = HRESULT_FROM_WIN32(lres);
								break;
							}
						case 'b':
							{
								DWORD			count;
								DWORD			curIndex;

								hres = get_word(&iter, buf);
								if (FAILED(hres))
									break;
								count = buf->len;
								if ((count & 1) != 0)
									return DISP_E_EXCEPTION;
								count = count / 2;
								for (curIndex = 0; curIndex < count; curIndex++)
									((BYTE*)buf->str)[curIndex] = (HexToBin(buf->str[curIndex * 2]) << 4) | HexToBin(buf->str[curIndex * 2 + 1]);
								lres = RegSetValueEx(hkey, name.len ? name.str :  NULL, 0, REG_BINARY, (PBYTE)buf->str, count);
								if (lres != ERROR_SUCCESS)
									hres = HRESULT_FROM_WIN32(lres);
								break;
							}
						default:
							hres = DISP_E_EXCEPTION;
					}
					if (FAILED(hres))
						break;
				}
				else
				{
					if (*iter == '-')
						iter++;
					hres = get_word(&iter, buf);
					if (FAILED(hres))
						break;
				}
			}
			else if(key_type == IS_VAL)
			{
				hres = DISP_E_EXCEPTION;
				break;
			}

			if (key_type != IS_VAL && key_type != DO_DELETE && *iter == '{' && iswspace(iter[1]))
			{
				hres = get_word(&iter, buf);
				if (FAILED(hres))
					break;
				hres = do_process_key(&iter, hkey, buf, do_register);
				if (FAILED(hres))
					break;
			}

			if (!do_register && (key_type == NORMAL || key_type == FORCE_REMOVE))
			{
				RegDeleteKey(parent_key, name.str);
			}

			if (hkey && key_type != IS_VAL)
				RegCloseKey(hkey);
			hkey = 0;
			name.len = 0;
	        
			hres = get_word(&iter, buf);
			if (FAILED(hres))
				break;
		}

		HeapFree(GetProcessHeap(), 0, name.str);
		if (hkey && key_type != IS_VAL)
			RegCloseKey(hkey);
		*pstr = iter;
		return hres;
	}

	HRESULT do_process_root_key(LPCOLESTR data, BOOL do_register)
	{
		LPCOLESTR							iter;
		strbuf								buf;
		unsigned int							i;
		HRESULT								hResult;
		static const struct {
			const wchar_t						*name;
			HKEY							key;
		} root_keys[] = {
			{_T("HKEY_CLASSES_ROOT"), HKEY_CLASSES_ROOT},
			{_T("HKEY_CURRENT_USER"), HKEY_CURRENT_USER},
			{_T("HKEY_LOCAL_MACHINE"), HKEY_LOCAL_MACHINE},
			{_T("HKEY_USERS"), HKEY_USERS},
			{_T("HKEY_PERFORMANCE_DATA"), HKEY_PERFORMANCE_DATA},
			{_T("HKEY_DYN_DATA"), HKEY_DYN_DATA},
			{_T("HKEY_CURRENT_CONFIG"), HKEY_CURRENT_CONFIG},
			{_T("HKCR"), HKEY_CLASSES_ROOT},
			{_T("HKCU"), HKEY_CURRENT_USER},
			{_T("HKLM"), HKEY_LOCAL_MACHINE},
			{_T("HKU"), HKEY_USERS},
			{_T("HKPD"), HKEY_PERFORMANCE_DATA},
			{_T("HKDD"), HKEY_DYN_DATA},
			{_T("HKCC"), HKEY_CURRENT_CONFIG},
		};

		iter = data;

		hResult = strbuf_init(&buf);
		if (FAILED(hResult))
			return hResult;
		hResult = get_word(&iter, &buf);
		if (FAILED(hResult))
			return hResult;

		while (*iter)
		{
			if (!buf.len)
			{
				hResult = DISP_E_EXCEPTION;
				break;
			}
			for (i = 0; i < sizeof(root_keys) / sizeof(root_keys[0]); i++)
			{
				if (!lstrcmpiW(buf.str, root_keys[i].name))
					break;
			}
			if (i == sizeof(root_keys) / sizeof(root_keys[0]))
			{
				hResult = DISP_E_EXCEPTION;
				break;
			}
			hResult = get_word(&iter, &buf);
			if (FAILED(hResult))
				break;
			if (buf.str[1] || buf.str[0] != '{')
			{
				hResult = DISP_E_EXCEPTION;
				break;
			}
			hResult = do_process_key(&iter, root_keys[i].key, &buf, do_register);
			if (FAILED(hResult))
				break;
			hResult = get_word(&iter, &buf);
			if (FAILED(hResult))
				break;
		}
		HeapFree(GetProcessHeap(), 0, buf.str);
		return hResult;
	}

};

}; //namespace ATL
