/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2015 Danny Robson <danny@nerdcruft.net>
 */

#include "registry.hpp"

#include "../cast.hpp"
#include "except.hpp"
#include "debug/panic.hpp"

#include <winerror.h>

#include <string>
#include <cstdint>

using cruft::win32::key;


///////////////////////////////////////////////////////////////////////////////
typedef uint64_t QWORD;

//-----------------------------------------------------------------------------
template <typename T> constexpr DWORD type_to_id (void);

template <> constexpr DWORD type_to_id<DWORD> (void) { return REG_DWORD; }
template <> constexpr DWORD type_to_id<QWORD> (void) { return REG_QWORD; }
template <> constexpr DWORD type_to_id<void>  (void) { return REG_NONE;  }
template <> constexpr DWORD type_to_id<std::string> (void) { return REG_SZ; }


//-----------------------------------------------------------------------------
template <typename T> constexpr DWORD restrict_to_id (void);

template <> constexpr DWORD restrict_to_id<DWORD> (void) { return RRF_RT_DWORD; }
template <> constexpr DWORD restrict_to_id<QWORD> (void) { return RRF_RT_QWORD; }
template <> constexpr DWORD restrict_to_id<void>  (void) { return RRF_RT_REG_NONE; }
template <> constexpr DWORD restrict_to_id<std::string> (void) { return RRF_RT_REG_SZ; }


///////////////////////////////////////////////////////////////////////////////
key::key (key const &parent, char const *path, REGSAM rights)
{
    auto err = RegOpenKeyEx (parent.m_handle, path, 0, rights, &m_handle);
    win32::error::try_code (err);
}

//-----------------------------------------------------------------------------
key::key (HKEY root, const char *child, REGSAM rights)
{
    auto err = RegOpenKeyEx (root, child, 0, rights, &m_handle);
    win32::error::try_code (err);
}


//-----------------------------------------------------------------------------
key::~key ()
{
    auto err = RegCloseKey (m_handle);
    win32::error::try_code (err);
}


///////////////////////////////////////////////////////////////////////////////
cruft::view<key::child_iterator>
key::subkeys (void)
{
    DWORD size;

    auto err = RegQueryInfoKey (
        m_handle,
        nullptr,
        nullptr,
        nullptr,
        &size,
        nullptr,  // longest subkey size
        nullptr,  // longest class string
        nullptr,  // number of values for this key
        nullptr,  // longest value name
        nullptr,  // longest value data
        nullptr,  // security descriptor
        nullptr   // last write time
    );
    error::try_code (err);

    return {
        child_iterator (*this),
        child_iterator (*this, size)
    };
}


//-----------------------------------------------------------------------------
key::child_iterator::child_iterator (key const &_parent)
    : child_iterator (_parent, 0)
{ ; }


//-----------------------------------------------------------------------------
key::child_iterator::child_iterator (key const &_parent, int _index)
    : m_parent (_parent)
    , m_index (_index)
{ ; }


//-----------------------------------------------------------------------------
key
key::child_iterator::operator* (void) const
{
    DWORD name_size = strlen ("{00000000-0000-0000-0000-000000000000}");
    std::string name (name_size, '\0');

    while (1) {
        auto const err = RegEnumKeyEx(
            m_parent.m_handle,
            m_index,
            name.data(), &name_size,
            nullptr,
            nullptr, nullptr,
            nullptr
        );

        switch (err) {
        case ERROR_SUCCESS:
            name.resize (name_size);
            return key (m_parent, name.c_str ());

        case ERROR_MORE_DATA:
            name_size *= 2;
            name.resize (name_size);
            continue;

        default:
            error::throw_code (err);
        }
    }

    unreachable ();
}


//-----------------------------------------------------------------------------
key::child_iterator&
key::child_iterator::operator++ ()
{
    ++m_index;
    return *this;
}


//-----------------------------------------------------------------------------
bool
key::child_iterator::operator== (child_iterator const &rhs)
{
    CHECK_EQ (&m_parent, &rhs.m_parent);
    return m_index == rhs.m_index;
}


//-----------------------------------------------------------------------------
bool
key::child_iterator::operator!= (child_iterator const &rhs)
{
    return !(*this == rhs);
}


///////////////////////////////////////////////////////////////////////////////
std::string
key::name (void) const
{
    // allocate an initial buffer large enough to contain a GUID given that
    // strings like these are pretty common.
    DWORD size = strlen ("{00000000-0000-0000-0000-000000000000}");
    std::string value (size, '\0');

    // keep attempting to read the name of the key. if it fails then we double
    // the size of the string and try again.
    while (1) {
        auto res = RegQueryInfoKey (
            m_handle,
            value.data (), &size,
            nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
        );

        switch (res) {
        case ERROR_SUCCESS:
            value.resize (size);
            return value;

        case ERROR_MORE_DATA:
            size *= 2;
            continue;

        default:
            win32::error::throw_code (res);
        }
    }

    unreachable ();
}


///////////////////////////////////////////////////////////////////////////////
template <typename T>
T
key::data (const char *name) const
{
    T     value;
    DWORD type;
    DWORD size = sizeof (value);

    auto err = RegGetValue (m_handle, name, &value, restrict_to_id<T> (), &type, &value, &size);
    win32::error::try_code (err);

    return value;
}


//-----------------------------------------------------------------------------
template <>
std::string
key::data (char const *name) const
{
    DWORD size = strlen ("{00000000-0000-0000-0000-000000000000}");
    std::string value (size, '\0');

    while (1) {
        DWORD type;
        auto const err = RegGetValue(
            m_handle, nullptr, name,
            restrict_to_id<std::string>(), &type,
            value.data(), &size
        );

        switch (err) {
        case ERROR_SUCCESS:
            value.resize (size);
            return value;

        case ERROR_MORE_DATA:
            size *= 2;
            value.resize (size);
            continue;

        default:
            error::throw_code (err);
        }
    }
}


//-----------------------------------------------------------------------------
std::set<std::string>
key::values (void) const
{
    std::set<std::string> all;

    for (DWORD i = 0; ; ++i) {
        std::string name (255, '\0');
        DWORD size = cruft::cast::narrow<DWORD> (name.size ());

        auto err = RegEnumValue (m_handle, i, &name[0], &size, nullptr, nullptr, nullptr, nullptr);
        if (ERROR_NO_MORE_ITEMS == err)
            return all;
        if (ERROR_SUCCESS != err)
            win32::error::throw_code (err);

        CHECK_GT (size, 0u);
        name.resize (size);

        all.insert (std::move (name));
    }
}