/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



//---------------------------------------
// 
//---------------------------------------

#ifdef _MSC_VER
#pragma warning(push, 1) /* disable warnings within system headers */
#endif
#include <windows.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif

#include <malloc.h>
#include "registrywnt.hxx"
#include "registryvalueimpl.hxx"
#include "registryexception.hxx"

#include <assert.h>

#ifdef _MSC_VER
#pragma warning(disable : 4786 4350)
#endif

//---------------------------------------
// 
//---------------------------------------

const size_t MAX_TMP_BUFF_SIZE = 1024 * sizeof(wchar_t);


//############################################
// Creation 
// only possible through WindowsRegistry class	
//############################################


//-----------------------------------------------------
/** Create instance and open the specified Registry key
*/
RegistryKeyImplWinNT::RegistryKeyImplWinNT(HKEY RootKey, const std::wstring& KeyName) : 
	RegistryKeyImpl(RootKey, KeyName)
{
}

//-----------------------------------------------------
/** Create instance and open the specified Registry key
*/
RegistryKeyImplWinNT::RegistryKeyImplWinNT(HKEY RootKey) : 
	RegistryKeyImpl(RootKey)
{
}

//-----------------------------------------------------
/** Create an instances of the specified Registry key,
	the key is assumed to be already opened.
*/
RegistryKeyImplWinNT::RegistryKeyImplWinNT(HKEY RootKey, HKEY SubKey, const std::wstring& KeyName, bool Writeable) :
	RegistryKeyImpl(RootKey, SubKey, KeyName, Writeable)
{
}


//############################################
// Queries
//############################################


//-----------------------------------------------------
/** The number of sub values of the key at hand
		
	@precond IsOpen = true

	@throws 
*/
size_t RegistryKeyImplWinNT::GetSubValueCount() const
{
	assert(IsOpen());

	DWORD nSubValues = 0;

	LONG rc = RegQueryInfoKeyW(
		m_hSubKey,
		0, 0, 0, 0, 0, 0, &nSubValues, 0, 0, 0, 0);

	if (ERROR_INVALID_HANDLE == rc)
		throw RegistryIOException(rc);
	else if (ERROR_SUCCESS != rc)
		throw RegistryException(rc);

	return nSubValues;
}

//-----------------------------------------------------
/** The number of sub-keys of the key at hand

	@precond IsOpen = true

	@throws
*/
size_t RegistryKeyImplWinNT::GetSubKeyCount() const
{
	assert(IsOpen());

	DWORD nSubKeys = 0;

	LONG rc = RegQueryInfoKeyA(
		m_hSubKey,
		0, 0, 0, &nSubKeys, 0, 0, 0, 0, 0, 0, 0);

	if (ERROR_INVALID_HANDLE == rc)
		throw RegistryIOException(rc);
	else if (ERROR_SUCCESS != rc)
		throw RegistryException(rc);

	return nSubKeys;
}

//-----------------------------------------------------
/**
*/
StringListPtr RegistryKeyImplWinNT::GetSubKeyNames() const
{	
	assert(IsOpen());

	wchar_t buff[1024];
	DWORD  buff_size = sizeof(buff);	
	FILETIME ftime;
	
	StringList* key_names = new StringList();
		
	LONG rc = ERROR_SUCCESS;

	for (DWORD i = 0; /* left empty */; i++)
	{	
		rc = RegEnumKeyExW(
			m_hSubKey, i, buff, &buff_size,
			0, 0, 0, &ftime);
		
		if (ERROR_SUCCESS != rc && 
			ERROR_MORE_DATA != rc)
			break;
		
		buff_size = sizeof(buff);

		key_names->push_back(buff);		
	}
	
	if (ERROR_INVALID_HANDLE == rc)
		throw RegistryIOException(rc);
	else if (ERROR_NO_MORE_ITEMS != rc && ERROR_SUCCESS != rc)
		throw RegistryException(rc);
		
#if (_MSC_VER < 1300) && !defined(__MINGW32__)
	return key_names;
#else
	return (StringListPtr) key_names;
#endif
}

//-----------------------------------------------------
/**
*/
StringListPtr RegistryKeyImplWinNT::GetSubValueNames() const
{
	assert(IsOpen());

	wchar_t buff[1024];
	DWORD  buff_size = sizeof(buff);	

	StringList* value_names = new StringList();

	LONG rc = ERROR_SUCCESS;

	for (DWORD i = 0; /* left empty */; i++)
	{
		rc = RegEnumValueW(
			m_hSubKey, i, buff, &buff_size,
			0, 0, 0, 0);
	
		if (ERROR_SUCCESS != rc && 
			ERROR_MORE_DATA != rc)
			break;

		buff_size = sizeof(buff);

		value_names->push_back(buff);		
	}
	
	if (ERROR_INVALID_HANDLE == rc)
		throw RegistryIOException(rc);
	else if (ERROR_NO_MORE_ITEMS != rc && ERROR_SUCCESS != rc)
		throw RegistryException(rc);

#if (_MSC_VER < 1300) && !defined(__MINGW32__)
	return value_names;
#else
	return (StringListPtr) value_names;
#endif
}

//-----------------------------------------------------
/** Get the specified registry value

	@precond IsOpen = true
*/
RegistryValue RegistryKeyImplWinNT::GetValue(const std::wstring& Name) const
{
	assert(IsOpen());

	DWORD Type;
	wchar_t buff[MAX_TMP_BUFF_SIZE];
	DWORD   size = sizeof(buff);

	LONG rc = RegQueryValueExW(
		m_hSubKey,
		Name.c_str(),
		0,
		&Type,
		reinterpret_cast<LPBYTE>(buff),
		&size);

	if (ERROR_FILE_NOT_FOUND == rc)
		throw RegistryValueNotFoundException(rc);
	else if (ERROR_ACCESS_DENIED == rc)
		throw RegistryAccessDeniedException(rc);
	else if (ERROR_SUCCESS != rc)
		throw RegistryException(rc);

	RegistryValue regval;

	if (REG_DWORD == Type)
    {
		regval = RegistryValue(new RegistryValueImpl(Name, *(reinterpret_cast<int*>(buff))));
    }
	else if (REG_SZ == Type || REG_EXPAND_SZ == Type || REG_MULTI_SZ == Type)
    {
        if (size > 0)
            regval = RegistryValue(new RegistryValueImpl(Name, std::wstring(reinterpret_cast<wchar_t*>(buff))));
        else
            regval = RegistryValue(new RegistryValueImpl(Name, std::wstring()));
    }
	else
    {
		assert(false);
    }
    
	return regval;
}

//-----------------------------------------------------
/** Get the specified registry value, return the given
	default value if value not found

	@precond IsOpen = true
*/
RegistryValue RegistryKeyImplWinNT::GetValue(const std::wstring& Name, const RegistryValue& Default) const
{
	assert(IsOpen());

	DWORD Type;
	wchar_t buff[MAX_TMP_BUFF_SIZE];
	DWORD   size = sizeof(buff);

	LONG rc = RegQueryValueExW(
		m_hSubKey,
		Name.c_str(),
		0,
		&Type,
		reinterpret_cast<LPBYTE>(buff),
		&size);

	if (ERROR_FILE_NOT_FOUND == rc)
	{
		#if (_MSC_VER < 1300) && !defined(__MINGW32__)
		return Default;
		#else
		RegistryValue regval_ptr;
		regval_ptr = RegistryValue(new RegistryValueImpl(*Default));
		return regval_ptr;
		#endif
	}

	if (ERROR_ACCESS_DENIED == rc)
		throw RegistryAccessDeniedException(rc);
	else if (ERROR_SUCCESS != rc)
		throw RegistryException(rc);

	RegistryValue regval;

	if (REG_DWORD == Type)
		regval = RegistryValue(new RegistryValueImpl(Name, *reinterpret_cast<int*>(buff)));
	else if (REG_SZ == Type || REG_EXPAND_SZ == Type || REG_MULTI_SZ == Type)
		regval = RegistryValue(new RegistryValueImpl(Name, std::wstring(reinterpret_cast<wchar_t*>(buff))));
	else
		assert(false);

	return regval;
}
	

//############################################
// Commands
//############################################


//-----------------------------------------------------
/** Open the registry key, has no effect if 
	the key is already open

	@precond IsOpen = false

	@throws RegistryKeyNotFoundException
			RegistryWriteAccessDenyException
			RegistryAccessDenyException
*/
void RegistryKeyImplWinNT::Open(bool Writeable)
{
	assert(!IsOpen());

	REGSAM regsam = KEY_READ;

	if (Writeable)
		regsam |= KEY_WRITE;

	LONG rc = RegOpenKeyExW(
		m_hRootKey,
		m_KeyName.c_str(),
		0,
		regsam,
		&m_hSubKey);

	if (ERROR_FILE_NOT_FOUND == rc)
		throw RegistryKeyNotFoundException(rc);
	else if (ERROR_ACCESS_DENIED == rc)
		throw RegistryAccessDeniedException(rc);
	else if (ERROR_SUCCESS != rc)
		throw RegistryException(rc);

	m_IsWriteable = Writeable;
	
	assert(IsOpen());
}

//-----------------------------------------------------
/** Open the specified sub-key of the registry key
	at hand

	@precond IsOpen = true
			 HasSubKey(Name) = true

	@throws RegistryIOException
			RegistryKeyNotFoundException
			RegistryAccessDeniedException
*/
RegistryKey RegistryKeyImplWinNT::OpenSubKey(const std::wstring& Name, bool Writeable)
{
	RegistryKey regkey(new RegistryKeyImplWinNT(m_hSubKey, Name));
	regkey->Open(Writeable);
	return regkey;
}

//-----------------------------------------------------
/** Creates a new sub-key below the key at hand

	@precond IsOpen = true
			 IsWriteable = true

	@throws  RegistryIOException
			 RegistryWriteAccessDenyException
*/

RegistryKey RegistryKeyImplWinNT::CreateSubKey(const std::wstring& Name)
{
	assert(IsOpen());
	assert(IsWriteable());

	HKEY hRoot = IsRootKey() ? m_hRootKey : m_hSubKey;

	HKEY hKey;
	
	LONG rc = RegCreateKeyExW(
		hRoot,
		Name.c_str(),
		0,
		0,
		REG_OPTION_NON_VOLATILE,
		KEY_READ | KEY_WRITE,
		0,
		&hKey,
		0);

	if (ERROR_INVALID_HANDLE == rc)
		throw RegistryIOException(rc);
	else if (ERROR_ACCESS_DENIED == rc)
		throw RegistryAccessDeniedException(rc);
	else if (ERROR_SUCCESS != rc)
		throw RegistryException(rc);

	return RegistryKey(new RegistryKeyImplWinNT(hRoot, hKey, Name));
}

//-----------------------------------------------------
/** Deletes a sub-key below the key at hand, the
	key must not have sub-keys

	@precond IsOpen = true
			 IsWriteable = true

	@throws  RegistryIOException
			 RegistryWriteAccessDenyException
*/
void RegistryKeyImplWinNT::DeleteSubKey(const std::wstring& Name)
{
	assert(IsOpen());
	assert(IsWriteable());
	assert(HasSubKey(Name));
	
	RegistryKey SubKey = OpenSubKey(Name);

	size_t nSubKeyCount = SubKey->GetSubKeyCount();
	
	assert(0 == nSubKeyCount);
	
	if (nSubKeyCount)
		throw RegistryInvalidOperationException(ERROR_NOT_SUPPORTED);
	
	LONG rc = RegDeleteKeyW(m_hSubKey, Name.c_str());

	if (ERROR_INVALID_HANDLE == rc)
		throw RegistryIOException(rc);
	else if (ERROR_ACCESS_DENIED == rc)
		throw RegistryAccessDeniedException(rc);
	else if (ERROR_SUCCESS != rc)
		throw RegistryException(rc);
}

//-----------------------------------------------------
/** Deletes a sub-key below the key at hand with all
	its sub-keys

	@precond IsOpen = true
			 IsWriteable = true;

	@throws  RegistryIOException
			 RegistryWriteAccessDenyException
*/
void RegistryKeyImplWinNT::DeleteSubKeyTree(const std::wstring& Name)
{
	ImplDeleteSubKeyTree(m_hSubKey, Name);
}

//-----------------------------------------------------
/** Deletes a sub-key below the key at hand with all
	its sub-keys

	@precond IsOpen = true
			 IsWriteable = true;

	@throws  RegistryIOException
			 RegistryWriteAccessDenyException
*/
LONG RegistryKeyImplWinNT::ImplDeleteSubKeyTree(HKEY RootKey, const std::wstring& Name)
{
	assert(IsOpen());

	HKEY hKey;

	LONG rc = RegOpenKeyExW(
		RootKey,
		Name.c_str(),
		0,
		KEY_READ | DELETE,
		&hKey);
	
	if (ERROR_SUCCESS == rc)
	{	
		wchar_t* lpSubKey;
		DWORD    nMaxSubKeyLen;

		rc = RegQueryInfoKeyW(
			hKey, 0, 0, 0, 0,
			&nMaxSubKeyLen,
			0, 0, 0, 0, 0, 0);
	
		nMaxSubKeyLen++; // space for trailing '\0'

		lpSubKey = reinterpret_cast<wchar_t*>(
			_alloca(nMaxSubKeyLen*sizeof(wchar_t)));

		while (ERROR_SUCCESS == rc)
        {
			DWORD nLen = nMaxSubKeyLen;

			rc = RegEnumKeyExW(
				hKey,
                0,       // always index zero
                lpSubKey,
                &nLen,
                0, 0, 0, 0);

            if (ERROR_NO_MORE_ITEMS == rc)
            {
				rc = RegDeleteKeyW(RootKey, Name.c_str());
                break;
            }
            else if (rc == ERROR_SUCCESS)
			{
				rc = ImplDeleteSubKeyTree(hKey, lpSubKey);
			}

		} // while

        RegCloseKey(hKey);        

	} // if

	if (ERROR_INVALID_HANDLE == rc)
		throw RegistryIOException(rc);
	else if (ERROR_ACCESS_DENIED == rc)
		throw RegistryAccessDeniedException(rc);
	else if (ERROR_FILE_NOT_FOUND == rc)
		throw RegistryKeyNotFoundException(rc);
	else if (ERROR_SUCCESS != rc)
		throw RegistryException(rc);

	return rc;
}

//-----------------------------------------------------
/** Delete the specified value

		@precond IsOpen = true
				 IsWriteable = true
				 HasValue(Name) = true

		@throws	RegistryIOException
				RegistryWriteAccessDeniedException
				RegistryValueNotFoundException
*/
void RegistryKeyImplWinNT::DeleteValue(const std::wstring& Name)
{
	assert(IsOpen());
	assert(HasValue(Name));
	assert(IsWriteable());

	LONG rc = RegDeleteValueW(
		m_hSubKey,
		Name.c_str());

	if (ERROR_INVALID_HANDLE == rc)
		throw RegistryIOException(rc);
	else if (ERROR_ACCESS_DENIED == rc)
		throw RegistryNoWriteAccessException(rc);
	else if (ERROR_FILE_NOT_FOUND == rc)
		throw RegistryValueNotFoundException(rc);
	else if (ERROR_SUCCESS != rc)
		throw RegistryException(rc);
}

//-----------------------------------------------------
/** Set the specified registry value

	@precond IsOpen = true
			 IsWriteable = true

	@throws  RegistryIOException
			 RegistryWriteAccessDenyException
*/
void RegistryKeyImplWinNT::SetValue(const RegistryValue& Value)
{
	assert(IsOpen());
	assert(IsWriteable());

	LONG rc = RegSetValueExW(
		m_hSubKey,
		Value->GetName().c_str(),
		0,
		Value->GetType(),
		reinterpret_cast<const unsigned char*>(Value->GetDataBuffer()),
		static_cast<DWORD>(Value->GetDataSize()));

	if (ERROR_INVALID_HANDLE == rc)
		throw RegistryIOException(rc);
	else if (ERROR_ACCESS_DENIED == rc)
		throw RegistryAccessDeniedException(rc);
	else if (ERROR_SUCCESS != rc)
		throw RegistryException(rc);
}




