#include "TerminalWidget.h"
#include <ctype.h>
#include <errno.h>
#include <luna/CType.h>
#include <os/File.h>
#include <os/Process.h>
#include <pty.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <time.h>
#include <ui/App.h>
#include <unistd.h>

static constexpr auto RED = ui::Color::from_u32(0xffcd0000);
static constexpr auto GREEN = ui::Color::from_u32(0xff00cd00);
static constexpr auto YELLOW = ui::Color::from_u32(0xffcdcd00);
static constexpr auto BLUE = ui::Color::from_u32(0xff0000ee);
static constexpr auto MAGENTA = ui::Color::from_u32(0xffcd00cd);
static constexpr auto CYAN = ui::Color::from_u32(0xff00cdcd);
static constexpr auto GRAY = ui::Color::from_u32(0xffe5e5e5);

static constexpr auto BRIGHT_BLACK = ui::Color::from_u32(0xff7f7f7f);
static constexpr auto BRIGHT_RED = ui::Color::from_u32(0xffff0000);
static constexpr auto BRIGHT_GREEN = ui::Color::from_u32(0xff00ff00);
static constexpr auto BRIGHT_YELLOW = ui::Color::from_u32(0xffffff00);
static constexpr auto BRIGHT_BLUE = ui::Color::from_u32(0xff5c5cff);
static constexpr auto BRIGHT_MAGENTA = ui::Color::from_u32(0xffff00ff);
static constexpr auto BRIGHT_CYAN = ui::Color::from_u32(0xff00ffff);
static constexpr auto BRIGHT_GRAY = ui::Color::from_u32(0xffffffff);

static void sigchld_handler(int)
{
    wait(NULL);
    ui::App::the().set_should_close(true);
}

Result<void> TerminalWidget::init(char* const* args)
{
    m_font = ui::Font::default_font();
    m_bold_font = ui::Font::default_bold_font();

    m_terminal_canvas = ui::App::the().main_window()->canvas();
    m_terminal_canvas.fill(ui::BLACK);

    m_cursor_timer = TRY(os::Timer::create_repeating(500, [this]() { this->tick_cursor(); }));

    signal(SIGCHLD, sigchld_handler);

    int master;
    pid_t child = forkpty(&master, nullptr, nullptr, nullptr);
    if (child < 0) return err(errno);
    if (child == 0)
    {
        execv(args[0], args);
        _exit(127);
    }

    m_pty = master;

    os::EventLoop::the().register_fd_listener(m_pty, [this](int, int) { this->process(); });

    m_child_pid = child;

    return {};
}

Result<ui::EventResult> TerminalWidget::handle_key_event(const ui::KeyEventRequest& request)
{
    // Avoid handling "key released" events
    if (!request.pressed) return ui::EventResult::DidNotHandle;

    // Non-printable key or key that has no special character (unlike Tab or Enter). We exit early to avoid inserting an
    // invalid zero byte into the terminal input (this would also happen on Shift or Ctrl keypresses).
    if (request.letter == '\0') return ui::EventResult::DidNotHandle;

    write(m_pty, &request.letter, 1);
    return ui::EventResult::DidHandle;
}

Result<void> TerminalWidget::draw(ui::Canvas&)
{
    return {};
}

Result<void> TerminalWidget::process()
{
    char buffer[BUFSIZ];
    ssize_t nread = read(m_pty, buffer, BUFSIZ);
    if (nread < 0)
    {
        if (errno == EAGAIN) nread = 0;
        else
            return err(errno);
    }

    ssize_t drawn = 0;

    for (ssize_t i = 0; i < nread; i++)
    {
        bool did_draw = TRY(putchar(buffer[i]));
        if (did_draw) drawn++;
    }

    if (drawn > 0) ui::App::the().main_window()->draw();

    return {};
}

void TerminalWidget::tick_cursor()
{
    if (!m_cursor_enabled) return;

    m_cursor_activated = !m_cursor_activated;

    if (m_cursor_activated) draw_cursor();
    else
        erase_current_char();

    ui::App::the().main_window()->draw();
}

void TerminalWidget::draw_glyph(wchar_t c, int x, int y)
{
    auto subcanvas = m_terminal_canvas.subcanvas({ x, y, m_font->width(), m_font->height() });
    subcanvas.fill(m_background_color);
    (m_bold ? m_bold_font : m_font)->render(c, m_foreground_color, subcanvas);
}

void TerminalWidget::erase_current_line()
{
    m_terminal_canvas.subcanvas({ 0, m_y_position, m_rect.width, m_font->height() }).fill(ui::BLACK);
}

void TerminalWidget::scroll()
{
    memcpy(m_terminal_canvas.ptr, m_terminal_canvas.ptr + (m_rect.width * sizeof(u32) * m_font->height()),
           (m_rect.width * m_rect.height * sizeof(u32)) - (m_rect.width * sizeof(u32) * m_font->height()));
    m_y_position -= m_font->height();
    erase_current_line();
}

bool TerminalWidget::should_scroll()
{
    return m_y_position >= m_rect.height;
}

void TerminalWidget::next_line()
{
    m_x_position = 0;
    m_y_position += m_font->height();
}

void TerminalWidget::next_char()
{
    m_x_position += m_font->width();
}

void TerminalWidget::prev_char()
{
    m_x_position -= m_font->width();
}

void TerminalWidget::erase_current_char()
{
    m_terminal_canvas.subcanvas({ m_x_position, m_y_position, m_font->width(), m_font->height() }).fill(ui::BLACK);
}

void TerminalWidget::draw_cursor()
{
    m_terminal_canvas.subcanvas({ m_x_position, m_y_position, m_font->width(), m_font->height() }).fill(ui::WHITE);
}

bool TerminalWidget::at_end_of_screen()
{
    return (m_x_position + m_font->width()) > m_rect.width;
}

bool TerminalWidget::handle_escape_sequence(wchar_t c)
{
    auto rc = m_escape_parser->advance(static_cast<u8>(c));
    if (rc.has_error())
    {
        m_escape_parser = Option<EscapeSequenceParser> {};
        return false;
    }
    if (!rc.value()) return true;
    if (!m_escape_parser->valid())
    {
        m_escape_parser = Option<EscapeSequenceParser> {};
        return false;
    }

    const auto& params = m_escape_parser->parameters();
    switch (m_escape_parser->code())
    {
    case EscapeCode::CursorUp: {
        int lines = params.size() ? params[0] : 1;
        int pixels = lines * m_font->height();
        if (pixels > m_y_position) m_y_position = 0;
        else
            m_y_position -= pixels;
    };
    break;
    case EscapeCode::CursorDown: {
        int lines = params.size() ? params[0] : 1;
        int pixels = lines * m_font->height();
        if (pixels + m_y_position >= m_rect.height) m_y_position = m_rect.height - m_font->height();
        else
            m_y_position += pixels;
    };
    break;
    case EscapeCode::CursorBack: {
        int chars = params.size() ? params[0] : 1;
        int pixels = chars * m_font->width();
        if (pixels > m_x_position) m_x_position = 0;
        else
            m_x_position -= pixels;
    };
    break;
    case EscapeCode::CursorForward: {
        int chars = params.size() ? params[0] : 1;
        int pixels = chars * m_font->width();
        if (pixels + m_x_position >= m_rect.width) m_x_position = m_rect.width - m_font->width();
        else
            m_x_position += pixels;
    };
    break;
    case EscapeCode::CursorNextLine: {
        int lines = params.size() ? params[0] : 1;
        int pixels = lines * m_font->height();
        if (pixels > m_y_position) m_y_position = 0;
        else
            m_y_position -= pixels;
        m_x_position = 0;
    };
    break;
    case EscapeCode::CursorPreviousLine: {
        int lines = params.size() ? params[0] : 1;
        int pixels = lines * m_font->height();
        if (pixels + m_y_position >= m_rect.height) m_y_position = m_rect.height - m_font->height();
        else
            m_y_position += pixels;
        m_x_position = 0;
    };
    break;
    case EscapeCode::CursorHorizontalAbsolute: {
        int line = (params.size() ? params[0] : 1) - 1;
        if (line < 0) break;
        int position = line * m_font->height();
        if (position >= m_rect.height) position = m_rect.height - m_font->height();
        m_y_position = position;
    };
    break;
    case EscapeCode::SetCursorPosition: {
        int x = (params.size() ? params[0] : 1) - 1;
        int y = (params.size() > 1 ? params[1] : 1) - 1;
        if (x < 0 || y < 0) break;
        int x_position = x * m_font->width();
        if (x_position >= m_rect.width) x_position = m_rect.width - m_font->height();
        m_x_position = x_position;
        int y_position = y * m_font->height();
        if (y_position >= m_rect.height) y_position = m_rect.height - m_font->height();
        m_y_position = y_position;
    };
    break;
    case EscapeCode::SelectGraphicRendition: {
        if (!params.size())
        {
            m_foreground_color = ui::WHITE;
            m_background_color = ui::BLACK;
            m_bold = false;
            break;
        }

        for (usize i = 0; i < params.size(); i++)
        {
            int arg = params[i];
            switch (arg)
            {
            case 0: {
                m_foreground_color = ui::BLACK;
                m_background_color = ui::WHITE;
                m_bold = false;
                break;
            }
            case 1: {
                m_bold = true;
                break;
            }
            case 22: {
                m_bold = false;
                break;
            }
            case 30: {
                m_foreground_color = m_bold ? BRIGHT_BLACK : ui::BLACK;
                break;
            }
            case 31: {
                m_foreground_color = m_bold ? BRIGHT_RED : RED;
                break;
            }
            case 32: {
                m_foreground_color = m_bold ? BRIGHT_GREEN : GREEN;
                break;
            }
            case 33: {
                m_foreground_color = m_bold ? BRIGHT_YELLOW : YELLOW;
                break;
            }
            case 34: {
                m_foreground_color = m_bold ? BRIGHT_BLUE : BLUE;
                break;
            }
            case 35: {
                m_foreground_color = m_bold ? BRIGHT_MAGENTA : MAGENTA;
                break;
            }
            case 36: {
                m_foreground_color = m_bold ? BRIGHT_CYAN : CYAN;
                break;
            }
            case 37: {
                m_foreground_color = m_bold ? BRIGHT_GRAY : GRAY;
                break;
            }
            case 39: {
                m_foreground_color = ui::WHITE;
                break;
            }
            case 40: {
                m_background_color = m_bold ? BRIGHT_BLACK : ui::BLACK;
                break;
            }
            case 41: {
                m_background_color = m_bold ? BRIGHT_RED : RED;
                break;
            }
            case 42: {
                m_background_color = m_bold ? BRIGHT_GREEN : GREEN;
                break;
            }
            case 43: {
                m_background_color = m_bold ? BRIGHT_YELLOW : YELLOW;
                break;
            }
            case 44: {
                m_background_color = m_bold ? BRIGHT_BLUE : BLUE;
                break;
            }
            case 45: {
                m_background_color = m_bold ? BRIGHT_MAGENTA : MAGENTA;
                break;
            }
            case 46: {
                m_background_color = m_bold ? BRIGHT_CYAN : CYAN;
                break;
            }
            case 47: {
                m_background_color = m_bold ? BRIGHT_GRAY : GRAY;
                break;
            }
            case 49: {
                m_background_color = ui::BLACK;
                break;
            }
            default: break;
            }
        }
    }
    break;
    default: break;
    }

    m_escape_parser = Option<EscapeSequenceParser> {};
    return true;
}

Result<bool> TerminalWidget::putchar(char c)
{
    auto guard = make_scope_guard([this] { m_decoder.reset(); });

    bool is_ready = TRY(m_decoder.feed(c));

    bool result = false;

    if (is_ready) result = put_code_point(TRY(m_decoder.extract()));

    guard.deactivate();

    return result;
}

bool TerminalWidget::put_code_point(wchar_t c)
{
    if (c > (wchar_t)255) c = (wchar_t)256;

    if (m_escape_parser.has_value())
    {
        if (handle_escape_sequence(c)) return false;
    }

    // Erase the current cursor.
    if (m_cursor_activated) erase_current_char();

    bool should_draw_cursor = m_cursor_enabled;

    bool did_draw = false;

    switch (c)
    {
    case L'\n': {
        next_line();
        if (should_scroll()) scroll();
        break;
    }
    case L'\t': {
        for (int i = 0; i < 4; i++) { put_code_point(L' '); }
        did_draw = true;
        break;
    }
    case L'\r': m_x_position = 0; break;
    case L'\b':
        if (m_x_position != 0)
        {
            prev_char();
            erase_current_char();
            did_draw = true;
        }
        break;
    case L'\x1b':
    case L'\x9b':
    case L'\x90':
    case L'\x9d':
        m_escape_parser = EscapeSequenceParser { (u8)c };
        should_draw_cursor = false;
        did_draw = true;
        break;
    default: {
        if (iscntrl(c)) return false;
        draw_glyph(c, m_x_position, m_y_position);
        next_char();
        if (at_end_of_screen())
        {
            next_line();
            if (should_scroll()) scroll();
        }
        break;
    }
    }

    if (should_draw_cursor)
    {
        m_cursor_timer->restart();
        m_cursor_activated = true;
        draw_cursor();
    }

    return did_draw;
}