/**
 * @file EditorWidget.cpp
 * @author apio (cloudapio.eu)
 * @brief Multiline text editing widget.
 *
 * @copyright Copyright (c) 2024, the Luna authors.
 *
 */

#include "EditorWidget.h"
#include <ctype.h>
#include <luna/PathParser.h>
#include <luna/Utf8.h>
#include <os/File.h>
#include <os/FileSystem.h>
#include <ui/App.h>

EditorWidget::EditorWidget(ui::Window* window, ui::Widget* parent)
    : ui::TextInput(window, parent), m_font(ui::Font::default_font())
{
    recalculate_lines();
}

Result<void> EditorWidget::load_file(const os::Path& path)
{
    struct stat st;
    auto rc = os::FileSystem::stat(path, st, true);

    if (!rc.has_error() && !S_ISREG(st.st_mode))
    {
        os::eprintln("editor: not loading %s as it is not a regular file", path.name().chars());
        return {};
    }

    os::eprintln("Loading file: %s", path.name().chars());

    auto file = TRY(os::File::open_or_create(path, os::File::ReadOnly));

    m_data = TRY(file->read_all());

    os::eprintln("Read %zu bytes.", m_data.size());

    m_cursor = m_data.size();

    m_path = path;

    auto basename = TRY(PathParser::basename(m_path.name()));

    String title = TRY(String::format("Text Editor - %s"_sv, basename.chars()));
    window()->set_title(title.view());

    TRY(recalculate_lines());

    return {};
}

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

    if (request.code == moon::K_UpArrow)
    {
        if (m_cursor_position.y > 0) m_cursor_position.y--;
        else
            return ui::EventResult::DidNotHandle;
        recalculate_cursor_index();
        update_cursor();
        return ui::EventResult::DidHandle;
    }

    if (request.code == moon::K_DownArrow)
    {
        if (m_cursor_position.y + 1 < (int)m_lines.size()) m_cursor_position.y++;
        else
            return ui::EventResult::DidNotHandle;
        recalculate_cursor_index();
        update_cursor();
        return ui::EventResult::DidHandle;
    }

    if (request.code == moon::K_LeftArrow)
    {
        if (m_cursor > 0) m_cursor--;
        else
            return ui::EventResult::DidNotHandle;
        recalculate_cursor_position();
        update_cursor();
        return ui::EventResult::DidHandle;
    }

    if (request.code == moon::K_RightArrow)
    {
        if (m_cursor < m_data.size()) m_cursor++;
        else
            return ui::EventResult::DidNotHandle;
        recalculate_cursor_position();
        update_cursor();
        return ui::EventResult::DidHandle;
    }

    if (request.code == moon::K_Backspace)
    {
        if (m_cursor == 0) return ui::EventResult::DidNotHandle;
        m_cursor--;

        delete_current_character();

        TRY(recalculate_lines());

        update_cursor();

        return ui::EventResult::DidHandle;
    }

    if (request.letter != '\n' && iscntrl(request.letter)) return ui::EventResult::DidNotHandle;

    if (m_cursor == m_data.size()) TRY(m_data.append_data((const u8*)&request.letter, 1));
    else
        TRY(insert_character(request.letter));

    m_cursor++;
    TRY(recalculate_lines());

    update_cursor();

    return ui::EventResult::DidHandle;
}

Result<void> EditorWidget::save_file()
{
    if (m_path.is_empty_path())
    {
        os::eprintln("editor: no file to save buffer to!");
        return err(ENOENT);
    }

    auto file = TRY(os::File::open(m_path, os::File::WriteOnly));
    return file->write(m_data);
}

Result<void> EditorWidget::draw(ui::Canvas& canvas)
{
    int visible_lines = canvas.height / m_font->height();
    int visible_columns = canvas.width / m_font->width();

    if ((usize)visible_lines > m_lines.size()) visible_lines = static_cast<int>(m_lines.size());

    for (int i = 0; i < visible_lines; i++)
    {
        auto line = m_lines[i];
        if (line.begin == line.end) continue;

        auto slice = TRY(m_data.slice(line.begin, line.end - line.begin));
        auto string = TRY(
            String::from_string_view(StringView::from_fixed_size_cstring((const char*)slice, line.end - line.begin)));

        Utf8StringDecoder decoder(string.chars());
        wchar_t buf[4096];
        decoder.decode(buf, sizeof(buf)).release_value();

        int characters_to_render = (int)wcslen(buf);

        for (int j = 0; j < visible_columns && j < characters_to_render; j++)
        {
            auto subcanvas =
                canvas.subcanvas({ j * m_font->width(), i * m_font->height(), m_font->width(), m_font->height() });
            m_font->render(buf[j], ui::WHITE, subcanvas);
        }
    }

    // Draw the cursor
    if (m_cursor_position.x < visible_columns && m_cursor_position.y < visible_lines && m_cursor_activated)
    {
        canvas
            .subcanvas(
                { m_cursor_position.x * m_font->width(), m_cursor_position.y * m_font->height(), 1, m_font->height() })
            .fill(ui::WHITE);
    }

    return {};
}

Result<void> EditorWidget::recalculate_lines()
{
    m_lines.clear();

    Line l;
    l.begin = 0;
    for (usize i = 0; i < m_data.size(); i++)
    {
        if (m_data.data()[i] == '\n')
        {
            l.end = i;
            TRY(m_lines.try_append(l));
            l.begin = i + 1;
        }
    }

    l.end = m_data.size();
    TRY(m_lines.try_append(l));

    recalculate_cursor_position();

    return {};
}

void EditorWidget::recalculate_cursor_position()
{
    if (m_cursor == 0) m_cursor_position = { 0, 0 };

    for (int i = 0; i < (int)m_lines.size(); i++)
    {
        auto line = m_lines[i];
        if (m_cursor >= line.begin && m_cursor <= line.end)
        {
            m_cursor_position.x = (int)(m_cursor - line.begin);
            m_cursor_position.y = i;
            return;
        }
    }

    unreachable();
}

void EditorWidget::recalculate_cursor_index()
{
    m_cursor = m_lines[m_cursor_position.y].begin + m_cursor_position.x;
    if (m_cursor > m_lines[m_cursor_position.y].end)
    {
        m_cursor = m_lines[m_cursor_position.y].end;
        recalculate_cursor_position();
    }
}