//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
// RCS-ID:
// Copyright (C) 2002 - 2013, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// registry.cpp - Windows Registry Reader for both 32 and 64 mode
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin3.h"

#include "utils/registry.h"

#ifdef __WXMSW__

#define RESERVED        0
#define PGREG_SEPERATOR wxT("\\")

static struct stdkey
{
	HKEY         key;
	wxString name;
}
aStdHKeys[] =
{
	{HKEY_CLASSES_ROOT,   wxT("HKEY_CLASSES_ROOT")},
	{HKEY_CURRENT_USER,   wxT("HKEY_CURRENT_USER")},
	{HKEY_LOCAL_MACHINE,  wxT("HKEY_LOCAL_MACHINE") },
	{HKEY_USERS,          wxT("HKEY_USERS")},
	{HKEY_CURRENT_CONFIG, wxT("HKEY_CURRENT_CONFIG")}
};

#define NOS_STD_KEYS (size_t)(sizeof(aStdHKeys)/sizeof(stdkey))

pgRegKey::pgRegKey(HKEY root, const wxString &subkey, PGREGWOWMODE wowMode, PGREGACCESSMODE accessMode)
{
	Init(root, subkey, wowMode, accessMode);
}

pgRegKey::pgRegKey(const pgRegKey &parent, const wxString &key)
{
	wxString strKey;
	strKey << parent.m_strName << PGREG_SEPERATOR << key;

	Init(parent.m_hRoot, strKey, PGREG_WOW_DEFAULT, parent.m_accessMode);
	m_wowMode        = parent.m_wowMode;
}

void pgRegKey::Init(HKEY root, const wxString &subkey, PGREGWOWMODE wowMode, PGREGACCESSMODE accessMode)
{
	m_hRoot            = root;
	m_strName             = subkey;
	m_accessMode = accessMode;
	m_hKey             = NULL;
#ifdef __WIN64__
	m_wowMode = KEY_WOW64_64KEY;
#else
	m_wowMode = KEY_WOW64_32KEY;
#endif

	switch (wowMode)
	{
		case PGREG_WOW32:
#ifdef __WIN64__
			m_wowMode = KEY_WOW64_32KEY;
#endif // __WIN64__
			break;

		case PGREG_WOW64:
#ifndef __WIN64__
			// we can only access 64 bit registry under 64 bit platforms
			if (::wxIsPlatform64Bit())
				m_wowMode = KEY_WOW64_64KEY;
#endif // !__WIN64__
			break;
	}
}

pgRegKey *pgRegKey::OpenRegKey(HKEY root, const wxString &subkey, PGREGACCESSMODE accessmode, PGREGWOWMODE wowMode)
{
	wxString strKey = subkey;

	!subkey.IsEmpty() && subkey.EndsWith(wxT("\\"), &strKey);
	pgRegKey *tmpKey = new pgRegKey(root, strKey, wowMode, accessmode);

	HKEY tmpRegKey = 0;
	long nError = ::RegOpenKeyEx(root, (LPCTSTR)strKey, RESERVED,
	                             (accessmode == PGREG_READ ? KEY_READ : KEY_ALL_ACCESS) | tmpKey->m_wowMode,
	                             &tmpRegKey);

	if (nError != ERROR_SUCCESS)
	{
		delete(tmpKey);
		tmpKey = NULL;
	}
	else
		tmpKey->m_hKey = tmpRegKey;

	return tmpKey;
}

void pgRegKey::Close()
{
	if (m_hKey)
	{
		::RegCloseKey(m_hKey);
		m_hKey = NULL;
	}
}

pgRegKey::~pgRegKey()
{
	Close();
}

bool pgRegKey::KeyExists(HKEY root, const wxString &subkey, PGREGWOWMODE wowMode)
{
	if (subkey.IsEmpty())
		return true;

#ifdef __WIN64__
	long keyWowMode = KEY_WOW64_64KEY;
#else
	long keyWowMode = KEY_WOW64_32KEY;
#endif

	switch (wowMode)
	{
		case PGREG_WOW32:
#ifdef __WIN64__
			keyWowMode = KEY_WOW64_32KEY;
#endif // __WIN64__
			break;

		case PGREG_WOW64:
#ifndef __WIN64__
			// we can only access 64 bit registry under 64 bit platforms
			if (::wxIsPlatform64Bit())
				keyWowMode = KEY_WOW64_64KEY;
#endif // ! __WIN64__
			break;
	}

	HKEY hkeyDummy;
	if (::RegOpenKeyEx(root, (LPCTSTR)subkey, RESERVED, KEY_READ | keyWowMode, &hkeyDummy) == ERROR_SUCCESS)
	{
		::RegCloseKey(hkeyDummy);
		return true;
	}
	return false;
}

bool pgRegKey::GetKeyInfo(size_t *pnSubKeys, size_t *pnMaxKeyLen, size_t *pnValues, size_t *pnMaxValueLen) const
{

	long nError = ::RegQueryInfoKey(m_hKey, NULL, NULL, RESERVED, (LPDWORD)pnSubKeys, (LPDWORD)pnMaxKeyLen, NULL,
	                                (LPDWORD)pnValues, (LPDWORD)pnMaxValueLen, NULL, NULL, NULL);

	if (nError != ERROR_SUCCESS)
		return false;
	return true;
}

wxString pgRegKey::ToString() const
{
	wxString str;
	size_t index = 0;
	for (; index < NOS_STD_KEYS; index++)
	{
		if (m_hRoot == aStdHKeys[index].key)
		{
			str = aStdHKeys[index].name;
			break;
		}
	}

	if (!m_strName.IsEmpty())
		str << wxT("\\") << m_strName;

	return str;
}

wxString pgRegKey::GetKeyName() const
{
	if (!m_strName.IsEmpty())
	{
		int index = m_strName.Find(wxT('\\'), true);
		return m_strName.SubString(index + 1, m_strName.Len());
	}
	return wxEmptyString;
}

bool pgRegKey::QueryValue(const wxString &strkey, LPDWORD pVal) const
{
	if (strkey.IsEmpty())
		return false;

	DWORD dwType, dwSize = sizeof(DWORD);
	unsigned char *pBuf = (unsigned char *)pVal;

	long nError = ::RegQueryValueEx(m_hKey, (LPCTSTR)strkey, RESERVED, &dwType, pBuf, &dwSize);

	if (nError != ERROR_SUCCESS)
		return false;

	if (dwType != REG_DWORD && dwType != REG_DWORD_BIG_ENDIAN)
		return false;
	return true;
}

bool pgRegKey::QueryValue(const wxString &strVal, wxString &sVal) const
{
	DWORD dwType, dwSize;

	long nError = RegQueryValueEx(m_hKey, (LPCTSTR)strVal, RESERVED, &dwType, NULL, &dwSize);

	if (nError == ERROR_SUCCESS)
	{
		sVal = wxT("");
		if (dwSize == 0)
			return true;

		wxChar *pBuf = (wxChar *)calloc(dwSize, sizeof(wxChar));
		nError = ::RegQueryValueEx(m_hKey, (LPCTSTR)strVal, RESERVED, &dwType, (LPBYTE)pBuf, &dwSize);

		DWORD index = 0;
		if (dwType == REG_EXPAND_SZ || dwType == REG_MULTI_SZ)
		{
			wxChar *actualValueStr = (wxChar *)calloc(dwSize, sizeof(wxChar));
			memcpy(actualValueStr, pBuf, dwSize * sizeof(wxChar));

			wxChar *curr_line;
			sVal = wxT("");
			do
			{
				DWORD dwExpSize = ::ExpandEnvironmentStrings((actualValueStr + index), NULL, 0);
				curr_line = (wxChar *)calloc(dwExpSize, sizeof(wxChar));

				::ExpandEnvironmentStrings(actualValueStr + index, curr_line, dwExpSize);

				if (index != 0)
					sVal << wxT("\n\r") << curr_line;
				else
					sVal << curr_line;
				index += (DWORD)wxStrlen(actualValueStr + index) + 1;
				free(curr_line);
			}
			while (actualValueStr[index] && dwType == REG_MULTI_SZ);

			free(actualValueStr);
		}
		else
			sVal = pBuf;
		return true;
	}
	return false;
}

bool pgRegKey::QueryValue(const wxString &strVal, LPBYTE &pVal, DWORD &len) const
{
	DWORD dwType;
	if(RegQueryValueEx(m_hKey, (LPCTSTR)strVal,
	                   RESERVED, &dwType, pVal, &len) == ERROR_SUCCESS)
		return true;
	len = 0;
	return false;
}

bool pgRegKey::GetFirstValue(wxString &strkey, long &lIndex) const
{
	lIndex = 0;
	return GetNextValue(strkey, lIndex);
}

bool pgRegKey::GetNextValue(wxString &strval, long &lIndex) const
{
	if (lIndex < 0)
		return false;

	TCHAR szVal[1024];
	DWORD dwValLen = 1024;

	long nError = RegEnumValue(m_hKey, lIndex, szVal, &dwValLen, RESERVED, NULL, NULL, NULL);

	if (nError != ERROR_SUCCESS)
	{
		if (nError == ERROR_NO_MORE_ITEMS)
		{
			nError = ERROR_SUCCESS;
			lIndex = -1;
		}
		return false;
	}
	strval = szVal;
	lIndex++;
	return true;
}

bool pgRegKey::HasValue(const wxString &strval)
{
	long nRetVal = ::RegQueryValueEx(m_hKey, (LPCTSTR)strval, RESERVED, NULL, NULL, NULL);

	return nRetVal == ERROR_SUCCESS;
}

bool pgRegKey::GetFirstKey(pgRegKey *&pkey, long &lIndex) const
{
	lIndex = 0;
	return GetNextKey(pkey, lIndex);
}

bool pgRegKey::GetNextKey(pgRegKey *&pKey, long &lIndex) const
{
	pKey = NULL;

	if (lIndex < 0)
		return false;

	TCHAR szKey[1024];
	DWORD dwKeyLen = 1023;
	long nError = ::RegEnumKeyEx(m_hKey, lIndex, szKey, &dwKeyLen, NULL, NULL, NULL, NULL);

	if (nError != ERROR_SUCCESS)
	{
		lIndex = -1;
		return false;
	}
	lIndex++;
	wxString strSubKey = m_strName;
	strSubKey << wxT("\\") << szKey;

	pgRegKey *tmpKey = new pgRegKey(m_hRoot, (LPCTSTR)strSubKey, PGREG_WOW_DEFAULT, m_accessMode);
	tmpKey->m_wowMode = m_wowMode;

	HKEY tmpRegKey = 0;
	nError
	    = ::RegOpenKeyEx(m_hRoot, (LPCTSTR)strSubKey, RESERVED, (m_accessMode == PGREG_READ ? KEY_READ : KEY_ALL_ACCESS) | m_wowMode, &tmpRegKey);

	if (nError != ERROR_SUCCESS)
	{
		delete(tmpKey);
		tmpKey = NULL;
		pKey = NULL;

		return false;
	}
	else
		tmpKey->m_hKey = tmpRegKey;

	pKey = tmpKey;

	return true;
}

bool pgRegKey::HasKey(const wxString &strKey) const
{
	wxString strSubKey = m_strName;
	strSubKey << wxT("\\") << strKey;

	return pgRegKey::KeyExists(m_hRoot, strSubKey, m_wowMode == KEY_WOW64_64KEY ? PGREG_WOW64 : PGREG_WOW32) ;
}

DWORD pgRegKey::GetValueType(const wxString &key) const
{
	DWORD dwType;
	long nError = RegQueryValueEx((HKEY) m_hKey, (LPCTSTR)key, RESERVED,
	                              &dwType, NULL, NULL);

	if (nError != ERROR_SUCCESS)
		return REG_NONE;
	return dwType;
}

#endif // __WXMSW__
