/*!A cross-platform build utility based on Lua
 *
 * Licensed 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.
 *
 * Copyright (C) 2015-present, Xmake Open Source Community.
 *
 * @author      ruki
 * @file        registry_keys.c
 *
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * trace
 */
#define TB_TRACE_MODULE_NAME "registry_keys"
#define TB_TRACE_MODULE_DEBUG (0)

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "prefix.h"

/* //////////////////////////////////////////////////////////////////////////////////////
 * types
 */

// the enum info type
typedef struct __xm_winos_registry_enum_info_t {
    lua_State *lua;
    HKEY key;
    tb_int_t ok;
    tb_char_t const *error;
    tb_int_t count;
    tb_wchar_t key_name[1024];
    tb_char_t key_path_a[TB_PATH_MAXN];
} xm_winos_registry_enum_info_t;

/* //////////////////////////////////////////////////////////////////////////////////////
 * private implementation
 */
static tb_void_t xm_winos_registry_enum_keys(xm_winos_registry_enum_info_t *info,
                                             tb_wchar_t const              *rootdir,
                                             tb_long_t                      recursion) {
    // enum keys
    HKEY keynew = tb_null;
    lua_State *lua = info->lua;
    tb_wchar_t *key_path = tb_null;
    tb_size_t key_path_maxn = TB_PATH_MAXN;
    do {
        // open registry key
        if (RegOpenKeyExW(info->key, rootdir, 0, KEY_READ, &keynew) != ERROR_SUCCESS && keynew) {
            info->ok    = -1;
            info->error = "open registry key failed";
            break;
        }

        // query keys
        DWORD key_name_num  = 0;
        DWORD key_name_maxn = 0;
        if (RegQueryInfoKeyW(keynew,
                             tb_null,
                             tb_null,
                             tb_null,
                             &key_name_num,
                             &key_name_maxn,
                             tb_null,
                             tb_null,
                             tb_null,
                             tb_null,
                             tb_null,
                             tb_null) != ERROR_SUCCESS) {
            // cannot query this key, we ignore it
            break;
        }
        key_name_maxn++; // add `\0`

        // ensure enough key path buffer
        if (key_name_maxn > tb_arrayn(info->key_name)) {
            info->ok    = -1;
            info->error = "no enough key path buffer";
            break;
        }

        // init key path
        key_path = (tb_wchar_t *)tb_malloc(key_path_maxn * sizeof(tb_wchar_t));
        if (!key_path) {
            info->ok    = -1;
            info->error = "no enough key path buffer";
            break;
        }

        // get all keys
        DWORD i = 0;
        for (i = 0; i < key_name_num && info->ok > 0; i++) {
            // get key name
            info->key_name[0] = L'\0';
            DWORD key_name_size = tb_arrayn(info->key_name);
            if (RegEnumKeyExW(keynew, i, info->key_name, &key_name_size, tb_null, tb_null, tb_null, tb_null) !=
                ERROR_SUCCESS) {
                info->ok    = -1;
                info->error = "get registry key failed";
                break;
            }

            // get key path
            tb_swprintf(key_path, key_path_maxn, L"%s\\%s", rootdir, info->key_name);

            // get key path (mbs)
            tb_size_t key_path_a_size = tb_wtoa(info->key_path_a, key_path, sizeof(info->key_path_a));
            if (key_path_a_size == -1) {
                info->ok    = -1;
                info->error = "convert registry key path failed";
                break;
            }

            // do callback(key_name)
            lua_pushvalue(lua, 4);
            lua_pushlstring(lua, info->key_path_a, key_path_a_size);
            lua_call(lua, 1, 1);
            info->count++;

            // is continue?
            tb_bool_t is_continue = lua_toboolean(lua, -1);
            lua_pop(lua, 1);
            if (!is_continue) {
                info->ok = 0;
                break;
            }

            // enum all subkeys
            if (recursion > 0 || recursion < 0) {
                xm_winos_registry_enum_keys(info, key_path, recursion > 0 ? recursion - 1 : recursion);
            }
        }

    } while (0);

    // exit registry key
    if (keynew) {
        RegCloseKey(keynew);
    }
    keynew = tb_null;

    // free key path
    if (key_path) {
        tb_free(key_path);
    }
    key_path = tb_null;
}

/* //////////////////////////////////////////////////////////////////////////////////////
 * implementation
 */

/* get registry keys
 *
 * local count, errors = winos.registry_keys("HKEY_LOCAL_MACHINE",
 *                                             "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug",
 *                                             function (key_path)
 *                                                 return true -- continue or break
 *                                             end)
 */
tb_int_t xm_winos_registry_keys(lua_State *lua) {
    tb_assert_and_check_return_val(lua, 0);

    // get the arguments
    tb_char_t const *rootkey = luaL_checkstring(lua, 1);
    tb_char_t const *rootdir = luaL_checkstring(lua, 2);
    tb_long_t recursion = (tb_long_t)lua_tointeger(lua, 3);
    tb_bool_t is_function = lua_isfunction(lua, 4);
    tb_check_return_val(rootkey && rootdir && is_function, 0);

    // enum keys
    tb_bool_t ok = tb_false;
    tb_int_t count = 0;
    HKEY key = tb_null;
    do {
        // get registry rootkey
        if (!tb_strcmp(rootkey, "HKEY_CLASSES_ROOT")) {
            key = HKEY_CLASSES_ROOT;
        } else if (!tb_strcmp(rootkey, "HKCR")) {
            key = HKEY_CLASSES_ROOT;
        } else if (!tb_strcmp(rootkey, "HKEY_CURRENT_CONFIG")) {
            key = HKEY_CURRENT_CONFIG;
        } else if (!tb_strcmp(rootkey, "HKCC")) {
            key = HKEY_CURRENT_CONFIG;
        } else if (!tb_strcmp(rootkey, "HKEY_CURRENT_USER")) {
            key = HKEY_CURRENT_USER;
        } else if (!tb_strcmp(rootkey, "HKCU")) {
            key = HKEY_CURRENT_USER;
        } else if (!tb_strcmp(rootkey, "HKEY_LOCAL_MACHINE")) {
            key = HKEY_LOCAL_MACHINE;
        } else if (!tb_strcmp(rootkey, "HKLM")) {
            key = HKEY_LOCAL_MACHINE;
        } else if (!tb_strcmp(rootkey, "HKEY_USERS")) {
            key = HKEY_USERS;
        } else {
            lua_pushnil(lua);
            lua_pushfstring(lua, "invalid registry rootkey: %s", rootkey);
            break;
        }

        // do enum
        tb_wchar_t rootdir_w[TB_PATH_MAXN];
        if (tb_atow(rootdir_w, rootdir, TB_PATH_MAXN) == -1) {
            lua_pushnil(lua);
            lua_pushfstring(lua, "rootdir is too long: %s", rootdir);
            break;
        }
        xm_winos_registry_enum_info_t info;
        info.lua   = lua;
        info.key   = key;
        info.count = 0;
        info.ok    = tb_true;
        info.error = tb_null;
        xm_winos_registry_enum_keys(&info, rootdir_w, recursion);
        count = info.count;
        ok    = info.ok >= 0;
        if (!ok) {
            lua_pushnil(lua);
            lua_pushfstring(lua, "%s: %s\\%s", info.error ? info.error : "enum registry keys failed", rootkey, rootdir);
        }

    } while (0);

    if (ok) {
        lua_pushinteger(lua, count);
        return 1;
    } else {
        return 2;
    }
}
