#include "TerminalWrapper.h"
#include "ProcessStateWidget.h"
#include <AK/String.h>
#include <LibCore/CConfigFile.h>
#include <LibGUI/GBoxLayout.h>
#include <LibGUI/GMessageBox.h>
#include <LibVT/TerminalWidget.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void TerminalWrapper::run_command(const String& command)
{
    if (m_pid != -1) {
        GMessageBox::show(
            "A command is already running in this TerminalWrapper",
            "Can't run command",
            GMessageBox::Type::Error,
            GMessageBox::InputType::OK,
            window());
        return;
    }

    int ptm_fd = open("/dev/ptmx", O_RDWR | O_CLOEXEC);
    if (ptm_fd < 0) {
        perror("open(ptmx)");
        ASSERT_NOT_REACHED();
    }

    m_terminal_widget->set_pty_master_fd(ptm_fd);
    m_terminal_widget->on_command_exit = [this] {
        int wstatus;
        int rc = waitpid(m_pid, &wstatus, 0);
        if (rc < 0) {
            perror("waitpid");
            ASSERT_NOT_REACHED();
        }
        if (WIFEXITED(wstatus)) {
            m_terminal_widget->inject_string(String::format("\033[%d;1m(Command exited with code %d)\033[0m\n", wstatus == 0 ? 32 : 31, WEXITSTATUS(wstatus)));
        } else if (WIFSTOPPED(wstatus)) {
            m_terminal_widget->inject_string(String::format("\033[34;1m(Command stopped!)\033[0m\n"));
        } else if (WIFSIGNALED(wstatus)) {
            m_terminal_widget->inject_string(String::format("\033[34;1m(Command signaled with %s!)\033[0m\n", strsignal(WTERMSIG(wstatus))));
        }
        m_process_state_widget->set_tty_fd(-1);
        m_pid = -1;

        if (on_command_exit)
            on_command_exit();
    };

    m_pid = fork();
    if (m_pid == 0) {
        // Create a new process group.
        setsid();

        const char* tty_name = ptsname(ptm_fd);
        if (!tty_name) {
            perror("ptsname");
            exit(1);
        }
        close(ptm_fd);
        int pts_fd = open(tty_name, O_RDWR);
        if (pts_fd < 0) {
            perror("open");
            exit(1);
        }

        // NOTE: It's okay if this fails.
        (void)ioctl(0, TIOCNOTTY);

        close(0);
        close(1);
        close(2);

        int rc = dup2(pts_fd, 0);
        if (rc < 0) {
            perror("dup2");
            exit(1);
        }
        rc = dup2(pts_fd, 1);
        if (rc < 0) {
            perror("dup2");
            exit(1);
        }
        rc = dup2(pts_fd, 2);
        if (rc < 0) {
            perror("dup2");
            exit(1);
        }
        rc = close(pts_fd);
        if (rc < 0) {
            perror("close");
            exit(1);
        }
        rc = ioctl(0, TIOCSCTTY);
        if (rc < 0) {
            perror("ioctl(TIOCSCTTY)");
            exit(1);
        }

        setenv("TERM", "xterm", true);
        setenv("PATH", "/bin:/usr/bin:/usr/local/bin", true);

        auto parts = command.split(' ');
        ASSERT(!parts.is_empty());
        const char** args = (const char**) calloc(parts.size() + 1, sizeof(const char*));
        for (int i = 0; i < parts.size(); i++) {
            args[i] = parts[i].characters();
        }
        rc = execvp(args[0], const_cast<char**>(args));
        if (rc < 0) {
            perror("execve");
            exit(1);
        }
        ASSERT_NOT_REACHED();
    }

    // Parent process, cont'd.
    m_process_state_widget->set_tty_fd(ptm_fd);
}

void TerminalWrapper::kill_running_command()
{
    ASSERT(m_pid != -1);

    // Kill our child process and its whole process group.
    (void)killpg(m_pid, SIGTERM);
}

TerminalWrapper::TerminalWrapper(GWidget* parent)
    : GWidget(parent)
{
    set_layout(make<GBoxLayout>(Orientation::Vertical));

    RefPtr<CConfigFile> config = CConfigFile::get_for_app("Terminal");
    m_terminal_widget = TerminalWidget::construct(-1, false, config);
    add_child(*m_terminal_widget);

    m_process_state_widget = ProcessStateWidget::construct(this);
}

TerminalWrapper::~TerminalWrapper()
{
}
