/**
 * @file RegistryKey.h
 * @author Bartosz Janiak
 * 
 * @brief Contains the definitions of RegistryKey and RegistryException classes.
 *
 * Copyright © 2009 NVIDIA Corporation. All rights reserved.
 *
 * NVIDIA Corporation and its licensors retain all intellectual property and proprietary
 * rights in and to this software, related documentation and any modifications thereto. Any
 * use, reproduction, disclosure or distribution of this software and related documentation
 * without express license agreement from NVIDIA Corporation is strictly prohibited.
 */

#pragma once

#include <boost/lexical_cast.hpp>
#include <boost/optional.hpp>

#define NOMINMAX
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <shlwapi.h>

#include "TStd.h"
#include <set>
#include <stdexcept>
#include <boost/shared_array.hpp>

#if !defined(MOCK_WINAPI)
#define MOCK_WINAPI
#define _NOT_MOCKED
#endif

#define _REG_QUERY_FUNCTION "RegQueryValueEx"
#define _REG_SET_FUNCTION "RegSetValueEx"
#define _REG_OPEN_FUNCTION "RegOpenKeyEx"
#define _REG_CREATE_FUNCTION "RegCreateKeyEx"
#define _REG_ENUM_VALUE_FUNCTION "RegEnumValue"
#define _REG_ENUM_KEY_FUNCTION "RegEnumKeyEx"

namespace Nvidia {
namespace Logging {

/// @brief Exception thrown by RegistryKey methods & constructor.
class RegistryException : public std::runtime_error {
public:
    RegistryException(const std::string& function, long returnCode);
    RegistryException(const std::string& message);
};

/// @brief High-level wrapper around registry-related Windows API functions.
class RegistryKey {
public:
    /// @brief The value returned as an error code for TryGetValue where the
    /// type stored in the registry is not the same as the type we're trying
    /// to store it into.
    static const long WrongType = 15302;

    /// @brief The default buffer size to retrieve data from the registry.
    /// If a larger buffer is needed, a new one will be allocated, but this
    /// seems big enough to get most data without a reallocation.
    static const size_t DefaultBufferSize = 1024; 

private:
    /// @brief Handle of the wrapped registry key.
    HKEY m_keyHandle;
    /// @brief Mask specifying access rights to this key.
    DWORD m_accessRights;

public:
    /**
     * @brief Constructor. Creates a RegistryKey wrapping either the registry key represented by
     * keyHandle, or (optionally) it's subkey named subKeyName.
     *
     * @param keyHandle A handle to an open registry key.
     * @param subKeyName The name of the registry subkey to be opened.
     * If this parameter is NULL or a pointer to an empty string, this RegistryKey will wrap a new
     * handle to the key identified by the keyHandle parameter.
     * @param accessRights The requested access rights for the key 
     * @param create Specifies whether the key should be created if it currently doesn't exist
     * @throws RegistryException If opening the key or retrieving information about it failed.
     */
    RegistryKey(HKEY keyHandle, const std::tstring& subKeyName = _T(""), DWORD accessRights = KEY_READ, bool create=false);

    /**
     * @brief Constructor. Creates a RegistryKey wrapping either the registry key represented by
     * keyHandle, or (optionally) it's subkey named subKeyName.
     *
     * @param keyHandle A handle to an open registry key.
     * @param subKeyName The name of the registry subkey to be opened.
     * If this parameter is NULL or a pointer to an empty string, this RegistryKey will wrap a new
     * handle to the key identified by the keyHandle parameter.
     * @param disposition Out paramter in which client gets disposition of newly created/accessed registry key.
     * @param accessRights The requested access rights for the key 
     * @param create Specifies whether the key should be created if it currently doesn't exist
     * @throws RegistryException If opening the key or retrieving information about it failed.
     */
    RegistryKey(HKEY keyHandle, const std::tstring& subKeyName, DWORD &disposition, DWORD accessRights = KEY_READ, bool create=false);

    /**
     * @brief Copy constructor. Creates a RegistryKey wrapping a new
     * handle to the key wrapped by aRegistryKey.
     * @throws RegistryException If opening the key or retrieving information about it failed.
     */
    RegistryKey(const RegistryKey& aRegistryKey);

    /// @brief Destructor. Closes the wrapped registry key handle.
    ~RegistryKey();

private:
    /**
     * @brief private constructor used by TryOpen to create a RegistryKey without
     * doing any initialization.
     */
    RegistryKey();

public:
    /**
     * @brief Copy assignment operator. Creates a RegistryKey wrapping a new
     * handle to the key wrapped by aRegistryKey.
     * @throws RegistryException If opening the key or retrieving information about it failed.
     */
    RegistryKey& operator=(const RegistryKey& aRegistryKey);
    
    /**
     * @brief Try to move the registry key to the given subkey
     *
     * @param keyHandle A handle to an open registry key.
     * @param subKeyName The name of the registry subkey to be opened.
     * If this parameter is NULL or a pointer to an empty string, this RegistryKey will wrap a new
     * handle to the key identified by the keyHandle parameter.
     * @param accessRights The requested access rights for the key 
     * @param create Specifies whether the key should be created if it currently doesn't exist
     * @param options The create options to pass, such as REG_OPTION_VOLATILE. Only useful when create = true
     * @return The error code from the Windows registry call. ERROR_SUCCESS means
     * the key was created.
     */
    static long TryOpen(boost::optional<RegistryKey> &key, HKEY keyHandle, const std::tstring &subkeyName, 
        DWORD accessRights = KEY_READ, bool create = false, DWORD options = 0);

    /**
     * @brief Try to open a registry key from HKCU of the user the current thread is impersonating
     *
     * @param subKeyName The name of the registry subkey to be opened.
     * If this parameter is NULL or a pointer to an empty string, this RegistryKey will wrap a new
     * handle to the key identified by the keyHandle parameter.
     * @param accessRights The requested access rights for the key 
     * @param create Specifies whether the key should be created if it currently doesn't exist
     * @param options The create options to pass, such as REG_OPTION_VOLATILE. Only useful when create = true
     * @return The error code from the Windows registry call. ERROR_SUCCESS means
     * the key was created.
     */
    static long TryOpenCurrentUser(boost::optional<RegistryKey> &key, const std::tstring &subkeyName, 
        DWORD accessRights = KEY_READ, bool create = false, DWORD options = 0);

    /**
     * @brief Returns a RegistryKey wrapping a subkey of this RegistryKey.
     *
     * @param subKeyName Name of the subkey.
     * @throws RegistryException If opening the subkey or retrieving information about it failed.
     */
    RegistryKey GetSubKey(const std::tstring& subKeyName) const;

    /**
     * @brief Returns a RegistryKey wrapping a subkey of this RegistryKey.
     *
     * @param subKeyName Name of the subkey.
     * @throws RegistryException If opening the subkey or retrieving information about it failed.
     */
    long TryGetSubKey(boost::optional<RegistryKey> &key, const std::tstring& subKeyName) const;

    /**
     * @brief Returns a RegistryKey wrapping a subkey of this RegistryKey.
     *
     * @param subKeyName Name of the subkey.
     * @param accessRights the access right mask representing access rights to use to open this key
     * @param create Specifies whether the key should be created if it doesn't exist
     * @param options Registry options like REG_OPTION_VOLATILE etc.
     * @throws RegistryException If opening the subkey or retrieving information about it failed.
     */
    long TryGetSubKey(boost::optional<RegistryKey> &key, const std::tstring& subKeyName, DWORD accessRights, bool create, DWORD options);

    /**
     * @brief Delete a sub key and its branch if it exists.
     *
     * @param subKeyName Name of the subkey.
     * @return The error code from the Windows registry call. ERROR_SUCCESS means
     * the value was deleted.
     */
    long TryDeleteSubKey(const std::tstring &subKeyName) const;

    /**
     * @brief Delete a value if it exists.
     *
     * @param valueName Name of the registry value.
     * @return The error code from the Windows registry call. ERROR_SUCCESS means
     * the value was deleted.
     */
    long TryDeleteValue(const std::tstring &valueName) const;

    /**
     * @brief Returns the data of the particular DWORD value.
     *
     * @param valueName Name of the registry value.
     * @throws RegistryException If value with such name doesn't exist or its type is different than DWORD.
     */
    DWORD GetDWORDValue(const std::tstring& valueName) const;

    /**
     * @brief Tries to get a DWORD value.
     *
     * @param valueName Name of the registry value.
     * @param[out] value To be updated with the new value if it exists.
     * @retval true When the value exists and is a DWORD. value will be updated.
     * @retval The error code from the Windows registry call. ERROR_SUCCESS means
     * the value was retrieved.
     */
    long TryGetValue(const std::tstring &valueName, DWORD &value) const;

    /**
    * @brief Sets the data of the particular DWORD value.
    *
    * @param valueName Name of the registry value.
    * @param value DWORD value to set.
    * @throws RegistryException If value with such name exists, but its type is different than string.
    */
    void SetDWORDValue(const std::tstring& valueName, DWORD value) const;

    /**
    * @brief Returns the data of the particular string value.
    *
    * @param valueName Name of the registry value.
    * @throws RegistryException If value with such name doesn't exist or its type is different than string.
    */
    std::tstring GetStringValue(const std::tstring& valueName) const;
    
    /**
     * @brief Tries to get a string value.
     *
     * @param valueName Name of the registry value.
     * @param[out] value To be updated with the new value if it exists.
     * @return The error code from the Windows registry call. ERROR_SUCCESS means
     * the value was retrieved.
     */
    long TryGetValue(const std::tstring &valueName, std::tstring &value) const;

    /**
    * @brief Sets the data of the particular string value.
    *
    * @param valueName Name of the registry value.
    * @param value String value to set.
    * @throws RegistryException If value with such name exists, but its type is different than string.
    */
    void SetStringValue(const std::tstring& valueName, const std::tstring& value) const;

    /**
    * @brief Returns the data of the particular binary value.
    *
    * @param[in] valueName Name of the registry value.
    * @param[out] buffer The buffer to return the requested value in. Can be NULL if only the size is requested
    * @param[in, out] bufferSize The size of the provided buffer. On output specifies the size of data queried
    * @throws RegistryException If value with such name doesn't exist or its type is different than string.
    */
    void GetBinaryValue(const std::tstring & valueName, LPBYTE buffer, size_t & bufferSize) const;

    /**
     * @brief Tries to get a binary value.
     *
     * @param valueName Name of the registry value.
     * @param[out] value To be updated with the new value if it exists.
     * @return The error code from the Windows registry call. ERROR_SUCCESS means
     * the value was retrieved.
     */
    long TryGetValue(const std::tstring &valueName, boost::shared_array<char> &value, size_t &valueSize) const;

    /**
    * @brief Sets the data of the particular binary value.
    *
    * @param valueName Name of the registry value.
    * @param buffer The buffer containing the value to set
    * @param bufferSize The size fo the data buffer passed in.
    * @throws RegistryException If value with such name exists, but its type is different than string.
    */
    void SetBinaryValue(const std::tstring & valueName, LPBYTE buffer, size_t bufferSize) const;
   /**
    * @brief Creates a sub key of this key.
    *
    * @param subKeyName Sub key name.
    * @throws RegistryException If RegCreateKeyEx failed.
    */
    RegistryKey CreateSubKey(const std::tstring& subKeyName) const;


    /**
     * @brief Returns the set of value names stored under this key.
     *
     * @return error code from the registry functions.
     */
    long TryGetValueNames(std::set<std::tstring> &valueNames) const;

    /**
     * @brief Returns the set of value names stored under this key.
     *
     * @throws RegistryException If enumerating the values failed.
     */
    std::set<std::tstring> GetValueNames() const;

    /**
     * @brief Returns the set of subkey names stored under this key.
     *
     * @throws RegistryException If enumerating the subkeys failed.
     */
    std::set<std::tstring> GetSubKeyNames() const;

    /**
     * @brief Returns the set of subkey names stored under this key.
     */
    long TryGetSubKeyNames(std::set<std::tstring> &set) const;

    /**
     * @brief Notifies the caller about changes to the attributes or contents of this key.
     */
    long NotifyChangeKeyValue(bool watchSubtree, DWORD notifyFilter, HANDLE hEvent, bool fAsynchronous) const;

private:
    /**
     * @brief Throws a RegistryException if errorCode is not ERROR_SUCCESS.
     */
    static void CheckErrorCode(long errorCode, const char *functionName);

    /**
     * @brief common code for TryGet* 
     *
     * This method will allocate space for buffer when successful. The caller
     * is responsible for allocating and deleting the buffer. 
     *
     * @param valueName Name of the registry value.
     * @param[out] buffer The buffer containing the value to set.
     * @param[in, out] bufferSize In: number of bytes in buffer. Out: the number of bytes in the value.
     * @param expectedType The expected type of the entry.
     * @return The error code from the Windows registry call. ERROR_SUCCESS means
     * the value was retrieved.
     */
    long TryGetValue(const std::tstring &valueName, void *buffer, size_t &bufferSize, DWORD expectedType) const;

    /**
     * @brief Broken out from GetValueNames and GetSubKeyNames to reset the size of the buffer should it be too short.
     * @param buffer The buffer to store the value name at the given index. Can be modified to increase the size.
     * @param bufferSize The size of the value returned.
     * @param index The index into the value names list.
     * @param function The enum function to call, either RegEnumValue or RegEnumKeyEx
     */
    template <class TFunction>
    long EnumName(std::auto_ptr<TCHAR> &buffer, DWORD &bufferSize, unsigned int index,
        TFunction function) const;

    /**
     * Split out from constructors and TryOpen functionality, this opens a registry key,
     * stored in newKey, or returns an error.
     *
     * @param keyHandle The base key
     * @param subKeyName The sub key under keyHandle to get the key for. Can be empty if reopening the
     * same key.
     * @param disposition Optional paramter in which client gets disposition of newly created/accessed registry key.
     * Can be NULL.
     * @param accessRights The requested access rights for the key 
     * @param create Specifies whether the key should be created if it currently doesn't exist
     * @param options The create key options
     * @param[out] newKey The newly-opened key if return is ERROR_SUCCESS.
     * @param[out] functionName The name of the function used to open the key -- can be used for logging an error.
     * @return The error code from the Windows registry call. ERROR_SUCCESS means
     * the key was created.
     */
    static long OpenKey(HKEY keyHandle, const std::tstring& subKeyName, 
        DWORD *disposition, DWORD accessRights, bool create, DWORD options, HKEY &newKey, const char *&functionName);
};

inline RegistryException::RegistryException(const std::string& function, long returnCode) :
    std::runtime_error("RegistryException: " + function + " returned " + boost::lexical_cast<std::string>(returnCode)) {}

inline RegistryException::RegistryException(const std::string& message) :
    std::runtime_error("RegistryException: " + message) {}

inline long RegistryKey::OpenKey(HKEY keyHandle, const std::tstring& subKeyName,
                                 DWORD *disposition, DWORD accessRights, 
                                 bool create, DWORD options, HKEY &newKey, const char *&functionName)
{
    long openErrorCode = ERROR_SUCCESS;
    if (create)
    {
        functionName = _REG_CREATE_FUNCTION;
        openErrorCode = MOCK_WINAPI::RegCreateKeyEx(keyHandle, subKeyName.c_str(), 
            0, NULL, options, accessRights, NULL, &newKey, disposition);
    }
    else
    {
        functionName = _REG_OPEN_FUNCTION;
        openErrorCode = MOCK_WINAPI::RegOpenKeyEx(keyHandle, subKeyName.c_str(), 
            0, accessRights, &newKey);
    }
    return openErrorCode;
}

inline RegistryKey::RegistryKey(HKEY keyHandle, const std::tstring& subKeyName, DWORD accessRights, bool create) 
    : m_keyHandle(NULL)
    , m_accessRights(accessRights)
{
    const char *functionName;
    long openErrorCode = OpenKey(keyHandle, subKeyName, NULL, accessRights, create, 0, m_keyHandle, functionName);
    CheckErrorCode(openErrorCode, functionName);
}

inline RegistryKey::RegistryKey(HKEY keyHandle, const std::tstring& subKeyName, DWORD &disposition, DWORD accessRights, bool create)
    : m_keyHandle(NULL)
    , m_accessRights(accessRights)
{
    const char *functionName;
    long openErrorCode = OpenKey(keyHandle, subKeyName, &disposition, accessRights, create, 0, m_keyHandle, functionName);
    CheckErrorCode(openErrorCode, functionName);
}

inline RegistryKey::RegistryKey(const RegistryKey& aRegistryKey)
    : m_keyHandle(NULL)
    , m_accessRights(aRegistryKey.m_accessRights)
{
    long openErrorCode = MOCK_WINAPI::RegOpenKeyEx(aRegistryKey.m_keyHandle, 0, 0, m_accessRights, &m_keyHandle);
    CheckErrorCode(openErrorCode, _REG_OPEN_FUNCTION);
}

inline RegistryKey& RegistryKey::operator=(const RegistryKey& aRegistryKey)
{
    m_accessRights = aRegistryKey.m_accessRights;
    MOCK_WINAPI::RegCloseKey(m_keyHandle);
    m_keyHandle = NULL;
    long openErrorCode = MOCK_WINAPI::RegOpenKeyEx(aRegistryKey.m_keyHandle, 0, 0, m_accessRights, &m_keyHandle);
    CheckErrorCode(openErrorCode, _REG_OPEN_FUNCTION);
    return *this;
}

inline void RegistryKey::CheckErrorCode(long openErrorCode, const char *functionName)
{
    if ( openErrorCode != ERROR_SUCCESS )
    {
        BOOST_THROW_EXCEPTION(RegistryException(functionName, openErrorCode));
    }
}

inline RegistryKey::RegistryKey() : m_keyHandle(HKEY_LOCAL_MACHINE), m_accessRights(KEY_ALL_ACCESS) {}

inline long RegistryKey::TryOpen(boost::optional<RegistryKey> &key, 
                                 HKEY keyHandle, 
                                 const std::tstring& subKeyName, 
                                 DWORD accessRights, 
                                 bool create,
                                 DWORD options)
{
    HKEY newKey = NULL;
    const char *functionName = NULL;
    long openErrorCode = OpenKey(keyHandle, subKeyName, NULL, accessRights, create, options, newKey, functionName);
    if (openErrorCode == ERROR_SUCCESS)
    {
        RegistryKey registryKey;
        registryKey.m_accessRights = accessRights;
        registryKey.m_keyHandle = newKey;
        key = registryKey;
    }
    return openErrorCode;
}

inline long RegistryKey::TryOpenCurrentUser(boost::optional<RegistryKey> &key, 
                                            const std::tstring& subKeyName, 
                                            DWORD accessRights, 
                                            bool create,
                                            DWORD options)
{
    HKEY curUserKey;
    long openErrorCode = MOCK_WINAPI::RegOpenCurrentUser(accessRights, &curUserKey);

    if (openErrorCode == ERROR_SUCCESS)
    {
        openErrorCode = TryOpen(key, curUserKey, subKeyName, accessRights, create, options);

        MOCK_WINAPI::RegCloseKey(curUserKey);
    }
    return openErrorCode;
}

inline RegistryKey::~RegistryKey()
{
    MOCK_WINAPI::RegCloseKey(m_keyHandle);
}

inline long RegistryKey::TryDeleteSubKey(const std::tstring &subKeyName) const
{
    long result = MOCK_WINAPI::SHDeleteKey(m_keyHandle, subKeyName.c_str());
    return result;
}

inline long RegistryKey::TryDeleteValue(const std::tstring &valueName) const
{
    return MOCK_WINAPI::RegDeleteValue(m_keyHandle, valueName.c_str());
}

inline RegistryKey RegistryKey::GetSubKey(const std::tstring& subKeyName) const
{
    return RegistryKey(m_keyHandle, subKeyName);
}

inline long RegistryKey::TryGetSubKey(boost::optional<RegistryKey> &key, const std::tstring& subKeyName) const
{
    return RegistryKey::TryOpen(key, m_keyHandle, subKeyName);
}

inline long RegistryKey::TryGetSubKey(boost::optional<RegistryKey> &key, const std::tstring& subKeyName, DWORD accessRights, bool create, DWORD options)
{
    return RegistryKey::TryOpen(key, m_keyHandle, subKeyName, accessRights, create, options);
}

inline std::tstring RegistryKey::GetStringValue(const std::tstring& valueName) const
{
    std::tstring value;
    int returnCode = TryGetValue(valueName, value);
    CheckErrorCode(returnCode, _REG_QUERY_FUNCTION);
    return value;
}

inline long RegistryKey::TryGetValue(const std::tstring &valueName,
                                     void *buffer,
                                     size_t &bufferSize,
                                     DWORD expectedType) const
{
    DWORD valueSize = DWORD(bufferSize);
    DWORD valueType;

    long returnCode = MOCK_WINAPI::RegQueryValueEx(m_keyHandle, valueName.c_str(), NULL,
        &valueType, reinterpret_cast<LPBYTE>(buffer), &valueSize);

    bufferSize = valueSize;
    if (returnCode == ERROR_SUCCESS && valueType != expectedType)
    {
        returnCode = WrongType;
    }
    return returnCode;
}

inline long RegistryKey::TryGetValue(const std::tstring &valueName, DWORD &value) const
{
    size_t size = sizeof(DWORD);
    return TryGetValue(valueName, reinterpret_cast<void *>(&value), size, REG_DWORD);
}

inline long RegistryKey::TryGetValue(const std::tstring &valueName, boost::shared_array<char> &value, size_t &bufferSize) const
{
    value.reset(new char[DefaultBufferSize]);
    bufferSize = DefaultBufferSize;
    long returnCode = TryGetValue(valueName, reinterpret_cast<void *>(value.get()), bufferSize, REG_BINARY);
    if (returnCode == ERROR_MORE_DATA)
    {
        value.reset(new char[bufferSize]);
        returnCode = TryGetValue(valueName, reinterpret_cast<void *>(value.get()), bufferSize, REG_BINARY);
    }
    return returnCode;
}

inline long RegistryKey::TryGetValue(const std::tstring &valueName, std::tstring &value) const
{
    boost::shared_array<TCHAR> buffer(new TCHAR[DefaultBufferSize]);
    DWORD bufferSize = DefaultBufferSize * sizeof(TCHAR);
    
    DWORD valueType;

    long returnCode = MOCK_WINAPI::RegQueryValueEx(m_keyHandle, valueName.c_str(), NULL, &valueType, reinterpret_cast<LPBYTE>(buffer.get()), &bufferSize);
    if (returnCode == ERROR_MORE_DATA)
    {
        buffer.reset(new TCHAR[bufferSize + 1]);
        returnCode = MOCK_WINAPI::RegQueryValueEx(m_keyHandle, valueName.c_str(), NULL, &valueType, reinterpret_cast<LPBYTE>(buffer.get()), &bufferSize);
    }

    if (returnCode == ERROR_SUCCESS)
    {
        size_t stringLength = bufferSize/sizeof(TCHAR);
        if ( valueType != REG_SZ && valueType != REG_EXPAND_SZ)
        {
            returnCode = WrongType;
        }
        else if (buffer[stringLength - 1] == _T('\0'))
        {   // null-terminated
            value = buffer.get();
        }
        else
        {   // not null-terminated
            value.clear();
            value.append(buffer.get(), stringLength);
        }
    }

    return returnCode;
}

inline DWORD RegistryKey::GetDWORDValue(const std::tstring& valueName) const
{
    DWORD valueData;
    long returnCode = TryGetValue(valueName, valueData);

    CheckErrorCode(returnCode, _REG_QUERY_FUNCTION);
    return valueData;
}

inline void RegistryKey::SetDWORDValue(const std::tstring& valueName, DWORD value) const
{
    long returnCode = MOCK_WINAPI::RegSetValueEx(m_keyHandle, valueName.c_str(), 0, 
                                        REG_DWORD, reinterpret_cast<LPBYTE>(&value), sizeof(DWORD));
    CheckErrorCode(returnCode, _REG_SET_FUNCTION);
}

inline void RegistryKey::GetBinaryValue( const std::tstring & valueName, LPBYTE buffer, size_t &bufferSize ) const
{
    long returnCode = TryGetValue(valueName, reinterpret_cast<void *>(buffer), bufferSize, REG_BINARY);

    CheckErrorCode(returnCode, _REG_QUERY_FUNCTION);
}

inline void RegistryKey::SetBinaryValue( const std::tstring & valueName, LPBYTE buffer, size_t bufferSize ) const
{
    long returnCode = MOCK_WINAPI::RegSetValueEx(m_keyHandle, valueName.c_str(), 0, REG_BINARY, buffer, (DWORD) bufferSize);

    CheckErrorCode(returnCode, _REG_SET_FUNCTION);
}

inline void RegistryKey::SetStringValue(const std::tstring& valueName, const std::tstring& value) const
{
    std::tstring valuePlusOneChar = value + _T(" ");
    long returnCode = MOCK_WINAPI::RegSetValueEx(m_keyHandle, valueName.c_str(), 0, REG_SZ, (LPBYTE)(value.c_str()), (DWORD) (valuePlusOneChar.size() * sizeof(TCHAR)) );

    CheckErrorCode(returnCode, _REG_SET_FUNCTION);
}

inline RegistryKey RegistryKey::CreateSubKey(const std::tstring& subKeyName) const
{
    HKEY key;
    long returnCode = MOCK_WINAPI::RegCreateKeyEx(m_keyHandle, subKeyName.c_str(), 0, 0, 0, KEY_ALL_ACCESS, 0, &key, 0);

    CheckErrorCode(returnCode, _REG_SET_FUNCTION);
    return RegistryKey(key);
}

inline long RegistryKey::TryGetValueNames(std::set<std::tstring> &valueNames) const
{
    std::auto_ptr<TCHAR> valueNameBuffer(new TCHAR[DefaultBufferSize]);
    long returnCode = ERROR_SUCCESS;
    DWORD thisValueNameSize = DefaultBufferSize;

    unsigned int index = 0;
    while ((returnCode = EnumName(valueNameBuffer, thisValueNameSize, index, MOCK_WINAPI::RegEnumValue)) == ERROR_SUCCESS)
    {
        valueNames.insert(valueNameBuffer.get());
        index++;
    }

    if (returnCode == ERROR_NO_MORE_ITEMS)
    {
        returnCode = ERROR_SUCCESS;
    }
    return returnCode;
}

inline std::set<std::tstring> RegistryKey::GetValueNames() const
{
    std::set<std::tstring> set;
    long returnCode = TryGetValueNames(set);
    CheckErrorCode(returnCode, _REG_ENUM_VALUE_FUNCTION);
    return set;
}

inline std::set<std::tstring> RegistryKey::GetSubKeyNames() const
{
    std::set<std::tstring> set;
    long returnCode = TryGetSubKeyNames(set);
    CheckErrorCode(returnCode, _REG_ENUM_KEY_FUNCTION);
    return set;
}

template <class TFunction>
inline long RegistryKey::EnumName(std::auto_ptr<TCHAR> &buffer, DWORD &bufferSize, unsigned int index, TFunction function) const
{
    DWORD valueSize = bufferSize;
    long returnCode = function(m_keyHandle, index, buffer.get(), &valueSize, 0, 0, 0, 0);
    if (returnCode == ERROR_MORE_DATA)
    {
        bufferSize = valueSize;
        buffer.reset(new TCHAR[bufferSize + 1]);
        returnCode = function(m_keyHandle, index, buffer.get(), &bufferSize, 0, 0, 0, 0);
    }
    return returnCode;
}

inline long RegistryKey::TryGetSubKeyNames(std::set<std::tstring> &set) const
{
    std::auto_ptr<TCHAR> buffer(new TCHAR[DefaultBufferSize]);
    long returnCode = ERROR_SUCCESS;
    DWORD bufferSize = DefaultBufferSize;

    for ( unsigned int i = 0 ; (returnCode = EnumName(buffer, bufferSize, i, MOCK_WINAPI::RegEnumKeyEx)) == ERROR_SUCCESS ; i++ )
    {
        set.insert(buffer.get());
    }
    if (returnCode == ERROR_NO_MORE_ITEMS)
    {
        returnCode = ERROR_SUCCESS;
    }
    return returnCode;
}

inline long RegistryKey::NotifyChangeKeyValue(bool watchSubtree, DWORD notifyFilter, HANDLE hEvent, bool fAsynchronous) const
{
    return MOCK_WINAPI::RegNotifyChangeKeyValue(m_keyHandle, watchSubtree, notifyFilter, hEvent, fAsynchronous);
}

}
}

#ifdef _NOT_MOCKED
#undef MOCK_WINAPI
#undef _NOT_MOCKED
#endif
