//
//  imGui integration in lolengine
//
//  Copyright © 2009—2015 Benjamin “Touky” Huet <huet.benjamin@gmail.com>
//
//  Lol Engine is free software. It comes without any warranty, to
//  the extent permitted by applicable law. You can redistribute it
//  and/or modify it under the terms of the Do What the Fuck You Want
//  to Public License, Version 2, as published by the WTFPL Task Force.
//  See http://www.wtfpl.net/ for more details.
//

#include <lol/engine-internal.h>

#include "imgui.cpp"

#include <cstdio>

using namespace lol;

#define Line(s) ((s) + "\n")

//-----------------------------------------------------------------------------
LolImGui::LolImGui()
{
    m_gamegroup = GAMEGROUP_IMGUI;
    m_drawgroup = DRAWGROUP_IMGUI;

    //Build shader code -------------------------------------------------------
    ShaderVar out_vertex = ShaderVar::GetShaderOut(ShaderProgram::Vertex);
    ShaderVar out_pixel = ShaderVar::GetShaderOut(ShaderProgram::Pixel);

    ShaderVar pass_texcoord = ShaderVar(ShaderVariable::Varying, ShaderVariableType::Vec2, "texcoord");
    ShaderVar pass_color = ShaderVar(ShaderVariable::Varying, ShaderVariableType::Vec4, "color");

    ShaderVar in_position = ShaderVar(ShaderVariable::Attribute, ShaderVariableType::Vec2, "position");
    ShaderVar in_texcoord = ShaderVar(ShaderVariable::Attribute, ShaderVariableType::Vec2, "texcoord");
    ShaderVar in_color = ShaderVar(ShaderVariable::Attribute, ShaderVariableType::Vec4, "color");

    m_ortho.m_var = ShaderVar(ShaderVariable::Uniform, ShaderVariableType::Mat4, "ortho");
    m_texture.m_var = ShaderVar(ShaderVariable::Uniform, ShaderVariableType::sampler2D, "texture");

    ShaderBlock imgui_vertex("imgui_vertex");
    imgui_vertex
        << out_vertex << m_ortho << in_position
        << pass_texcoord << in_texcoord
        << pass_color << in_color;
    imgui_vertex.SetMainCode(String() +
        Line(out_vertex + " = .5 *" + m_ortho + " * vec4(" + in_position + ", -1.0, 1.0);")
        + Line(pass_texcoord + " = " + in_texcoord + ";")
        + Line(pass_color + " = " + in_color + ";")
        );

    ShaderBlock imgui_pixel("imgui_pixel");
    imgui_pixel << m_texture << pass_texcoord << pass_color << out_pixel;
    imgui_pixel.SetMainCode(String() +
        Line(String()
        + "vec4 col = " + pass_color + " * texture2D(" + m_texture + ", " + pass_texcoord + ");")
        + Line(String()
        + "if (col.a == 0.0) discard; ")
        + Line(out_pixel + " = col;")
        );

    m_builder
        << ShaderProgram::Vertex << imgui_vertex
        << ShaderProgram::Pixel << imgui_pixel;

    //Input Setup -------------------------------------------------------------
    InputProfile& ip = m_profile;
    ip.AddBindings<LolImGuiKey, LolImGuiKey::KEY_START, LolImGuiKey::KEY_END>(InputProfileType::Keyboard);
    //for (int i = LolImGuiKey::KEY_START; i < LolImGuiKey::KEY_END; ++i)
    //    m_profile << InputProfile::Keyboard(i, LolImGuiKey(i).ToString());
    for (int i = LolImGuiKey::MOUSE_KEY_START; i < LolImGuiKey::MOUSE_KEY_END; ++i)
        m_profile << InputProfile::MouseKey(i, LolImGuiKey(i).ToString());

    for (int i = LolImGuiAxis::MOUSE_AXIS_START; i < LolImGuiAxis::MOUSE_AXIS_END; ++i)
        m_profile << InputProfile::MouseAxis(i, LolImGuiAxis(i).ToString());

    Ticker::Ref(m_controller = new Controller("ImGui_Controller"));
    m_controller->Init(m_profile);
    //InputDevice::CaptureMouse(true);
    m_mouse = InputDevice::GetMouse();
    m_keyboard = InputDevice::GetKeyboard();

    //m_controller->Get
    //#   define KB InputProfile::Keyboard
//    m_profile
//        << InputProfile::Keyboard(idx, g_name_key_Left);
//#   undef KB

}
LolImGui::~LolImGui()
{
    ImGui::GetIO().Fonts->TexID = nullptr;
    Ticker::Unref(m_font);
    m_font = nullptr;

    Shader::Destroy(m_shader);
    delete m_vdecl;
}

//-----------------------------------------------------------------------------
LolImGui* g_lolimgui = nullptr;
void LolImGui::Init()
{
    Ticker::Ref(g_lolimgui = new LolImGui());

    ImGuiIO& io = ImGui::GetIO();
    //ImFont* font0 = io.Fonts->AddFontDefault();

    // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array.
    io.KeyMap[ImGuiKey_Tab]         = LolImGuiKey::Tab;
    io.KeyMap[ImGuiKey_LeftArrow]   = LolImGuiKey::LeftArrow;
    io.KeyMap[ImGuiKey_RightArrow]  = LolImGuiKey::RightArrow;
    io.KeyMap[ImGuiKey_UpArrow]     = LolImGuiKey::UpArrow;
    io.KeyMap[ImGuiKey_DownArrow]   = LolImGuiKey::DownArrow;
    io.KeyMap[ImGuiKey_Home]        = LolImGuiKey::Home;
    io.KeyMap[ImGuiKey_End]         = LolImGuiKey::End;
    io.KeyMap[ImGuiKey_Delete]      = LolImGuiKey::Delete;
    io.KeyMap[ImGuiKey_Backspace]   = LolImGuiKey::Backspace;
    io.KeyMap[ImGuiKey_Enter]       = LolImGuiKey::Enter;
    io.KeyMap[ImGuiKey_Escape]      = LolImGuiKey::Escape;
    io.KeyMap[ImGuiKey_A]           = LolImGuiKey::A;
    io.KeyMap[ImGuiKey_C]           = LolImGuiKey::C;
    io.KeyMap[ImGuiKey_V]           = LolImGuiKey::V;
    io.KeyMap[ImGuiKey_X]           = LolImGuiKey::X;
    io.KeyMap[ImGuiKey_Y]           = LolImGuiKey::Y;
    io.KeyMap[ImGuiKey_Z]           = LolImGuiKey::Z;

    //Func pointer
    io.RenderDrawListsFn = LolImGui::RenderDrawLists;
    io.SetClipboardTextFn = LolImGui::SetClipboard;
    io.GetClipboardTextFn = LolImGui::GetClipboard;
}

/* CALLBACKS
void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c)
{
ImGuiIO& io = ImGui::GetIO();
if (c > 0 && c < 0x10000)
io.AddInputCharacter((unsigned short)c);
}

*/

void LolImGui::Shutdown()
{
    Ticker::Unref(g_lolimgui);

    ImGui::Shutdown();
}

//-----------------------------------------------------------------------------
static String g_clipboard;
void LolImGui::SetClipboard(const char* text)
{
    g_clipboard = text;
}
const char* LolImGui::GetClipboard()
{
    return g_clipboard.C();
}

//-----------------------------------------------------------------------------
void LolImGui::TickGame(float seconds)
{
    super::TickGame(seconds);

    ImGuiIO& io = ImGui::GetIO();

    //Init Texture
    if (!m_font)
    {
        // Build texture
        unsigned char* pixels;
        ivec2 size;
        io.Fonts->GetTexDataAsRGBA32(&pixels, &size.x, &size.y);

        Image* image = new Image();
        image->Copy(pixels, size, PixelFormat::RGBA_8);

        Ticker::Ref(m_font = new TextureImage("", image));
    }
    //Texture has been created
    if (m_font && m_font->GetTexture())
    {
        io.Fonts->TexID = (void *)(intptr_t)m_font;
    }

    // Setup display size (every frame to accommodate for window resizing)
    vec2 video_size = vec2(0);
    video_size = vec2(Video::GetSize());
    io.DisplaySize = ImVec2(video_size.x, video_size.y);

    //Setup time step
    io.DeltaTime = seconds;
    io.MouseDrawCursor = true;

    //Update Keyboard
    io.KeyCtrl = false;
    io.KeyShift = false;
    for (int i = LolImGuiKey::KEY_START; i < LolImGuiKey::KEY_END; ++i)
    {
        switch (i)
        {
        default:
            io.KeysDown[i] = m_controller->IsKeyPressed(i);
            break;
        case LolImGuiKey::LShift:
        case LolImGuiKey::RShift:
            io.KeyShift = (io.KeyShift || m_controller->IsKeyPressed(i));
            break;
        case LolImGuiKey::LCtrl:
        case LolImGuiKey::RCtrl:
            io.KeyCtrl = (io.KeyCtrl || m_controller->IsKeyPressed(i));
            break;
        }
    }

    //Update text input
    String text = m_keyboard->GetText();
    //text.case_change(io.KeyShift);
    for (int i = 0; i < text.count(); ++i)
        io.AddInputCharacter(text[i]);

    //Update mouse
    if (m_mouse)
    {
        vec2 cursor = m_mouse->GetCursor(0);
        cursor.y = 1.f - cursor.y;
        cursor *= video_size;
        io.MousePos = ImVec2(cursor.x, cursor.y);
        msg::debug("%.2f/%.2f\n", io.MousePos.x, io.MousePos.y);
        io.MouseWheel = m_controller->GetAxisValue(LolImGuiAxis::Scroll);

        for (int i = LolImGuiKey::MOUSE_KEY_START; i < LolImGuiKey::MOUSE_KEY_END; ++i)
        {
            switch (i)
            {
            default:
                io.MouseDown[i - LolImGuiKey::MOUSE_KEY_START] = m_controller->IsKeyPressed(i);
                break;
            case LolImGuiKey::Focus:
                if (m_controller->IsKeyReleased(i))
                {
                    msg::debug("Not focused .....\n");
                    io.MousePos = ImVec2(-1.f, -1.f);
                }
                else
                    msg::debug("Focused !!\n");
                break;
            }
        }
    }

    // Start the frame
    ImGui::NewFrame();
}

//-----------------------------------------------------------------------------
void LolImGui::TickDraw(float seconds, Scene &scene)
{
    super::TickDraw(seconds, scene);

    scene.AddPrimitiveRenderer(this, new PrimitiveLolImGui());
}
void PrimitiveLolImGui::Render(Scene& scene, PrimitiveSource* primitive)
{
    ImGuiIO& io = ImGui::GetIO();
    if (io.Fonts->TexID)
        ImGui::Render();
}

//// Data
//static GLFWwindow*  g_Window = NULL;
//static double       g_Time = 0.0f;
//static bool         g_MousePressed[3] = { false, false, false };
//static float        g_MouseWheel = 0.0f;
//static GLuint       g_FontTexture = 0;

//-------------------------------------------------------------------------
// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure)
// If text or lines are blurry when integrating ImGui in your engine:
// - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f)
//-------------------------------------------------------------------------
void LolImGui::RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count)
{
    g_lolimgui->RenderDrawListsMethod(cmd_lists, cmd_lists_count);
}
void LolImGui::RenderDrawListsMethod(ImDrawList** const cmd_lists, int cmd_lists_count)
{
    if (cmd_lists_count == 0)
        return;

    vec2 size = vec2(Video::GetSize());
    float alpha = 1.f;
    mat4 ortho = mat4::ortho(size.x * alpha, size.y * alpha, -1000.f, 1000.f)
        * mat4::lookat(vec3::axis_z, vec3::zero, vec3::axis_y)
        * mat4::scale(vec3::axis_x - vec3::axis_y - vec3::axis_z)
        * mat4::translate(-size.x * .5f * alpha, -size.y * .5f * alpha, 0.f);

    //Create shader
    if (!m_shader)
    {
        String code;
        m_builder.Build(code);

        m_shader = Shader::Create(m_builder.GetName(), code);
        ASSERT(m_shader);

        m_ortho.m_uniform = m_shader->GetUniformLocation(m_ortho.m_var);
        m_texture.m_uniform = m_shader->GetUniformLocation(m_texture.m_var);

        m_attribs
            << m_shader->GetAttribLocation(VertexUsage::Position, 0)
            << m_shader->GetAttribLocation(VertexUsage::TexCoord, 0)
            << m_shader->GetAttribLocation(VertexUsage::Color, 0);

        m_vdecl = new VertexDeclaration(
            VertexStream<vec2, vec2, u8vec4>(
                VertexUsage::Position,
                VertexUsage::TexCoord,
                VertexUsage::Color));
    }

    //Do not render without shader
    if (!m_shader)
        return;

    RenderContext rc;
    rc.SetCullMode(CullMode::Disabled);
    rc.SetDepthFunc(DepthFunc::Disabled);

    m_shader->Bind();
    for (int n = 0; n < cmd_lists_count; n++)
    {
        const ImDrawList* cmd_list = cmd_lists[n];
        const unsigned char* vtx_buffer = (const unsigned char*)&cmd_list->vtx_buffer.front();

        //Register uniforms
        m_shader->SetUniform(m_ortho, ortho);
        m_shader->SetUniform(m_texture, m_font->GetTexture()->GetTextureUniform(), 0);

        int vtx_offset = 0;
        for (size_t cmd_i = 0; cmd_i < cmd_list->commands.size(); cmd_i++)
        {
            const ImDrawCmd* pcmd = &cmd_list->commands[cmd_i];

            struct Vertex
            {
                vec2 pos, tex;
                u8vec4 color;
            };

            VertexBuffer* vbo = new VertexBuffer(pcmd->vtx_count * sizeof(Vertex));
            Vertex *vert = (Vertex *)vbo->Lock(0, 0);

#ifdef SHOW_IMGUI_DEBUG
            //-----------------------------------------------------------------
            //<Debug render> --------------------------------------------------
            //-----------------------------------------------------------------
            float mod = -200.f;
            vec3 off = vec3(vec2(-size.x, -size.y), 0.f);
            vec3 pos[4] = {
                (1.f / mod) * (off + vec3(0.f)),
                (1.f / mod) * (off + size.x * vec3::axis_x),
                (1.f / mod) * (off + size.x * vec3::axis_x + size.y * vec3::axis_y),
                (1.f / mod) * (off + size.y * vec3::axis_y)
            };
            for (int i = 0; i < 4; ++i)
                Debug::DrawLine(pos[i], pos[(i + 1) % 4], Color::white);
            ImDrawVert* buf = (ImDrawVert*)(vtx_buffer + vtx_offset);
            for (uint16_t i = 0; i < pcmd->vtx_count; i += 3)
            {
                vec2 pos[3];
                pos[0] = vec2(buf[i + 0].pos.x, buf[i + 0].pos.y);
                pos[1] = vec2(buf[i + 1].pos.x, buf[i + 1].pos.y);
                pos[2] = vec2(buf[i + 2].pos.x, buf[i + 2].pos.y);
                vec4 col[3];
                col[0] = vec4(Color::FromRGBA32(buf[i + 0].col).arg, 1.f);
                col[1] = vec4(Color::FromRGBA32(buf[i + 1].col).arg, 1.f);
                col[2] = vec4(Color::FromRGBA32(buf[i + 2].col).arg, 1.f);
                Debug::DrawLine((off + vec3(pos[0], 0.f)) / mod, (off + vec3(pos[1], 0.f)) / mod, col[0]);
                Debug::DrawLine((off + vec3(pos[1], 0.f)) / mod, (off + vec3(pos[2], 0.f)) / mod, col[1]);
                Debug::DrawLine((off + vec3(pos[2], 0.f)) / mod, (off + vec3(pos[0], 0.f)) / mod, col[2]);
            }
            //-----------------------------------------------------------------
            //<\Debug render> -------------------------------------------------
            //-----------------------------------------------------------------
#endif //SHOW_IMGUI_DEBUG

            memcpy(vert, vtx_buffer + vtx_offset, pcmd->vtx_count * sizeof(Vertex));
            vbo->Unlock();

            m_font->Bind();
            m_vdecl->Bind();
            m_vdecl->SetStream(vbo, m_attribs[0], m_attribs[1], m_attribs[2]);
            m_vdecl->DrawElements(MeshPrimitive::Triangles, 0, pcmd->vtx_count);
            m_vdecl->Unbind();
            m_font->Unbind();

            vtx_offset += pcmd->vtx_count * sizeof(Vertex);

            delete vbo;
        }
    }
    m_shader->Unbind();
}

