#include "registry.h"
#include "unicode.h"
#include "common/mallocHelper.h"

static const char* hKey2Str(HKEY hKey)
{
    #define HKEY_CASE(compareKey) if(hKey == compareKey) return #compareKey;
    HKEY_CASE(HKEY_CLASSES_ROOT)
    HKEY_CASE(HKEY_CURRENT_USER)
    HKEY_CASE(HKEY_LOCAL_MACHINE)
    HKEY_CASE(HKEY_USERS)
    HKEY_CASE(HKEY_PERFORMANCE_DATA)
    HKEY_CASE(HKEY_PERFORMANCE_TEXT)
    HKEY_CASE(HKEY_PERFORMANCE_NLSTEXT)
    HKEY_CASE(HKEY_CURRENT_CONFIG)
    HKEY_CASE(HKEY_DYN_DATA)
    HKEY_CASE(HKEY_CURRENT_USER_LOCAL_SETTINGS)
    #undef HKEY_CASE

    return "UNKNOWN";
}

bool ffRegOpenKeyForRead(HKEY hKey, const wchar_t* subKeyW, HKEY* result, FFstrbuf* error)
{
    if(RegOpenKeyExW(hKey, subKeyW, 0, KEY_READ, result) != ERROR_SUCCESS)
    {
        if(error)
        {
            FF_STRBUF_AUTO_DESTROY subKeyA = ffStrbufCreateWS(subKeyW);
            ffStrbufAppendF(error, "RegOpenKeyExW(%s\\%s) failed", hKey2Str(hKey), subKeyA.chars);
        }
        return false;
    }
    return true;
}

bool ffRegReadStrbuf(HKEY hKey, const wchar_t* valueNameW, FFstrbuf* result, FFstrbuf* error)
{
    DWORD bufSize; //with tailing '\0'
    if(RegGetValueW(hKey, NULL, valueNameW, RRF_RT_REG_SZ, NULL, NULL, &bufSize) != ERROR_SUCCESS)
    {
        if(error)
        {
            if(!valueNameW)
                valueNameW = L"(default)";
            FF_STRBUF_AUTO_DESTROY valueNameA = ffStrbufCreateWS(valueNameW);
            ffStrbufAppendF(error, "RegGetValueW(%s, NULL, RRF_RT_REG_SZ) failed", valueNameA.chars);
        }
        return false;
    }
    assert(bufSize >= sizeof(wchar_t));
    wchar_t* FF_AUTO_FREE resultW = (wchar_t*)malloc(bufSize);
    if(RegGetValueW(hKey, NULL, valueNameW, RRF_RT_REG_SZ, NULL, resultW, &bufSize) != ERROR_SUCCESS)
    {
        if(error)
        {
            if(!valueNameW)
                valueNameW = L"(default)";
            FF_STRBUF_AUTO_DESTROY valueNameA = ffStrbufCreateWS(valueNameW);
            ffStrbufAppendF(error, "RegGetValueW(%s, result, RRF_RT_REG_SZ) failed", valueNameA.chars);
        }
        return false;
    }
    ffStrbufSetNWS(result, (uint32_t) (bufSize / sizeof(*resultW) - 1), resultW);
    return true;
}

bool ffRegReadData(HKEY hKey, const wchar_t* valueNameW, uint8_t** result, uint32_t* length, FFstrbuf* error)
{
    assert(result && length);
    DWORD bufSize = 0;
    LONG err = RegGetValueW(hKey, NULL, valueNameW, RRF_RT_REG_BINARY, NULL, NULL, &bufSize);
    if(err != ERROR_SUCCESS || bufSize == 0)
    {
        if(error)
        {
            if(!valueNameW)
                valueNameW = L"(default)";
            FF_STRBUF_AUTO_DESTROY valueNameA = ffStrbufCreateWS(valueNameW);
            ffStrbufAppendF(error, "RegGetValueW(%s, NULL, RRF_RT_REG_BINARY, NULL, NULL, &bufSize) failed", valueNameA.chars);
        }
        return false;
    }

    uint8_t* buf = (uint8_t*) malloc(bufSize);
    err = RegGetValueW(hKey, NULL, valueNameW, RRF_RT_REG_BINARY, NULL, buf, &bufSize);
    if(err != ERROR_SUCCESS)
    {
        if(error)
        {
            if(!valueNameW)
                valueNameW = L"(default)";
            FF_STRBUF_AUTO_DESTROY valueNameA = ffStrbufCreateWS(valueNameW);
            ffStrbufAppendF(error, "RegGetValueW(%s, NULL, RRF_RT_REG_BINARY, NULL, length) failed", valueNameA.chars);
        }
        free(buf);
        return false;
    }
    *result = buf;
    *length = bufSize;
    return true;
}

bool ffRegReadUint(HKEY hKey, const wchar_t* valueNameW, uint32_t* result, FFstrbuf* error)
{
    DWORD bufSize = sizeof(*result);
    if(RegGetValueW(hKey, NULL, valueNameW, RRF_RT_DWORD, NULL, result, &bufSize) != ERROR_SUCCESS)
    {
        if(error)
        {
            if(!valueNameW)
                valueNameW = L"(default)";
            FF_STRBUF_AUTO_DESTROY valueNameA = ffStrbufCreateWS(valueNameW);
            ffStrbufAppendF(error, "RegGetValueW(%s, result, RRF_RT_DWORD) failed", valueNameA.chars);
        }
        return false;
    }
    return true;
}

bool ffRegReadUint64(HKEY hKey, const wchar_t* valueNameW, uint64_t* result, FFstrbuf* error)
{
    DWORD bufSize = sizeof(*result);
    if(RegGetValueW(hKey, NULL, valueNameW, RRF_RT_QWORD, NULL, result, &bufSize) != ERROR_SUCCESS)
    {
        if(error)
        {
            if(!valueNameW)
                valueNameW = L"(default)";
            FF_STRBUF_AUTO_DESTROY valueNameA = ffStrbufCreateWS(valueNameW);
            ffStrbufAppendF(error, "RegGetValueW(%s, result, RRF_RT_QWORD) failed", valueNameA.chars);
        }
        return false;
    }
    return true;
}

bool ffRegGetSubKey(HKEY hKey, uint32_t index, FFstrbuf* result, FFstrbuf* error)
{
    DWORD bufSize = 0;
    if(RegQueryInfoKeyW(hKey, NULL, NULL, NULL, NULL, &bufSize, NULL, NULL, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
    {
        if (error)
            ffStrbufAppendS(error, "RegQueryInfoKeyW(hKey) failed");
        return false;
    }
    ++bufSize;
    wchar_t* FF_AUTO_FREE resultW = (wchar_t*) malloc(bufSize * sizeof(*resultW));
    if(RegEnumKeyExW(hKey, index, resultW, &bufSize, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
    {
        if (error)
            ffStrbufAppendF(error, "RegEnumKeyExW(hKey, %u) failed", (unsigned) index);
        return false;
    }
    ffStrbufSetWS(result, resultW);
    return true;
}

bool ffRegGetNSubKeys(HKEY hKey, uint32_t* result, FFstrbuf* error)
{
    DWORD buffer;
    if(RegQueryInfoKeyW(hKey, NULL, NULL, NULL, &buffer, NULL, NULL, NULL, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
    {
        if (error)
            ffStrbufAppendS(error, "RegQueryInfoKeyW(hKey) failed");
        return false;
    }

    *result = buffer;
    return true;
}
