/*!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        find.c
 *
 */

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

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

/* //////////////////////////////////////////////////////////////////////////////////////
 * private implementation
 */
static tb_long_t xm_os_find_walk(tb_char_t const *path, tb_file_info_t const *info, tb_cpointer_t priv) {
    tb_value_ref_t tuple = (tb_value_ref_t)priv;
    tb_assert_and_check_return_val(path && info && tuple, TB_DIRECTORY_WALK_CODE_END);

    // the lua
    lua_State *lua = (lua_State *)tuple[0].ptr;
    tb_assert_and_check_return_val(lua, TB_DIRECTORY_WALK_CODE_END);

    // the pattern
    tb_char_t const *pattern = (tb_char_t const *)tuple[1].cstr;
    tb_assert_and_check_return_val(pattern, TB_DIRECTORY_WALK_CODE_END);

    // remove ./ for path
    if (path[0] == '.' && (path[1] == '/' || path[1] == '\\')) {
        path = path + 2;
    }

    // the match mode
    tb_long_t mode = tuple[2].l;

    // the count
    tb_size_t *pcount = &(tuple[3].ul);

    tb_trace_d("path[%c]: %s", info->type == TB_FILE_TYPE_DIRECTORY ? 'd' : 'f', path);

    // we can ignore it directly if this path is file, but we need directory
    tb_size_t needtype = (mode == 1) ? TB_FILE_TYPE_DIRECTORY
                                     : ((mode == 0) ? TB_FILE_TYPE_FILE : (TB_FILE_TYPE_FILE | TB_FILE_TYPE_DIRECTORY));
    if (info->type == TB_FILE_TYPE_FILE && needtype == TB_FILE_TYPE_DIRECTORY) {
        return TB_DIRECTORY_WALK_CODE_CONTINUE;
    }

    // do path:match(pattern)
    lua_getfield(lua, -1, "match");
    lua_pushstring(lua, path);
    lua_pushstring(lua, pattern);
    if (lua_pcall(lua, 2, 1, 0)) {
        tb_printf("error: call string.match(%s, %s) failed: %s!\n", path, pattern, lua_tostring(lua, -1));
        return TB_DIRECTORY_WALK_CODE_END;
    }

    // match ok?
    tb_bool_t matched        = tb_false;
    tb_bool_t skip_recursion = tb_false;
    if (lua_isstring(lua, -1) && !tb_strcmp(path, lua_tostring(lua, -1))) {
        // exists excludes?
        tb_bool_t excluded = tb_false;
        if (lua_istable(lua, 5)) {
            // the root directory
            size_t rootlen = 0;
            tb_char_t const *rootdir = luaL_checklstring(lua, 1, &rootlen);
            tb_assert_and_check_return_val(rootdir && rootlen, TB_DIRECTORY_WALK_CODE_END);

            tb_assert(!tb_strncmp(path, rootdir, rootlen));
            tb_assert(rootlen + 1 <= tb_strlen(path));

            // skip the rootdir if not "."
            if (tb_strcmp(rootdir, ".")) {
                path += rootlen + 1;
            }

            // exclude paths
            tb_int_t i = 0;
            tb_int_t count = (tb_int_t)lua_objlen(lua, 5);
            for (i = 0; i < count && !excluded; i++) {
                // get exclude
                lua_rawgeti(lua, 5, i + 1);
                tb_char_t const *exclude = lua_tostring(lua, -1);
                if (exclude) {
                    // do path:match(exclude)
                    lua_getfield(lua, -3, "match");
                    lua_pushstring(lua, path);
                    lua_pushstring(lua, exclude);
                    if (lua_pcall(lua, 2, 1, 0)) {
                        tb_printf("error: call string.match(%s, %s) failed: %s!\n",
                                  path,
                                  exclude,
                                  lua_tostring(lua, -1));
                    }

                    // matched?
                    excluded = lua_isstring(lua, -1) && !tb_strcmp(path, lua_tostring(lua, -1));

                    // pop the match result
                    lua_pop(lua, 1);
                }

                // pop exclude
                lua_pop(lua, 1);
            }
        }

        // does not exclude this path?
        if (!excluded) {
            // match file or directory?
            if (info->type & needtype) {
                // save it
                lua_rawseti(lua, -3, (tb_int_t)(++*pcount));

                // do callback function
                if (lua_isfunction(lua, 6)) {
                    // do callback(path, isdir)
                    lua_pushvalue(lua, 6);
                    lua_pushstring(lua, path);
                    lua_pushboolean(lua, info->type == TB_FILE_TYPE_DIRECTORY);
                    lua_call(lua, 2, 1);

                    // is continue?
                    tb_bool_t is_continue = lua_toboolean(lua, -1);
                    lua_pop(lua, 1);
                    if (!is_continue) {
                        return TB_DIRECTORY_WALK_CODE_END;
                    }
                }
                matched = tb_true;
            }
        }
        // we do not recurse sub-directories if this path has been excluded and it's directory
        else {
            skip_recursion = tb_true;
        }
    }
    if (!matched) {
        lua_pop(lua, 1);
    }
    return skip_recursion ? TB_DIRECTORY_WALK_CODE_SKIP_RECURSION : TB_DIRECTORY_WALK_CODE_CONTINUE;
}
/* //////////////////////////////////////////////////////////////////////////////////////
 * implementation
 */
tb_int_t xm_os_find(lua_State *lua) {
    tb_assert_and_check_return_val(lua, 0);

    // get the root directory
    tb_char_t const *rootdir = luaL_checkstring(lua, 1);
    tb_check_return_val(rootdir, 0);

    // get the pattern
    tb_char_t const *pattern = luaL_checkstring(lua, 2);
    tb_check_return_val(pattern, 0);

    // the recursion level
    tb_long_t recursion = (tb_long_t)lua_tointeger(lua, 3);

    // the match mode
    tb_long_t mode = (tb_long_t)lua_tointeger(lua, 4);

    // init table
    lua_newtable(lua);

    // get string package
    lua_getglobal(lua, "string");

    // do os.find(root, name)
    tb_value_t tuple[4];
    tuple[0].ptr  = lua;
    tuple[1].cstr = pattern;
    tuple[2].l    = mode;
    tuple[3].ul   = 0;
    tb_directory_walk(rootdir, recursion, tb_true, xm_os_find_walk, tuple);

    // pop string package
    lua_pop(lua, 1);

    // return count
    lua_pushinteger(lua, tuple[3].ul);
    return 2;
}
