/*
 * Implement convenience wrappers on the awkward low-level functions
 * for accessing the Windows registry.
 */

#include "putty.h"

HKEY open_regkey_fn(bool create, bool write, HKEY hk, const char *path, ...)
{
    HKEY toret = NULL;
    bool hk_needs_close = false;
    va_list ap;
    va_start(ap, path);

    for (; path; path = va_arg(ap, const char *)) {
        HKEY hk_sub = NULL;

        DWORD access = KEY_READ | (write ? KEY_WRITE : 0);
        LONG status;
        if (create)
            status = RegCreateKeyEx(
                hk, path, 0, NULL, REG_OPTION_NON_VOLATILE,
                access, NULL, &hk_sub, NULL);
        else
            status = RegOpenKeyEx(hk, path, 0, access, &hk_sub);

        if (status != ERROR_SUCCESS)
            goto out;

        if (hk_needs_close)
            RegCloseKey(hk);
        hk = hk_sub;
        hk_needs_close = true;
    }

    toret = hk;
    hk = NULL;
    hk_needs_close = false;

  out:
    va_end(ap);
    if (hk_needs_close)
        RegCloseKey(hk);
    return toret;
}

void close_regkey(HKEY key)
{
    RegCloseKey(key);
}

void del_regkey(HKEY key, const char *name)
{
    RegDeleteKey(key, name);
}

char *enum_regkey(HKEY key, int index)
{
    size_t regbuf_size = MAX_PATH + 1;
    char *regbuf = snewn(regbuf_size, char);

    while (1) {
        LONG status = RegEnumKey(key, index, regbuf, regbuf_size);
        if (status == ERROR_SUCCESS)
            return regbuf;
        if (status != ERROR_MORE_DATA) {
            sfree(regbuf);
            return NULL;
        }
        sgrowarray(regbuf, regbuf_size, regbuf_size);
    }
}

bool get_reg_dword(HKEY key, const char *name, DWORD *out)
{
    DWORD type, size;
    size = sizeof(*out);

    if (RegQueryValueEx(key, name, 0, &type,
                        (BYTE *)out, &size) != ERROR_SUCCESS ||
        size != sizeof(*out) || type != REG_DWORD)
        return false;
    else
        return true;
}

bool put_reg_dword(HKEY key, const char *name, DWORD value)
{
    return RegSetValueEx(key, name, 0, REG_DWORD, (CONST BYTE *) &value,
                         sizeof(value)) == ERROR_SUCCESS;
}

char *get_reg_sz(HKEY key, const char *name)
{
    DWORD type, size;

    if (RegQueryValueEx(key, name, 0, &type, NULL,
                        &size) != ERROR_SUCCESS || type != REG_SZ)
        return NULL;                   /* not a string */

    size_t allocsize = size+1;         /* allow for an extra NUL if needed */
    char *toret = snewn(allocsize, char);
    if (RegQueryValueEx(key, name, 0, &type, (BYTE *)toret,
                        &size) != ERROR_SUCCESS || type != REG_SZ) {
        sfree(toret);
        return NULL;
    }
    assert(size < allocsize);
    toret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx
                         * didn't supply one */

    return toret;
}

bool put_reg_sz(HKEY key, const char *name, const char *str)
{
    /* You have to store the trailing NUL as well */
    return RegSetValueEx(key, name, 0, REG_SZ, (CONST BYTE *)str,
                         1 + strlen(str)) == ERROR_SUCCESS;
}

/*
 * REG_MULTI_SZ items are stored as a concatenation of NUL-terminated
 * strings, terminated in turn with an empty string, i.e. a second
 * consecutive NUL.
 *
 * We represent these in their storage format, as a strbuf - but
 * *without* the second consecutive NUL.
 *
 * So you can build up a new MULTI_SZ value in a strbuf by calling
 * put_asciz once per output string and then put_reg_multi_sz; and you
 * can consume one by initialising a BinarySource to the result of
 * get_reg_multi_sz, and then calling get_asciz on it and assuming
 * that !get_err(src) means you have a real output string.
 *
 * Also, calling strbuf_to_str on one of these will give you back a
 * bare 'char *' with the same double-NUL termination, to pass back to
 * a caller.
 */
strbuf *get_reg_multi_sz(HKEY key, const char *name)
{
    DWORD type, size;

    if (RegQueryValueEx(key, name, 0, &type, NULL,
                        &size) != ERROR_SUCCESS || type != REG_MULTI_SZ)
        return NULL;                   /* not a string */

    strbuf *toret = strbuf_new();
    void *ptr = strbuf_append(toret, (size_t)size + 2);
    if (RegQueryValueEx(key, name, 0, &type, (BYTE *)ptr,
                        &size) != ERROR_SUCCESS || type != REG_MULTI_SZ) {
        strbuf_free(toret);
        return NULL;
    }
    strbuf_shrink_to(toret, size);
    /* Ensure we end with exactly one \0 */
    while (strbuf_chomp(toret, '\0'));
    put_byte(toret, '\0');
    return toret;
}

bool put_reg_multi_sz(HKEY key, const char *name, strbuf *str)
{
    /*
     * Of course, to write our string list into the registry, we _do_
     * have to include both trailing NULs. But this is easy, because a
     * strbuf is also designed to hold a single string and make it
     * conveniently accessible in NUL-terminated form, so it stores a
     * NUL in its buffer just beyond its formal length. So we just
     * include that extra byte in the data we write.
     */
    return RegSetValueEx(key, name, 0, REG_MULTI_SZ, (CONST BYTE *)str->s,
                         str->len + 1) == ERROR_SUCCESS;
}

char *get_reg_sz_simple(HKEY key, const char *name, const char *leaf)
{
    HKEY subkey = open_regkey_ro(key, name);
    if (!subkey)
        return NULL;
    char *toret = get_reg_sz(subkey, leaf);
    RegCloseKey(subkey);
    return toret;
}