// ----------------------------------------------------------------------------
// Copyright (C) 2002-2005 Marcin Kalicinski
//
// Distributed under the Boost Software License, Version 1.0. 
// (See accompanying file LICENSE_1_0.txt or copy at 
// http://www.boost.org/LICENSE_1_0.txt)
//
// For more information, see www.boost.org
// ----------------------------------------------------------------------------
#ifndef BOOST_PROPERTY_TREE_REGISTRY_PARSER_HPP_INCLUDED
#define BOOST_PROPERTY_TREE_REGISTRY_PARSER_HPP_INCLUDED

// Include minimal version of windows.h if not included yet
#ifndef _WINDOWS_
#ifndef NOMINMAX
    #define NOMINMAX
#endif
#define STRICT
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#define NOGDICAPMASKS
#define NOVIRTUALKEYCODES
#define NOWINMESSAGES
#define NOWINSTYLES
#define NOSYSMETRICS
#define NOMENUS
#define NOICONS
#define NOKEYSTATES
#define NOSYSCOMMANDS
#define NORASTEROPS
#define NOSHOWWINDOW
#define OEMRESOURCE
#define NOATOM
#define NOCLIPBOARD
#define NOCOLOR
#define NOCTLMGR
#define NODRAWTEXT
#define NOGDI
#define NOKERNEL
#define NOUSER
#define NONLS
#define NOMB 
#define NOMEMMGR
#define NOMETAFILE
#define NOMSG
#define NOOPENFILE
#define NOSCROLL
#define NOSERVICE
#define NOSOUND
#define NOTEXTMETRIC
#define NOWH
#define NOWINOFFSETS
#define NOCOMM
#define NOKANJI
#define NOHELP
#define NOPROFILER
#define NODEFERWINDOWPOS
#define NOMCX
#include <windows.h>
#endif

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/detail/ptree_utils.hpp>
#include <boost/cstdint.hpp>                // for 64 bit int
#include <sstream>
#include <iomanip>
#include <string>
#include <vector>
#include <stdexcept>

namespace boost { namespace property_tree { namespace registry_parser
{

    //! Registry parser error
    class registry_parser_error: public ptree_error
    {
    public:
    
        // Construct error 
        registry_parser_error(const std::string &message, DWORD windows_error): 
            ptree_error(format_what(message, windows_error)), 
            m_windows_error(windows_error)
        { 
        }
    
        // Get windows error
        DWORD windows_error()
        {
            return m_windows_error;
        }
    
    private:

        DWORD m_windows_error;

        // Format error message to be returned by std::runtime_error::what()
        std::string format_what(const std::string &message,
                                DWORD windows_error)
        {
            std::stringstream stream;
            if (windows_error)
                stream << message << " (windows error 0x" << std::hex << windows_error << ")";
            else
                stream << message;
            return stream.str();
        }

    };

    // Translate from binary buffer to string
    template<class Ch>
    std::basic_string<Ch> translate(DWORD type, const std::vector<BYTE> &data)
    {

        typedef std::basic_string<Ch> Str;
        typedef std::basic_stringstream<Ch> Stream;

        Str value;
        switch (type)
        {
        
            // No data
            case REG_NONE:
                break;
            
            // Binary data
            case REG_BINARY: 
                if (!data.empty())
                {
                    Stream stream;
                    stream << std::hex << std::setfill(Ch('0'));
                    for (std::vector<BYTE>::const_iterator it = data.begin(), end = data.end(); 
                         it != end; ++it)
                        stream << std::setw(2) << static_cast<int>(*it) << Ch(' ');
                    value = stream.str();
                    value.resize(value.size() - 1); // remove final space
                }
                break;
            
            // DWORD value
            case REG_DWORD: 
                if (!data.empty())
                {
                    Stream stream;
                    stream << *reinterpret_cast<const DWORD *>(&data.front());
                    value = stream.str();
                }
                break;

            // QWORD value
            case REG_QWORD: 
                if (!data.empty())
                {
                    Stream stream;
                    stream << *reinterpret_cast<const boost::uint64_t *>(&data.front());
                    value = stream.str();
                }
                break;
            
            // Zero terminated string
            case REG_SZ: case REG_EXPAND_SZ: 
                if (!data.empty())
                    value.assign(reinterpret_cast<const Ch *>(&data.front()));
                break;
            
            // Unknown data type
            default:
                throw registry_parser_error("unsupported data type", 0);

        };
        return value;
    }

    // Translate from string to binary buffer
    template<class Ch>
    std::vector<BYTE> translate(DWORD type, const std::basic_string<Ch> &s)
    {

        typedef std::basic_string<Ch> Str;
        typedef std::basic_stringstream<Ch> Stream;

        std::vector<BYTE> data;
        switch (type)
        {
        
            // No data
            case REG_NONE:
                break;
            
            // Binary data
            case REG_BINARY:
                {
                    int v;
                    Stream stream(s);
                    stream >> std::hex;
                    while (1)
                    {
                        stream >> v >> std::ws;
                        if (stream.fail() || stream.bad())
                            throw registry_parser_error("bad REG_BINARY value", 0);
                        data.push_back(v);
                        if (stream.eof())
                            break;
                    }
                }
                break;
            
            // DWORD value
            case REG_DWORD: 
                {
                    DWORD v;
                    Stream stream(s);
                    stream >> v >> std::ws;
                    if (!stream.eof() || stream.fail() || stream.bad())
                        throw registry_parser_error("bad REG_DWORD value", 0);
                    for (size_t i = 0; i < sizeof(v); ++i)
                        data.push_back(*(reinterpret_cast<BYTE *>(&v) + i));
                }
                break;

            // QWORD value
            case REG_QWORD: 
                {
                    boost::uint64_t v;
                    Stream stream(s);
                    stream >> v;
                    if (!stream.eof() || stream.fail() || stream.bad())
                        throw registry_parser_error("bad REG_QWORD value", 0);
                    for (size_t i = 0; i < sizeof(v); ++i)
                        data.push_back(*(reinterpret_cast<BYTE *>(&v) + i));
                }
                break;
            
            // Zero terminated string
            case REG_SZ: case REG_EXPAND_SZ:
                {
                    const Ch *sz = s.c_str();
                    size_t len = (s.size() + 1) * sizeof(Ch);
                    for (size_t i = 0; i < len; ++i)
                        data.push_back(*(reinterpret_cast<const BYTE *>(sz) + i));
                }
                break;
            
            // Unknown data type
            default:
                throw registry_parser_error("unsupported data type", 0);

        };
        return data;
    }

    /////////////////////////////////////////////////////////////////////////////
    // Registry functions wrappers
    
    template<class Ch> 
    inline LONG reg_create_key_ex(HKEY hkey, const Ch *subkey, REGSAM sam, HKEY *result);

    template<> 
    inline LONG reg_create_key_ex<char>(HKEY hkey, const char *subkey, REGSAM sam, HKEY *result)
    {
        return RegCreateKeyExA(hkey, subkey, 0, NULL, REG_OPTION_NON_VOLATILE, sam, NULL, result, NULL);
    }
    
    template<> 
    inline LONG reg_create_key_ex<wchar_t>(HKEY hkey, const wchar_t *subkey, REGSAM sam, HKEY *result)
    {
        return RegCreateKeyExW(hkey, subkey, 0, NULL, REG_OPTION_NON_VOLATILE, sam, NULL, result, NULL);
    }

    template<class Ch> 
    inline LONG reg_set_value_ex(HKEY hkey, const Ch *name, DWORD type, const BYTE *data, DWORD size);

    template<> 
    inline LONG reg_set_value_ex<char>(HKEY hkey, const char *name, DWORD type, const BYTE *data, DWORD size)
    {
        return RegSetValueExA(hkey, name, 0, type, data, size);
    }

    template<> 
    inline LONG reg_set_value_ex<wchar_t>(HKEY hkey, const wchar_t *name, DWORD type, const BYTE *data, DWORD size)
    {
        return RegSetValueExW(hkey, name, 0, type, data, size);
    }

    template<class Ch> 
    inline LONG reg_open_key_ex(HKEY hkey, const Ch *subkey, REGSAM sam, HKEY *result);

    template<> 
    inline LONG reg_open_key_ex<char>(HKEY hkey, const char *subkey, REGSAM sam, HKEY *result)
    {
        return RegOpenKeyExA(hkey, subkey, 0, sam, result);
    }
    
    template<> 
    inline LONG reg_open_key_ex<wchar_t>(HKEY hkey, const wchar_t *subkey, REGSAM sam, HKEY *result)
    {
        return RegOpenKeyExW(hkey, subkey, 0, sam, result);
    }

    template<class Ch> 
    inline LONG reg_enum_key_ex(HKEY hkey, DWORD index, Ch *name, DWORD *size);

    template<> 
    inline LONG reg_enum_key_ex<char>(HKEY hkey, DWORD index, char *name, DWORD *size)
    {
        FILETIME ft;
        return RegEnumKeyExA(hkey, index, name, size, 0, NULL, NULL, &ft);
    }

    template<> 
    inline LONG reg_enum_key_ex<wchar_t>(HKEY hkey, DWORD index, wchar_t *name, DWORD *size)
    {
        FILETIME ft;
        return RegEnumKeyExW(hkey, index, name, size, 0, NULL, NULL, &ft);
    }

    template<class Ch> 
    inline LONG reg_enum_value(HKEY hkey, DWORD index, Ch *name, DWORD *name_size, DWORD *type, BYTE *data, DWORD *data_size);

    template<> 
    inline LONG reg_enum_value<char>(HKEY hkey, DWORD index, char *name, DWORD *name_size, DWORD *type, BYTE *data, DWORD *data_size)
    {
        return RegEnumValueA(hkey, index, name, name_size, NULL, type, data, data_size);
    }

    template<> 
    inline LONG reg_enum_value<wchar_t>(HKEY hkey, DWORD index, wchar_t *name, DWORD *name_size, DWORD *type, BYTE *data, DWORD *data_size)
    {
        return RegEnumValueW(hkey, index, name, name_size, NULL, type, data, data_size);
    }

    template<class Ch> 
    inline LONG reg_query_info_key(HKEY hkey, DWORD *max_subkey_len, DWORD *max_name_len, DWORD *max_value_len);

    template<> 
    inline LONG reg_query_info_key<char>(HKEY hkey, DWORD *max_subkey_len, DWORD *max_name_len, DWORD *max_value_len)
    {
        return RegQueryInfoKeyA(hkey, NULL, NULL, NULL, NULL, max_subkey_len, NULL, NULL, max_name_len, max_value_len, NULL, NULL);
    }

    template<> 
    inline LONG reg_query_info_key<wchar_t>(HKEY hkey, DWORD *max_subkey_len, DWORD *max_name_len, DWORD *max_value_len)
    {
        return RegQueryInfoKeyW(hkey, NULL, NULL, NULL, NULL, max_subkey_len, NULL, NULL, max_name_len, max_value_len, NULL, NULL);
    }

    /////////////////////////////////////////////////////////////////////////////
    // Registry key handle wrapper
    
    template<class Ch>
    class reg_key
    {
    public:
        typedef std::basic_string<Ch> Str;
        reg_key(HKEY root, const std::basic_string<Ch> &key, bool create):
            hkey(0)
        {
            if (create)
            {
                LONG result = reg_create_key_ex(root, key.c_str(), KEY_WRITE, &hkey);
                if (result != ERROR_SUCCESS)
                    throw registry_parser_error("RegCreateKeyEx failed", result);
            }
            else
            {
                LONG result = reg_open_key_ex(root, key.c_str(), KEY_READ, &hkey);
                if (result != ERROR_SUCCESS)
                    throw registry_parser_error("RegOpenKeyEx failed", result);
            }
            BOOST_ASSERT(hkey);
        }
        ~reg_key()
        {
            BOOST_ASSERT(hkey);
            RegCloseKey(hkey);
        }
        HKEY handle()
        {
            BOOST_ASSERT(hkey);
            return hkey;
        }
    private:
        HKEY hkey;
    };
    
    /////////////////////////////////////////////////////////////////////////////
    // Registry parser
    
    //! Read registry
    template<class Ptree>
    void read_registry(HKEY root, 
                       const std::basic_string<typename Ptree::char_type> &key, 
                       Ptree &pt)
    {

        typedef typename Ptree::char_type Ch;
        typedef std::basic_string<Ch> Str;
        typedef std::basic_stringstream<Ch> Stream;
        
        Ptree local;
        
        // Open key
        reg_key<Ch> rk(root, key, false);
        
        // Query key info
        DWORD max_subkey_len, max_name_len, max_value_len;
        LONG result = reg_query_info_key<Ch>(rk.handle(), &max_subkey_len, &max_name_len, &max_value_len);
        if (result != ERROR_SUCCESS)
            throw registry_parser_error("RegQueryInfoKey failed", result);

        // For all subkeys
        std::vector<Ch> subkey(max_subkey_len + 1);
        for (DWORD index = 0; true; ++index)
        {
            
            // Get subkey name
            DWORD size = static_cast<DWORD>(subkey.size());
            LONG result = reg_enum_key_ex(rk.handle(), index, &subkey.front(), &size);
            if (result == ERROR_NO_MORE_ITEMS)
                break;
            if (result != ERROR_SUCCESS)
                throw registry_parser_error("RegEnumKeyEx failed", result);
            
            // Parse recursively
            Ptree &child = local.push_back(typename Ptree::value_type(&subkey.front(), Ptree()))->second;
            read_registry<Ptree>(rk.handle(), &subkey.front(), child);

        }

        // For all values
        for (DWORD index = 0; true; ++index)
        {

            // Resize data to max size
            std::vector<Ch> name(max_name_len + 1);
            std::vector<BYTE> data(max_value_len + 1);
            
            // Get name and value from registry
            DWORD name_size = static_cast<DWORD>(name.size());
            DWORD data_size = static_cast<DWORD>(data.size());
            DWORD type;
            result = reg_enum_value<Ch>(rk.handle(), index, &name.front(), &name_size, &type, &data.front(), &data_size);
            if (result == ERROR_NO_MORE_ITEMS)
                break;
            if (result != ERROR_SUCCESS)
                throw registry_parser_error("RegEnumValue failed", result);

            // Truncate data to actual size
            name.resize(name_size + 1);
            data.resize(data_size);

            // Translate and put value in tree
            Str value = translate<Ch>(type, data);
            if (name_size > 0)
            {
                local.put(Str(detail::widen<Ch>("\\values.") + &name.front()), value);
                local.put(Str(detail::widen<Ch>("\\types.") + &name.front()), type);
            }
            else
                local.data() = value;

        }

        // Swap pt and local
        pt.swap(local);

    }

    //! Write registry
    template<class Ptree>
    void write_registry(HKEY root, 
                        const std::basic_string<typename Ptree::char_type> &key, 
                        const Ptree &pt)
    {

        typedef typename Ptree::char_type Ch;
        typedef std::basic_string<Ch> Str;
        typedef std::basic_stringstream<Ch> Stream;
        
        // Create key
        reg_key<Ch> rk(root, key, true);

        // Set default key value
        if (!pt.data().empty())
        {
            std::vector<BYTE> data = translate<Ch>(REG_SZ, pt.data());
            reg_set_value_ex<Ch>(rk.handle(), NULL, REG_SZ, 
                                 data.empty() ? NULL : &data.front(), 
                                 static_cast<DWORD>(data.size()));
        }

        // Create values
        const Ptree &values = pt.get_child(detail::widen<Ch>("\\values"), empty_ptree<Ptree>());
        const Ptree &types = pt.get_child(detail::widen<Ch>("\\types"), empty_ptree<Ptree>());
        for (typename Ptree::const_iterator it = values.begin(), end = values.end(); it != end; ++it)
        {
            DWORD type = types.get(it->first, REG_SZ);
            std::vector<BYTE> data = translate<Ch>(type, it->second.data());
            reg_set_value_ex<Ch>(rk.handle(), it->first.c_str(), type, 
                                 data.empty() ? NULL : &data.front(), 
                                 static_cast<DWORD>(data.size()));
        }

        // Create subkeys
        for (typename Ptree::const_iterator it = pt.begin(), end = pt.end(); it != end; ++it)
            if (&it->second != &values && &it->second != &types)
                write_registry(rk.handle(), it->first, it->second);

    }

} } }

namespace boost { namespace property_tree
{
    using registry_parser::read_registry;
    using registry_parser::write_registry;
    using registry_parser::registry_parser_error;
} }

#endif
