/*
 * Copyright (c) 2005-2006 Tom N Harris.
 *
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     A copy of the GNU General Public License can be found at
 *     http://www.gnu.org/
 *
 * Written by Tom N Harris <telliamed@whoopdedo.org>
 *
 */

#include "Registry.h"

using namespace Setup;

long 
Registry::update()
{
	if (!m_key)
		return ERROR_INVALID_HANDLE;

	TCHAR clname[MAX_PATH];
	DWORD clbuf = MAX_PATH;
	long err = ::RegQueryInfoKey(m_key, clname, &clbuf, NULL,
				     &m_numKeys, &m_maxKeyName, &m_maxClassName,
				     &m_numValues, &m_maxValueName, &m_maxValueSize,
				     &m_maxSecurity, &m_writeTime);
	if (err == ERROR_MORE_DATA)
		err = ERROR_SUCCESS;
	return err;
}

bool 
Registry::check()
{
	if (!m_key)
		return false;
	if (!m_notify)
	{
		notify();
		return ERROR_SUCCESS == update();
	}
	if (WAIT_OBJECT_0 == ::WaitForSingleObject(m_notify, 0))
	{
		return ERROR_SUCCESS == update();
	}
	return true;
}

void 
Registry::notify()
{
	m_notify = ::CreateEvent(NULL, TRUE, FALSE, NULL);
	if (m_notify)
		::RegNotifyChangeKeyValue(m_key, FALSE, 0xF, m_notify, TRUE);
}

Registry::~Registry()
{
	::RegCloseKey(m_key);
	::CloseHandle(m_notify);
}

Registry::Registry(HKEY key)
	: m_key(key), m_notify(NULL)
{
	long err = update();
	if (err == ERROR_SUCCESS)
	{
		notify();
	}
	else
	{
		::SetLastError(err);
		m_maxClassName = 0;
		m_numKeys = 0;
		m_numValues = 0;
		m_maxKeyName = 0;
		m_maxValueName = 0;
		m_maxValueSize = 0;
		m_maxSecurity = 0;
		m_writeTime.dwHighDateTime = 0;
		m_writeTime.dwLowDateTime = 0;
	}
}

Registry::Registry(Registry const& cpy)
	: m_numKeys(cpy.m_numKeys),
	  m_numValues(cpy.m_numValues),
	  m_maxClassName(cpy.m_maxClassName),
	  m_maxKeyName(cpy.m_maxKeyName),
	  m_maxValueName(cpy.m_maxValueName),
	  m_maxValueSize(cpy.m_maxValueSize),
	  m_maxSecurity(cpy.m_maxSecurity),
	  m_writeTime(cpy.m_writeTime)
{
	if (::DuplicateHandle(::GetCurrentProcess(), cpy.m_key, ::GetCurrentProcess(), 
			      reinterpret_cast<HANDLE*>(&m_key), 0, FALSE, DUPLICATE_SAME_ACCESS))
	{
		notify();
	}
	else
	{
		m_key = NULL;
		m_notify = NULL;
	}
}

Registry& 
Registry::operator=(Registry const& cpy)
{
	HKEY key;
	if (::DuplicateHandle(::GetCurrentProcess(), cpy.m_key, ::GetCurrentProcess(), 
			      reinterpret_cast<HANDLE*>(&key), 0, FALSE, DUPLICATE_SAME_ACCESS))
	{
		::RegCloseKey(m_key);
		::CloseHandle(m_notify);
		m_key = key;
		m_numKeys = cpy.m_numKeys;
		m_numValues = cpy.m_numValues;
		m_maxClassName = cpy.m_maxClassName;
		m_maxKeyName = cpy.m_maxKeyName;
		m_maxValueName = cpy.m_maxValueName;
		m_maxValueSize = cpy.m_maxValueSize;
		m_maxSecurity = cpy.m_maxSecurity;
		m_writeTime = cpy.m_writeTime;
		notify();
	}
	else
	{
		::RegCloseKey(m_key);
		::CloseHandle(m_notify);
		m_key = NULL;
		m_notify = NULL;
		m_numKeys = 0;
		m_numValues = 0;
		m_maxClassName = 0;
		m_maxKeyName = 0;
		m_maxValueName = 0;
		m_maxValueSize = 0;
		m_maxSecurity = 0;
		m_writeTime.dwHighDateTime = 0;
		m_writeTime.dwLowDateTime = 0;
	}
	return *this;
}

Registry 
Registry::getKey(HKEY root, LPCTSTR name, REGSAM access)
{
	HKEY handle = NULL;
	long err = ::RegOpenKeyEx(root, name, 0, access | KEY_NOTIFY | KEY_QUERY_VALUE, 
				  &handle);
	if (err != ERROR_SUCCESS)
		::SetLastError(err);
	return Registry(handle);
}

Registry 
Registry::getKey(LPCTSTR name, REGSAM access)
{
	if (!check())
		return Registry();

	HKEY handle = NULL;
	long err = ::RegOpenKeyEx(m_key, name, 0, access | KEY_NOTIFY | KEY_QUERY_VALUE,
				  &handle);
	if (err != ERROR_SUCCESS)
		::SetLastError(err);
	return Registry(handle);
}

Registry 
Registry::createKey(LPCTSTR name, REGSAM access, LPSECURITY_ATTRIBUTES perm)
{
	if (!check())
		return Registry();

	HKEY handle = NULL;
	DWORD disp;
	long err = ::RegCreateKeyEx(m_key, name, 0, NULL, REG_OPTION_NON_VOLATILE, 
				    access | KEY_NOTIFY | KEY_QUERY_VALUE, perm,
				    &handle, &disp);
	if (err != ERROR_SUCCESS)
		::SetLastError(err);
	return Registry(handle);
}

bool 
Registry::deleteKey(LPCTSTR name)
{
	if (!check())
		return false;

	long err = ::RegDeleteKey(m_key, name);
	if (err != ERROR_SUCCESS)
		::SetLastError(err);
	return err == ERROR_SUCCESS;
}

DWORD 
Registry::getValueType(LPCTSTR name)
{
	if (!check())
		return REG_NONE;

	DWORD type = REG_NONE;
	DWORD size = 0;
	long err = ::RegQueryValueEx(m_key, name, NULL, &type, NULL, &size);
	if (err != ERROR_SUCCESS)
		::SetLastError(err);
	return type;
}

DWORD 
Registry::getValueSize(LPCTSTR name)
{
	if (!check())
		return 0;

	DWORD type = REG_NONE;
	DWORD size = 0;
	long err = ::RegQueryValueEx(m_key, name, NULL, &type, NULL, &size);
	if (err != ERROR_SUCCESS)
		::SetLastError(err);
	return size;
}

DWORD 
Registry::getValueInt(LPCTSTR name)
{
	if (!check())
		return 0;

	DWORD value = 0;
	DWORD type = REG_DWORD;
	DWORD size = sizeof(DWORD);
	long err = ::RegQueryValueEx(m_key, name, NULL, &type, reinterpret_cast<BYTE*>(&value), &size);
	if (err != ERROR_SUCCESS)
		::SetLastError(err);
	return value;
}

int 
Registry::getValueString(LPCTSTR name, LPTSTR value, int len)
{
	if (!check())
		return 0;

	DWORD type = REG_SZ;
	DWORD size = len;
	long err = ::RegQueryValueEx(m_key, name, NULL, &type, reinterpret_cast<BYTE*>(value), &size);
	if (err != ERROR_SUCCESS)
		::SetLastError(err);
	return size;
}

String 
Registry::getValueString(LPCTSTR name)
{
	String str;
	if (!check())
		return str;

	TCHAR* value = reinterpret_cast<TCHAR*>(::operator new(m_maxValueSize));
	DWORD len = m_maxValueSize;
	DWORD type = REG_SZ;
	long err = ::RegQueryValueEx(m_key, name, NULL, &type, reinterpret_cast<BYTE*>(value), &len);
	if (err == ERROR_SUCCESS)
		str = value;
	else
		::SetLastError(err);
	::operator delete(value);
	return str;
}


