/* 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.
 */

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include <winsock2.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#include <shlwapi.h>
#include <tlhelp32.h>

#include "apr.h"
#include "apr_pools.h"
#include "apr_arch_misc.h"   /* for apr_os_level */
#include "apr_arch_atime.h"  /* for FileTimeToAprTime */

#include "tcn.h"

#define SAFE_CLOSE_KEY(k)                               \
    if ((k) != NULL && (k) != INVALID_HANDLE_VALUE) {   \
        RegCloseKey((k));                               \
        (k) = NULL;                                     \
    }

typedef struct {
    apr_pool_t     *pool;
    HKEY           root;
    HKEY           key;
} tcn_nt_registry_t;


#define TCN_HKEY_CLASSES_ROOT       1
#define TCN_HKEY_CURRENT_CONFIG     2
#define TCN_HKEY_CURRENT_USER       3
#define TCN_HKEY_LOCAL_MACHINE      4
#define TCN_HKEY_USERS              5

static const struct {
    HKEY k;
} TCN_KEYS[] = {
    INVALID_HANDLE_VALUE,
    HKEY_CLASSES_ROOT,
    HKEY_CURRENT_CONFIG,
    HKEY_CURRENT_USER,
    HKEY_LOCAL_MACHINE,
    HKEY_USERS,
    INVALID_HANDLE_VALUE
};

#define TCN_KEY_ALL_ACCESS          0x0001
#define TCN_KEY_CREATE_LINK         0x0002
#define TCN_KEY_CREATE_SUB_KEY      0x0004
#define TCN_KEY_ENUMERATE_SUB_KEYS  0x0008
#define TCN_KEY_EXECUTE             0x0010
#define TCN_KEY_NOTIFY              0x0020
#define TCN_KEY_QUERY_VALUE         0x0040
#define TCN_KEY_READ                0x0080
#define TCN_KEY_SET_VALUE           0x0100
#define TCN_KEY_WOW64_64KEY         0x0200
#define TCN_KEY_WOW64_32KEY         0x0400
#define TCN_KEY_WRITE               0x0800

#define TCN_REGSAM(s, x)                    \
        s = 0;                              \
        if (x & TCN_KEY_ALL_ACCESS)         \
            s |= KEY_ALL_ACCESS;            \
        if (x & TCN_KEY_CREATE_LINK)        \
            s |= KEY_CREATE_LINK;           \
        if (x & TCN_KEY_CREATE_SUB_KEY)     \
            s |= KEY_CREATE_SUB_KEY;        \
        if (x & TCN_KEY_ENUMERATE_SUB_KEYS) \
            s |= KEY_ENUMERATE_SUB_KEYS;    \
        if (x & TCN_KEY_EXECUTE)            \
            s |= KEY_EXECUTE;               \
        if (x & TCN_KEY_NOTIFY)             \
            s |= KEY_NOTIFY;                \
        if (x & TCN_KEY_READ)               \
            s |= KEY_READ;                  \
        if (x & TCN_KEY_SET_VALUE)          \
            s |= KEY_SET_VALUE;             \
        if (x & TCN_KEY_WOW64_64KEY)        \
            s |= KEY_WOW64_64KEY;           \
        if (x & TCN_KEY_WOW64_32KEY)        \
            s |= KEY_WOW64_32KEY;           \
        if (x & TCN_KEY_WRITE)              \
            s |= KEY_WRITE

#define TCN_REG_BINARY              1
#define TCN_REG_DWORD               2
#define TCN_REG_EXPAND_SZ           3
#define TCN_REG_MULTI_SZ            4
#define TCN_REG_QWORD               5
#define TCN_REG_SZ                  6

static const struct {
    DWORD t;
} TCN_REGTYPES[] = {
    REG_NONE,
    REG_BINARY,
    REG_DWORD,
    REG_EXPAND_SZ,
    REG_MULTI_SZ,
    REG_QWORD,
    REG_SZ,
    REG_NONE
};

static apr_status_t registry_cleanup(void *data)
{
    tcn_nt_registry_t *reg = (tcn_nt_registry_t *)data;

    if (reg) {
        SAFE_CLOSE_KEY(reg->key);
    }
    return APR_SUCCESS;
}

TCN_IMPLEMENT_CALL(jlong, Registry, create)(TCN_STDARGS, jint root, jstring name,
                                            jint sam, jlong pool)
{
    apr_pool_t *p = J2P(pool, apr_pool_t *);
    tcn_nt_registry_t *reg = NULL;
    TCN_ALLOC_WSTRING(name);
    HKEY key;
    LONG rc;
    REGSAM s;

    UNREFERENCED(o);
    TCN_ASSERT(pool != 0);

    if (root < TCN_HKEY_CLASSES_ROOT || root > TCN_HKEY_USERS) {
        tcn_ThrowException(e, "Invalid Registry Root Key");
        goto cleanup;
    }
    if (sam < TCN_KEY_ALL_ACCESS || root > TCN_KEY_WRITE) {
        tcn_ThrowException(e, "Invalid Registry Key Security");
        goto cleanup;
    }
    reg = (tcn_nt_registry_t *)apr_palloc(p, sizeof(tcn_nt_registry_t));
    reg->pool = p;
    reg->root = TCN_KEYS[root].k;
    reg->key  = NULL;
    TCN_INIT_WSTRING(name);
    TCN_REGSAM(s, sam);
    rc = RegCreateKeyExW(reg->root, J2W(name), 0, NULL, REG_OPTION_NON_VOLATILE,
                         s, NULL, &key, NULL);
    if (rc !=  ERROR_SUCCESS) {
        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
        goto cleanup;
    }
    reg->key = key;
    apr_pool_cleanup_register(p, (const void *)reg,
                              registry_cleanup,
                              apr_pool_cleanup_null);

cleanup:
    TCN_FREE_WSTRING(name);
    return P2J(reg);
}

TCN_IMPLEMENT_CALL(jlong, Registry, open)(TCN_STDARGS, jint root, jstring name,
                                          jint sam, jlong pool)
{
    apr_pool_t *p = J2P(pool, apr_pool_t *);
    tcn_nt_registry_t *reg = NULL;
    TCN_ALLOC_WSTRING(name);
    HKEY key;
    LONG rc;
    REGSAM s;

    UNREFERENCED(o);
    TCN_ASSERT(pool != 0);

    if (root < TCN_HKEY_CLASSES_ROOT || root > TCN_HKEY_USERS) {
        tcn_ThrowException(e, "Invalid Registry Root Key");
        goto cleanup;
    }
    if (sam < TCN_KEY_ALL_ACCESS || root > TCN_KEY_WRITE) {
        tcn_ThrowException(e, "Invalid Registry Key Security");
        goto cleanup;
    }
    reg = (tcn_nt_registry_t *)apr_palloc(p, sizeof(tcn_nt_registry_t));
    reg->pool = p;
    reg->root = TCN_KEYS[root].k;
    reg->key  = NULL;
    TCN_INIT_WSTRING(name);
    TCN_REGSAM(s, sam);
    rc = RegOpenKeyExW(reg->root, J2W(name), 0, s, &key);
    if (rc !=  ERROR_SUCCESS) {
        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
        goto cleanup;
    }
    reg->key = key;
    apr_pool_cleanup_register(p, (const void *)reg,
                              registry_cleanup,
                              apr_pool_cleanup_null);

cleanup:
    TCN_FREE_WSTRING(name);
    return P2J(reg);
}

TCN_IMPLEMENT_CALL(jint, Registry, close)(TCN_STDARGS, jlong reg)
{
    tcn_nt_registry_t *r = J2P(reg, tcn_nt_registry_t *);
    UNREFERENCED_STDARGS;

    TCN_ASSERT(reg != 0);

    registry_cleanup(r);
    apr_pool_cleanup_kill(r->pool, r, registry_cleanup);
    return APR_SUCCESS;
}

TCN_IMPLEMENT_CALL(jint, Registry, getType)(TCN_STDARGS, jlong key,
                                            jstring name)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    LONG rc;
    DWORD v;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    rc = RegQueryValueExW(k->key, J2W(name), NULL, &v, NULL, NULL);
    if (rc != ERROR_SUCCESS)
        v = -rc;
    TCN_FREE_WSTRING(name);
    switch (v) {
        case REG_BINARY:
            v = TCN_REG_BINARY;
            break;
        case REG_DWORD:
            v = TCN_REG_DWORD;
            break;
        case REG_EXPAND_SZ:
            v = TCN_REG_EXPAND_SZ;
            break;
        case REG_MULTI_SZ:
            v = TCN_REG_MULTI_SZ;
            break;
        case REG_QWORD:
            v = TCN_REG_QWORD;
            break;
        case REG_SZ:
            v = TCN_REG_SZ;
            break;
        case REG_DWORD_BIG_ENDIAN:
            v = 0;
            break;
    }
    return v;
}

TCN_IMPLEMENT_CALL(jint, Registry, getSize)(TCN_STDARGS, jlong key,
                                            jstring name)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    LONG rc;
    DWORD v;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, NULL, &v);
    if (rc != ERROR_SUCCESS)
        v = -rc;
    TCN_FREE_WSTRING(name);
    return v;
}

TCN_IMPLEMENT_CALL(jint, Registry, getValueI)(TCN_STDARGS, jlong key,
                                              jstring name)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    LONG rc;
    DWORD t, l;
    DWORD v = 0;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    rc = RegQueryValueExW(k->key, J2W(name), NULL, &t, NULL, &l);
    if (rc != ERROR_SUCCESS) {
        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
        goto cleanup;
    }
    if (t == REG_DWORD) {
        l = sizeof(DWORD);
        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, (LPBYTE)&v, &l);
        if (rc != ERROR_SUCCESS) {
            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
            goto cleanup;
        }
    }
    else if (t == REG_SZ || t == REG_BINARY ||
             t == REG_MULTI_SZ || t == REG_EXPAND_SZ)
        v = l;
    else {
        v = 0;
        tcn_ThrowException(e, "Unable to convert the value to integer");
    }
cleanup:
    TCN_FREE_WSTRING(name);
    return v;
}

TCN_IMPLEMENT_CALL(jlong, Registry, getValueJ)(TCN_STDARGS, jlong key,
                                               jstring name)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    LONG rc;
    DWORD t, l;
    UINT64 v = 0;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    rc = RegQueryValueExW(k->key, J2W(name), NULL, &t, NULL, &l);
    if (rc != ERROR_SUCCESS) {
        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
        goto cleanup;
    }
    if (t == REG_DWORD) {
        DWORD tv;
        l = sizeof(DWORD);
        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, (LPBYTE)&tv, &l);
        if (rc != ERROR_SUCCESS) {
            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
            goto cleanup;
        }
        v = tv;
    }
    else if (t == REG_QWORD) {
        l = sizeof(UINT64);
        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, (LPBYTE)&v, &l);
        if (rc != ERROR_SUCCESS) {
            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
            goto cleanup;
        }
    }
    else if (t == REG_SZ || t == REG_BINARY ||
             t == REG_MULTI_SZ || t == REG_EXPAND_SZ)
        v = l;
    else {
        v = 0;
        tcn_ThrowException(e, "Unable to convert the value to long");
    }
cleanup:
    TCN_FREE_WSTRING(name);
    return v;
}

TCN_IMPLEMENT_CALL(jstring, Registry, getValueS)(TCN_STDARGS, jlong key,
                                                 jstring name)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    LONG rc;
    DWORD t, l;
    jstring v = NULL;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    rc = RegQueryValueExW(k->key, J2W(name), NULL, &t, NULL, &l);
    if (rc != ERROR_SUCCESS) {
        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
        goto cleanup;
    }
    if (t == REG_SZ || t == REG_EXPAND_SZ) {
        jchar *vw = (jchar *)malloc(l);
        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, (LPBYTE)vw, &l);
        if (rc != ERROR_SUCCESS) {
            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
            free(vw);
            goto cleanup;
        }
        v = (*e)->NewString((e), vw, lstrlenW(vw));
        free(vw);
    }
cleanup:
    TCN_FREE_WSTRING(name);
    return v;
}

TCN_IMPLEMENT_CALL(jbyteArray, Registry, getValueB)(TCN_STDARGS, jlong key,
                                                    jstring name)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    LONG rc;
    DWORD t, l;
    jbyteArray v = NULL;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    rc = RegQueryValueExW(k->key, J2W(name), NULL, &t, NULL, &l);
    if (rc != ERROR_SUCCESS) {
        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
        goto cleanup;
    }
    if (t == REG_BINARY) {
        BYTE *b = (BYTE *)malloc(l);
        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, b, &l);
        if (rc != ERROR_SUCCESS) {
            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
            free(b);
            goto cleanup;
        }
        v = tcn_new_arrayb(e, b, l);
        free(b);
    }
cleanup:
    TCN_FREE_WSTRING(name);
    return v;
}

static jsize get_multi_sz_count(LPCWSTR str)
{
    LPCWSTR p = str;
    jsize   cnt = 0;
    for ( ; p && *p; p++) {
        cnt++;
        while (*p)
            p++;
    }
    return cnt;
}

TCN_IMPLEMENT_CALL(jobjectArray, Registry, getValueA)(TCN_STDARGS, jlong key,
                                                      jstring name)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    LONG rc;
    DWORD t, l;
    jobjectArray v = NULL;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    rc = RegQueryValueExW(k->key, J2W(name), NULL, &t, NULL, &l);
    if (rc != ERROR_SUCCESS) {
        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
        goto cleanup;
    }
    if (t == REG_MULTI_SZ) {
        jsize cnt = 0;
        jchar *p;
        jchar *vw = (jchar *)malloc(l);
        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, (LPBYTE)vw, &l);
        if (rc != ERROR_SUCCESS) {
            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
            free(vw);
            goto cleanup;
        }
        cnt = get_multi_sz_count(vw);
        if (cnt) {
            jsize idx = 0;
            v = tcn_new_arrays(e, cnt);
            for (p = vw ; p && *p; p++) {
                jstring s;
                jchar *b = p;
                while (*p)
                    p++;
                s = (*e)->NewString((e), b, (jsize)(p - b));
                (*e)->SetObjectArrayElement((e), v, idx++, s);
            }
        }
        free(vw);
    }
cleanup:
    TCN_FREE_WSTRING(name);
    return v;
}

TCN_IMPLEMENT_CALL(jint, Registry, setValueI)(TCN_STDARGS, jlong key,
                                              jstring name, jint val)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    LONG rc;
    DWORD v = (DWORD)val;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    rc = RegSetValueExW(k->key, J2W(name), 0, REG_DWORD, (CONST BYTE *)&v, sizeof(DWORD));
    TCN_FREE_WSTRING(name);
    return v;
}

TCN_IMPLEMENT_CALL(jint, Registry, setValueJ)(TCN_STDARGS, jlong key,
                                              jstring name, jlong val)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    LONG rc;
    UINT64 v = (UINT64)val;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    rc = RegSetValueExW(k->key, J2W(name), 0, REG_QWORD, (CONST BYTE *)&v, sizeof(UINT64));
    TCN_FREE_WSTRING(name);
    return rc;
}

TCN_IMPLEMENT_CALL(jint, Registry, setValueS)(TCN_STDARGS, jlong key,
                                              jstring name, jstring val)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    TCN_ALLOC_WSTRING(val);
    LONG rc;
    DWORD len;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    TCN_INIT_WSTRING(val);
    len = lstrlenW(J2W(val));
    rc = RegSetValueExW(k->key, J2W(name), 0, REG_SZ,
                        (CONST BYTE *)J2W(val), (len + 1) * 2);
    TCN_FREE_WSTRING(name);
    TCN_FREE_WSTRING(val);
    return rc;
}

TCN_IMPLEMENT_CALL(jint, Registry, setValueE)(TCN_STDARGS, jlong key,
                                              jstring name, jstring val)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    TCN_ALLOC_WSTRING(val);
    LONG rc;
    DWORD len;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    TCN_INIT_WSTRING(val);
    len = lstrlenW(J2W(val));
    rc = RegSetValueExW(k->key, J2W(name), 0, REG_EXPAND_SZ,
                        (CONST BYTE *)J2W(val), (len + 1) * 2);
    TCN_FREE_WSTRING(name);
    TCN_FREE_WSTRING(val);
    return rc;
}

TCN_IMPLEMENT_CALL(jint, Registry, setValueA)(TCN_STDARGS, jlong key,
                                              jstring name,
                                              jobjectArray vals)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    LONG rc;
    jsize i, len;
    jsize sl = 0;
    jchar *msz, *p;
    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    TCN_INIT_WSTRING(name);
    len = (*e)->GetArrayLength((e), vals);
    for (i = 0; i < len; i++) {
        jstring s = (jstring)(*e)->GetObjectArrayElement((e), vals, i);
        sl += (*e)->GetStringLength((e), s) + 1;
    }
    sl = (sl + 1) * 2;
    p = msz = (jchar *)calloc(1, sl);
    for (i = 0; i < len; i++) {
        jsize   l;
        jstring s = (jstring)(*e)->GetObjectArrayElement((e), vals, i);
        l = (*e)->GetStringLength((e), s);
        wcsncpy(p, (*e)->GetStringChars(e, s, 0), l);
        p += l + 1;
    }
    rc = RegSetValueExW(k->key, J2W(name), 0, REG_MULTI_SZ,
                        (CONST BYTE *)msz, sl);
    TCN_FREE_WSTRING(name);
    free(msz);
    return rc;
}

TCN_IMPLEMENT_CALL(jint, Registry, setValueB)(TCN_STDARGS, jlong key,
                                              jstring name,
                                              jbyteArray val)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    TCN_ALLOC_WSTRING(name);
    jsize nbytes = (*e)->GetArrayLength(e, val);
    jbyte *bytes = (*e)->GetByteArrayElements(e, val, NULL);
    LONG rc;

    rc = RegSetValueExW(k->key, J2W(name), 0, REG_BINARY,
                        bytes, (DWORD)nbytes);
    (*e)->ReleaseByteArrayElements(e, val, bytes, JNI_ABORT);
    TCN_FREE_WSTRING(name);
    return rc;
}

#define MAX_VALUE_NAME 4096

TCN_IMPLEMENT_CALL(jobjectArray, Registry, enumKeys)(TCN_STDARGS, jlong key)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    LONG rc;
    jobjectArray v = NULL;
    jsize cnt = 0;

    WCHAR    achKey[MAX_PATH];
    WCHAR    achClass[MAX_PATH] = L"";
    DWORD    cchClassName = MAX_PATH;
    DWORD    cSubKeys;
    DWORD    cbMaxSubKey;
    DWORD    cchMaxClass;
    DWORD    cValues;
    DWORD    cchMaxValue;
    DWORD    cbMaxValueData;
    DWORD    cbSecurityDescriptor;
    FILETIME ftLastWriteTime;

    DWORD cchValue = MAX_VALUE_NAME;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    rc = RegQueryInfoKeyW(k->key,
                          achClass,
                          &cchClassName,
                          NULL,
                          &cSubKeys,
                          &cbMaxSubKey,
                          &cchMaxClass,
                          &cValues,
                          &cchMaxValue,
                          &cbMaxValueData,
                          &cbSecurityDescriptor,
                          &ftLastWriteTime);
    if (rc != ERROR_SUCCESS) {
        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
        goto cleanup;
    }
    cnt = cSubKeys;
    if (cnt) {
        jsize idx = 0;
        v = tcn_new_arrays(e, cnt);
        for (idx = 0; idx < cnt; idx++) {
            jstring s;
            DWORD achKeyLen = MAX_PATH;
            rc = RegEnumKeyExW(k->key,
                               idx,
                               achKey,
                               &achKeyLen,
                               NULL,
                               NULL,
                               NULL,
                               &ftLastWriteTime);
            if (rc == (DWORD)ERROR_SUCCESS) {
                s = (*e)->NewString((e), achKey, lstrlenW(achKey));
                (*e)->SetObjectArrayElement((e), v, idx, s);
            }
        }
    }
cleanup:
    return v;
}

TCN_IMPLEMENT_CALL(jobjectArray, Registry, enumValues)(TCN_STDARGS, jlong key)
{
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
    LONG rc;
    jobjectArray v = NULL;
    jsize cnt = 0;

    WCHAR    achClass[MAX_PATH] = L"";
    DWORD    cchClassName = MAX_PATH;
    DWORD    cSubKeys;
    DWORD    cbMaxSubKey;
    DWORD    cchMaxClass;
    DWORD    cValues;
    DWORD    cchMaxValue;
    DWORD    cbMaxValueData;
    DWORD    cbSecurityDescriptor;
    FILETIME ftLastWriteTime;

    WCHAR  achValue[MAX_VALUE_NAME];
    DWORD  cchValue = MAX_VALUE_NAME;

    UNREFERENCED(o);
    TCN_ASSERT(key != 0);
    /* Get the class name and the value count. */
    rc = RegQueryInfoKeyW(k->key,
                          achClass,
                          &cchClassName,
                          NULL,
                          &cSubKeys,
                          &cbMaxSubKey,
                          &cchMaxClass,
                          &cValues,
                          &cchMaxValue,
                          &cbMaxValueData,
                          &cbSecurityDescriptor,
                          &ftLastWriteTime);
    if (rc != ERROR_SUCCESS) {
        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
        goto cleanup;
    }
    cnt = cValues;
    if (cnt) {
        jsize idx = 0;
        v = tcn_new_arrays(e, cnt);
        for (idx = 0; idx < cnt; idx++) {
            jstring s;
            cchValue = MAX_VALUE_NAME;
            achValue[0] = '\0';
            rc = RegEnumValueW(k->key, idx,
                               achValue,
                               &cchValue,
                               NULL,
                               NULL,    // &dwType,
                               NULL,    // &bData,
                               NULL);   // &bcData
            if (rc == (DWORD)ERROR_SUCCESS) {
                s = (*e)->NewString((e), achValue, lstrlenW(achValue));
                (*e)->SetObjectArrayElement((e), v, idx, s);
            }
        }
    }
cleanup:
    return v;
}

TCN_IMPLEMENT_CALL(jint, Registry, deleteKey)(TCN_STDARGS, jint root, jstring name,
                                              jboolean only_if_empty)
{
    DWORD rv;
    TCN_ALLOC_WSTRING(name);

    UNREFERENCED(o);
    if (root < TCN_HKEY_CLASSES_ROOT || root > TCN_HKEY_USERS) {
        rv = EBADF;
        goto cleanup;
    }
    TCN_INIT_WSTRING(name);
    if (only_if_empty)
        rv = SHDeleteEmptyKeyW(TCN_KEYS[root].k, J2W(name));
    else
        rv = SHDeleteKeyW(TCN_KEYS[root].k, J2W(name));
cleanup:
    TCN_FREE_WSTRING(name);
    return rv;
}

TCN_IMPLEMENT_CALL(jint, Registry, deleteValue)(TCN_STDARGS, jlong key,
                                                jstring name)
{
    LONG rv;
    TCN_ALLOC_WSTRING(name);
    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);

    UNREFERENCED(o);
    TCN_INIT_WSTRING(name);
    rv = RegDeleteValueW(k->key, J2W(name));
    TCN_FREE_WSTRING(name);
    return (jint)rv;
}
