#include "Command.h"
#include "Prompt.h"
#include "sh.h"
#include <errno.h>
#include <luna/String.h>
#include <luna/Vector.h>
#include <os/ArgumentParser.h>
#include <os/Directory.h>
#include <os/File.h>
#include <os/FileSystem.h>
#include <os/Process.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include <luna/HashMap.h>

using os::File;

// Do nothing, but we need a handler so read() returns EINTR.
static void sigint_handler(int)
{
}

static Result<int> execute_loop(bool interactive, SharedPtr<File> input_file)
{
    while (1)
    {
        if (interactive) display_prompt();

        auto maybe_cmd = TRY(read_command(input_file));
        if (!maybe_cmd.has_value())
        {
            if (interactive) puts("exit");
            break;
        }

        auto cmd = maybe_cmd.release_value();
        if (cmd.is_empty()) continue;

        auto command = TRY(parse_command(cmd.view()));
        if (command->args.size() < 1) continue;

        TRY(execute_command_in_subprocess(move(command), input_file, interactive));
    }

    return 0;
}

Result<void> execute_rc_file()
{
    static const StringView rc_file_name = ".shellrc"_sv;

    auto home_path = TRY(os::FileSystem::home_directory());
    auto home_dir = TRY(os::Directory::open(home_path.view()));

    auto rc_file_path = os::Path { home_dir->fd(), rc_file_name };
    if (os::FileSystem::exists(rc_file_path))
    {
        auto rc_file = TRY(os::File::open(rc_file_path, os::File::ReadOnly));

        TRY(execute_loop(false, rc_file));
    }

    return {};
}

Result<int> luna_main(int argc, char** argv)
{
    StringView path;
    StringView command;
    bool interactive { false };

    SharedPtr<File> input_file;

    os::ArgumentParser parser;
    parser.add_description("The Luna system's command shell."_sv);
    parser.add_system_program_info("sh"_sv);
    parser.add_positional_argument(path, "path"_sv, "-"_sv);
    parser.add_value_argument(command, 'c', "command"_sv, "execute a single command and then exit"_sv);
    parser.add_switch_argument(interactive, 'i', "interactive"_sv, "run an interactive shell"_sv);
    parser.parse(argc, argv);

    TRY(init_builtins());

    if (!command.is_empty())
    {
        auto cmd_obj = TRY(parse_command(command));
        execute_command_or_builtin(move(cmd_obj));
    }

    if (path == "-") { input_file = File::standard_input(); }
    else
    {
        input_file = TRY(File::open(path, File::ReadOnly));
        input_file->set_close_on_exec();
    }

    if (isatty(input_file->fd())) interactive = true;

    if (interactive)
    {
        setup_prompt();

        signal(SIGTTOU, SIG_IGN);
        signal(SIGINT, sigint_handler);

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

        TRY(execute_rc_file());
    }

    return execute_loop(interactive, input_file);
}