/**
 * @file Command.cpp
 * @author apio (cloudapio.eu)
 * @brief Command parsing and execution.
 *
 * @copyright Copyright (c) 2024, the Luna authors.
 *
 */

#include "Command.h"
#include <luna/HashMap.h>
#include <os/Process.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

extern shell_builtin_t builtin_cd;
extern shell_builtin_t builtin_exit;
extern shell_builtin_t builtin_set;
extern shell_builtin_t builtin_unset;
extern shell_builtin_t builtin_echo;

static HashMap<StringView, shell_builtin_t> s_builtins;

Result<void> init_builtins()
{
    s_builtins = {};

    TRY(s_builtins.try_set("cd"_sv, builtin_cd));
    TRY(s_builtins.try_set("exit"_sv, builtin_exit));
    TRY(s_builtins.try_set("set"_sv, builtin_set));
    TRY(s_builtins.try_set("unset"_sv, builtin_unset));
    TRY(s_builtins.try_set("echo"_sv, builtin_echo));

    return {};
}

Result<Option<String>> read_command(SharedPtr<os::File> file)
{
    auto maybe_cmd = file->read_line();
    if (maybe_cmd.has_error())
    {
        if (maybe_cmd.error() == EINTR)
        {
            os::print("\n");
            return Option<String> { String {} };
        }
        return maybe_cmd.release_error();
    }

    auto cmd = maybe_cmd.release_value();
    if (cmd.is_empty()) return Option<String> {};

    if (strspn(cmd.chars(), " \n") == cmd.length()) return Option<String> { String {} };

    if (strcspn(cmd.chars(), " #") == 0) return Option<String> { String {} };

    return Option<String> { move(cmd) };
}

Result<OwnedPtr<Command>> parse_command(StringView command)
{
    auto vec = TRY(command.split(" \n"_sv));

    auto cmd = TRY(make_owned<Command>());
    cmd->args = move(vec);

    return cmd;
}

shell_builtin_t* check_builtin(Command& command)
{
    if (!strchr(command.args[0].chars(), '/')) { return s_builtins.try_get_ref(command.args[0].view()); }

    return nullptr;
}

[[noreturn]] void execute_command(OwnedPtr<Command> command)
{
    auto err = os::Process::exec(command->args[0].view(), command->args.slice());

    os::eprintln("%s: %s", command->args[0].chars(), err.error_string());

    os::Process::exit(127);
}

[[noreturn]] void execute_command_or_builtin(OwnedPtr<Command> command)
{
    shell_builtin_t* builtin;
    if ((builtin = check_builtin(*command)))
    {
        auto rc = run_builtin(command->args, *builtin);
        if (rc.has_error()) os::Process::exit(rc.error());
        os::Process::exit(0);
    }

    execute_command(move(command));
}

Result<void> run_builtin(const Vector<String>& args, shell_builtin_t builtin)
{
    Vector<const char*> argv;
    for (const auto& arg : args) { TRY(argv.try_append(arg.chars())); }
    TRY(argv.try_append(nullptr));

    auto rc = builtin((int)args.size(), const_cast<char**>(argv.data()));
    if (rc.has_error())
    {
        os::eprintln("%s: %s", args[0].chars(), rc.error_string());
        return rc.release_error();
    }

    return {};
}

Result<int> execute_command_in_subprocess(OwnedPtr<Command> command, SharedPtr<os::File> input_file, bool interactive)
{
    shell_builtin_t* builtin;
    if ((builtin = check_builtin(*command)))
    {
        auto rc = run_builtin(command->args, *builtin);
        if (rc.has_error()) return rc.error();
        return 0;
    }

    pid_t child = TRY(os::Process::fork());

    if (child == 0)
    {
        if (interactive)
        {
            setpgid(0, 0);
            tcsetpgrp(input_file->fd(), getpid());
        }

        execute_command(move(command));
    }

    int status;
    TRY(os::Process::wait(child, &status));

    if (interactive) tcsetpgrp(input_file->fd(), getpgid(0));

    if (WIFSIGNALED(status))
    {
        int sig = WTERMSIG(status);
        if (sig != SIGINT && sig != SIGQUIT) os::println("[sh] Process %d exited: %s", child, strsignal(sig));
        else
            os::print("\n");
    }

    return status;
}