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

/* //////////////////////////////////////////////////////////////////////////////////////
 * trace
 */
#define TB_TRACE_MODULE_NAME "process.openv"
#define TB_TRACE_MODULE_DEBUG (0)

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "prefix.h"
#include "../io/prefix.h"
#if defined(TB_CONFIG_OS_MACOSX) || defined(TB_CONFIG_OS_LINUX) || defined(TB_CONFIG_OS_BSD) ||                        \
    defined(TB_CONFIG_OS_HAIKU) || defined(TB_COMPILER_IS_MINGW)
#include <signal.h>
#endif

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

/* p = process.openv(shellname, argv,
 * {outpath = "", errpath = "", outfile = "",
 *  errfile = "", outpipe = "", errpipe = "",
 *  infile = "", inpipe = "", inpipe = "",
 *  envs = {"PATH=xxx", "XXX=yyy"}})
 */
tb_int_t xm_process_openv(lua_State *lua) {
    tb_assert_and_check_return_val(lua, 0);

    // check argv
    if (!lua_istable(lua, 2)) {
        // error
        lua_pushfstring(lua, "invalid argv type(%s) for process.openv", luaL_typename(lua, 2));
        lua_error(lua);
        return 0;
    }

    // get shellname
    tb_char_t const *shellname = lua_tostring(lua, 1);
    tb_check_return_val(shellname, 0);

    // get the arguments count
    tb_long_t argn = (tb_long_t)lua_objlen(lua, 2);
    tb_check_return_val(argn >= 0, 0);

    // get arguments
    tb_size_t argi = 0;
    tb_char_t const **argv = tb_nalloc0_type(1 + argn + 1, tb_char_t const *);
    tb_check_return_val(argv, 0);

    // fill arguments
    argv[0] = shellname;
    for (argi = 0; argi < argn; argi++) {
        // get argv[i]
        lua_pushinteger(lua, argi + 1);
        lua_gettable(lua, 2);

        // is string?
        if (lua_isstring(lua, -1)) {
            // pass this argument
            argv[1 + argi] = lua_tostring(lua, -1);
        }
        // is path instance?
        else if (lua_istable(lua, -1)) {
            lua_pushstring(lua, "_STR");
            lua_gettable(lua, -2);
            argv[1 + argi] = lua_tostring(lua, -1);
            lua_pop(lua, 1);
        } else {
            // error
            lua_pushfstring(lua, "invalid argv[%d] type(%s) for process.openv", (tb_int_t)argi, luaL_typename(lua, -1));
            lua_error(lua);
        }

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

    // init attributes
    tb_process_attr_t attr = { 0 };

    // get option arguments
    tb_bool_t exclusive = tb_false;
    tb_size_t envn = 0;
    tb_char_t const *envs[1024] = { 0 };
    tb_char_t const *inpath = tb_null;
    tb_char_t const *outpath = tb_null;
    tb_char_t const *errpath = tb_null;
    xm_io_file_t *infile = tb_null;
    xm_io_file_t *outfile = tb_null;
    xm_io_file_t *errfile = tb_null;
    tb_pipe_file_ref_t inpipe = tb_null;
    tb_pipe_file_ref_t outpipe = tb_null;
    tb_pipe_file_ref_t errpipe = tb_null;
    if (lua_istable(lua, 3)) {
        // is detached?
        lua_pushstring(lua, "detach");
        lua_gettable(lua, 3);
        if (lua_toboolean(lua, -1)) {
            attr.flags |= TB_PROCESS_FLAG_DETACH;
        }
        lua_pop(lua, 1);

        // is exclusive?
        lua_pushstring(lua, "exclusive");
        lua_gettable(lua, 3);
        if (lua_toboolean(lua, -1)) {
            exclusive = tb_true;
        }
        lua_pop(lua, 1);

        // get curdir
        lua_pushstring(lua, "curdir");
        lua_gettable(lua, 3);
        attr.curdir = lua_tostring(lua, -1);
        lua_pop(lua, 1);

        // get inpath
        lua_pushstring(lua, "inpath");
        lua_gettable(lua, 3);
        inpath = lua_tostring(lua, -1);
        lua_pop(lua, 1);

        // get outpath
        lua_pushstring(lua, "outpath");
        lua_gettable(lua, 3);
        outpath = lua_tostring(lua, -1);
        lua_pop(lua, 1);

        // get errpath
        lua_pushstring(lua, "errpath");
        lua_gettable(lua, 3);
        errpath = lua_tostring(lua, -1);
        lua_pop(lua, 1);

        // get infile
        if (!inpath) {
            lua_pushstring(lua, "infile");
            lua_gettable(lua, 3);
            infile = (xm_io_file_t *)lua_touserdata(lua, -1);
            lua_pop(lua, 1);
        }

        // get outfile
        if (!outpath) {
            lua_pushstring(lua, "outfile");
            lua_gettable(lua, 3);
            outfile = (xm_io_file_t *)lua_touserdata(lua, -1);
            lua_pop(lua, 1);
        }

        // get errfile
        if (!errpath) {
            lua_pushstring(lua, "errfile");
            lua_gettable(lua, 3);
            errfile = (xm_io_file_t *)lua_touserdata(lua, -1);
            lua_pop(lua, 1);
        }

        // get inpipe
        if (!inpath && !infile) {
            lua_pushstring(lua, "inpipe");
            lua_gettable(lua, 3);
            inpipe = (tb_pipe_file_ref_t)lua_touserdata(lua, -1);
            lua_pop(lua, 1);
        }

        // get outpipe
        if (!outpath && !outfile) {
            lua_pushstring(lua, "outpipe");
            lua_gettable(lua, 3);
            outpipe = (tb_pipe_file_ref_t)lua_touserdata(lua, -1);
            lua_pop(lua, 1);
        }

        // get errpipe
        if (!errpath && !errfile) {
            lua_pushstring(lua, "errpipe");
            lua_gettable(lua, 3);
            errpipe = (tb_pipe_file_ref_t)lua_touserdata(lua, -1);
            lua_pop(lua, 1);
        }

        // get environments
        lua_pushstring(lua, "envs");
        lua_gettable(lua, 3);
        if (lua_istable(lua, -1)) {
            // get environment variables count
            tb_size_t count = (tb_size_t)lua_objlen(lua, -1);

            // get all passed environment variables
            tb_size_t i;
            for (i = 0; i < count; i++) {
                // get envs[i]
                lua_pushinteger(lua, i + 1);
                lua_gettable(lua, -2);

                // is string?
                if (lua_isstring(lua, -1)) {
                    // add this environment value
                    if (envn + 1 < tb_arrayn(envs)) {
                        envs[envn++] = lua_tostring(lua, -1);
                    } else {
                        // error
                        lua_pushfstring(lua,
                                        "envs is too large(%d > %d) for process.openv",
                                        (tb_int_t)envn,
                                        tb_arrayn(envs) - 1);
                        lua_error(lua);
                    }
                } else {
                    // error
                    lua_pushfstring(lua,
                                    "invalid envs[%d] type(%s) for process.openv",
                                    (tb_int_t)i,
                                    luaL_typename(lua, -1));
                    lua_error(lua);
                }

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

    // redirect stdin?
    if (inpath) {
        // redirect stdin to file
        attr.in.path = inpath;
        attr.inmode  = TB_FILE_MODE_RO;
        attr.intype  = TB_PROCESS_REDIRECT_TYPE_FILEPATH;
    } else if (infile && xm_io_file_is_file(infile)) {
        tb_file_ref_t rawfile = tb_null;
        if (tb_stream_ctrl(infile->stream, TB_STREAM_CTRL_FILE_GET_FILE, &rawfile) && rawfile) {
            attr.in.file = rawfile;
            attr.intype  = TB_PROCESS_REDIRECT_TYPE_FILE;
        }
    } else if (inpipe) {
        attr.in.pipe = inpipe;
        attr.intype  = TB_PROCESS_REDIRECT_TYPE_PIPE;
    }

    // redirect stdout?
    if (outpath) {
        // redirect stdout to file
        attr.out.path = outpath;
        attr.outmode  = TB_FILE_MODE_RW | TB_FILE_MODE_TRUNC | TB_FILE_MODE_CREAT;
        attr.outtype  = TB_PROCESS_REDIRECT_TYPE_FILEPATH;
    } else if (outfile && xm_io_file_is_file(outfile)) {
        tb_file_ref_t rawfile = tb_null;
        if (tb_stream_ctrl(outfile->stream, TB_STREAM_CTRL_FILE_GET_FILE, &rawfile) && rawfile) {
            attr.out.file = rawfile;
            attr.outtype  = TB_PROCESS_REDIRECT_TYPE_FILE;
        }
    } else if (outpipe) {
        attr.out.pipe = outpipe;
        attr.outtype  = TB_PROCESS_REDIRECT_TYPE_PIPE;
    }

    // redirect stderr?
    if (errpath) {
        // redirect stderr to file
        attr.err.path = errpath;
        attr.errmode  = TB_FILE_MODE_RW | TB_FILE_MODE_TRUNC | TB_FILE_MODE_CREAT;
        attr.errtype  = TB_PROCESS_REDIRECT_TYPE_FILEPATH;
    } else if (errfile && xm_io_file_is_file(errfile)) {
        tb_file_ref_t rawfile = tb_null;
        if (tb_stream_ctrl(errfile->stream, TB_STREAM_CTRL_FILE_GET_FILE, &rawfile) && rawfile) {
            attr.err.file = rawfile;
            attr.errtype  = TB_PROCESS_REDIRECT_TYPE_FILE;
        }
    } else if (errpipe) {
        attr.err.pipe = errpipe;
        attr.errtype  = TB_PROCESS_REDIRECT_TYPE_PIPE;
    }

    // set the new environments
    if (envn > 0) {
        attr.envp = envs;
    }

    /* we need to ignore SIGINT and SIGQUIT if we enter exclusive mode
     * @see https://github.com/xmake-io/xmake/discussions/2893
     */
#if defined(SIGINT)
    if (exclusive) {
        signal(SIGINT, SIG_IGN);
    }
#endif
#if defined(SIGQUIT)
    if (exclusive) {
        signal(SIGQUIT, SIG_IGN);
    }
#endif

    // init process
    tb_process_ref_t process = (tb_process_ref_t)tb_process_init(shellname, argv, &attr);
    if (process) {
        xm_lua_pushpointer(lua, (tb_pointer_t)process);
    } else {
        lua_pushnil(lua);
    }

    // exit argv
    if (argv) {
        tb_free(argv);
    }
    argv = tb_null;

    return 1;
}
