#include "pch.h"
#include "app.h"
#include "log.h"
#include "legacy_ui_gl_dispatch.h"
#include "legacy_ui_overlay_services.h"
#include "node_text_input.h"
#include "node_border.h"

Node* NodeTextInput::clone_instantiate() const
{
    return new NodeTextInput();
}

void NodeTextInput::on_tick(float dt)
{
    timer += dt;
    
    bool focus = root()->current_key_capture.get() == this;
    if (!focus)
        m_cursor_visible = false;
    m_thinkness = focus;

    if (timer > 1.0)
    {
        timer = 0;
        if (focus)
        {
            m_cursor_visible = !m_cursor_visible;
            app_redraw();
        }
    }
}

void NodeTextInput::clone_copy(Node* dest) const
{
    NodeBorder::clone_copy(dest);
    NodeTextInput* n = static_cast<NodeTextInput*>(dest);
    n->m_text_mesh.max_width = m_text_mesh.max_width;
    n->m_text_mesh.create();
    n->m_text_mesh.update(m_text, m_font_name, m_font_size, m_font_weight, m_font_italic);
    n->m_text = m_text;
    n->m_font_name = m_font_name;
    n->m_font_size = m_font_size;
    n->m_font_weight = m_font_weight;
    n->m_font_italic = m_font_italic;
    n->m_color = m_color;
    n->m_multiline = m_multiline;
    n->m_off = m_off;
    n->m_text_align_v = m_text_align_v;
    n->m_text_align_h = m_text_align_h;
}

kEventResult NodeTextInput::handle_event(Event* e)
{
    KeyEvent* ke = (KeyEvent*)e;
    switch (e->m_type)
    {
    case kEventType::MouseDownL:
        App::I->showKeyboard();
        break;
    case kEventType::MouseUpL:
        key_capture();
        timer = 0;
        m_cursor_visible = true;
        break;
    case kEventType::KeyDown:
        if (!has_focus())
            return kEventResult::Available;
        //switch (ke->m_key)
        //{
        //case VK_BACK:
        //    m_string.erase(m_string.end() - 1);
        //    m_text->set_text(m_string);
        //    break;
        //default:
        //    break;
        //}
        break;
    case kEventType::KeyUp:
        if (!has_focus())
            return kEventResult::Available;
        if (ke->m_key == kKey::KeyEnter && on_return)
            on_return(this);
        if (ke->m_key == kKey::KeyBackspace)
        {
            if (!m_text.empty())
            {
                m_text.erase(m_text.end() - 1);
                set_text(m_text);
            }
        }
        break;
    case kEventType::KeyChar:
        if (!has_focus())
            return kEventResult::Available;
        timer = 0;
        m_cursor_visible = true;
        if (ke->m_char == '\b' // backspace
            || ke->m_char == 0x7f // DEL
                )
        {
            if (!m_text.empty())
            {
                m_text.erase(m_text.end() - 1);
                set_text(m_text);
            }
        }
        else if (ke->m_char == '\n' || ke->m_char == '\r') // enter/return
        {
            if (m_multiline)
            {
                m_text += '\n';
                set_text(m_text);
            }
            else if (on_return)
            {
                on_return(this);
            }
        }
        else if ((uint8_t)ke->m_char >= 32 && (uint8_t)ke->m_char < 256) // visible ascii character
        {
            m_text += (char)ke->m_char;
            set_text(m_text);
        }
        break;
    default:
        return kEventResult::Available;
        break;
    }
    return kEventResult::Consumed;
}

void NodeTextInput::set_text(const std::string& s)
{
    m_text = s;
    m_text_mesh.update(m_text, m_font_name, m_font_size, m_font_weight, m_font_italic);
    update_layout();
}

void NodeTextInput::set_text_format(const char* fmt, ...)
{
    static char buffer[4096];
    va_list args;
    va_start(args, fmt);
    vsnprintf(buffer, sizeof(buffer), fmt, args);
    va_end(args);
    m_text = buffer;
    m_text_mesh.update(m_text, m_font_name, m_font_size, m_font_weight, m_font_italic);
    update_layout();
}

void NodeTextInput::set_font(const std::string& name, int size, const std::string& weight, bool italic)
{
    m_font_name = name;
    m_font_size = size;
    m_font_weight = weight;
    m_font_italic = italic;
    m_text_mesh.create();
    m_text_mesh.update(m_text, m_font_name, m_font_size, m_font_weight, m_font_italic);
    update_layout();
}

void NodeTextInput::set_font_size(int size)
{
    m_font_size = size;
    m_text_mesh.create();
    m_text_mesh.update(m_text, m_font_name, m_font_size, m_font_weight, m_font_italic);
    update_layout();
}

void NodeTextInput::update_layout()
{
    auto pad = GetPadding();
    float h = GetHeight() - (pad[1] + pad[3]);
    float w = GetWidth() - (pad[0] + pad[2]);

    if (m_text_align_v == TextAlign::Begin)
        m_off.y = pad[0];
    else if (m_text_align_v == TextAlign::Center)
        m_off.y = (h - m_text_mesh.bb.y) * 0.5f + pad[0];
    else
        m_off.y = (h - m_text_mesh.bb.y) + pad[0];

    if (m_text_align_h == TextAlign::Begin)
        m_off.x = pad[3];
    else if (m_text_align_v == TextAlign::Center)
        m_off.x = (w - m_text_mesh.bb.x) * 0.5f + pad[3];
    else
        m_off.x = (w - m_text_mesh.bb.x) + pad[3];
}

bool NodeTextInput::has_focus() noexcept
{
    return root()->current_key_capture.get() == this;
}

void NodeTextInput::handle_resize(glm::vec2 old_size, glm::vec2 new_size, float zoom)
{
    auto pad = GetPadding();
    m_text_mesh.max_width = m_multiline ? new_size.x - (pad[1] + pad[3]) : 0;
    update_layout();
}

void NodeTextInput::create()
{
    NodeBorder::create();
    if (!m_font_name.empty())
    {
        m_text_mesh.create();
        m_text_mesh.update(m_text, m_font_name, m_font_size, m_font_weight, m_font_italic);
        update_layout();
    }
}

void NodeTextInput::draw()
{
    NodeBorder::draw();

    glm::mat4 pos = glm::translate(glm::vec3(glm::floor(m_pos + m_off), 0));
    pp::panopainter::configure_legacy_ui_font_shader(m_proj * pos, m_color);
    pp::legacy::ui_gl::set_blend_enabled(true, "NodeTextInput");
    m_text_mesh.draw();
    pp::legacy::ui_gl::set_blend_enabled(false, "NodeTextInput");

    if (m_cursor_visible)
    {
        pp::legacy::ui_gl::set_blend_enabled(false, "NodeTextInput");
        glm::mat4 cur_pos = glm::translate(glm::vec3(m_pos + m_off + xy(m_text_mesh.cur_box), 0));
        glm::mat4 cur_scale = glm::scale(glm::vec3(zw(m_text_mesh.cur_box), 1));
        glm::mat4 cur_pivot = glm::translate(glm::vec3(0.5, 0.5, 0));
        pp::panopainter::configure_legacy_ui_color_shader(
            m_proj * cur_pos * cur_scale * cur_pivot,
            { 0, 0, 0, 1 });
        m_plane.draw_fill();
    }
}

void NodeTextInput::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
    switch (ka)
    {
    case kAttribute::Multiline:
        m_multiline = attr->BoolValue();
        break;
    case kAttribute::TextAlign:
        if (strcmp(attr->Value(), "left") == 0)
            m_text_align_h = TextAlign::Begin;
        else if (strcmp(attr->Value(), "center") == 0)
            m_text_align_h = TextAlign::Center;
        else if (strcmp(attr->Value(), "right") == 0)
            m_text_align_h = TextAlign::End;
        break;
    case kAttribute::TextVerticalAlign:
        if (strcmp(attr->Value(), "top") == 0)
            m_text_align_v = TextAlign::Begin;
        else if (strcmp(attr->Value(), "center") == 0)
            m_text_align_v = TextAlign::Center;
        else if (strcmp(attr->Value(), "bottom") == 0)
            m_text_align_v = TextAlign::End;
        break;
    default:
        NodeBorder::parse_attributes(ka, attr);
    }
}

void NodeTextInput::destroy()
{
    if (root()->current_key_capture.get() == this)
        App::I->hideKeyboard();
    NodeBorder::destroy();
}
