#include <blt/gfx/window.h>
#include <blt/gfx/state.h>
#include <imgui.h>
#include "blt/gfx/imgui/IconsFontAwesome5.h"

#include "blt/gfx/renderer/resource_manager.h"
#include "blt/gfx/renderer/batch_2d_renderer.h"
#include "blt/gfx/renderer/camera.h"
#include <blt/parse/obj_loader.h>

#include <blt/gfx/renderer/3d_textured.vert>
#include <blt/gfx/renderer/3d_textured.frag>

blt::gfx::matrix_state_manager global_matrices;
blt::gfx::resource_manager resources;
blt::gfx::batch_renderer_2d renderer_2d(resources);
blt::gfx::first_person_camera camera;

std::vector<std::pair<blt::gfx::vertex_array_t*, size_t>> vaos;
blt::gfx::shader_t* shader;

float x = 0, y = 0, z = 0;
float bx = 500, by = 500;
float mx = 0, my = -9.8;

void local_render(int width, int height)
{
    const float w = 120, h = 120, cf = 30, rf = 15, crf = 10;
    
    renderer_2d.drawRectangle("ibuythat", (float) width / 2.0f, (float) height / 2.0f, (float) width, (float) height, 90.0f);
    renderer_2d.drawRectangle("niko", bx, by, w, h);
    
    bx += mx * blt::gfx::getFrameDeltaSeconds() * cf;
    by += my * blt::gfx::getFrameDeltaSeconds() * cf;
    
    if (bx < w / 2.0 || bx > width - w / 2.0)
    {
        mx = -mx;
        my += (static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * rf + crf) - (rf + crf) / 2.0f;
    }
    if (by < h / 2.0 || by > height - h / 2.0)
    {
        my = -my;
        mx += (static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * rf + crf) - (rf + crf) / 2.0f;
    }
    if (mx > 100 || mx < -100)
    {
        mx = mx * 0.2;
    }
    if (my > 100 || my < -100)
        my = my * 0.2;
}

void init()
{
    using namespace blt::gfx;
    
    resources.enqueue("../resources/textures/cumdollar.jpg", "ibuythat");
    resources.enqueue("../resources/textures/dfoedbi-28157978-1555-45c3-b2f4-d5e5fe25b253.png", "niko");
    
    auto object = blt::parse::quick_load("../resources/models/many_objects.obj");
    
    vertex_buffer_t vertices_vbo;
    vertices_vbo.create();
    vertices_vbo.allocate(static_cast<long>(object.vertex_data().size() * sizeof(blt::parse::constructed_vertex_t)), object.vertex_data().data());
    auto ptr = vertex_array_t::make_vbo(vertices_vbo);
    
    for (auto obj : object.objects())
    {
        vertex_buffer_t indices_vbo;
        indices_vbo.create(GL_ELEMENT_ARRAY_BUFFER);
        indices_vbo.allocate(static_cast<long>(obj.indices.size() * sizeof(blt::parse::triangle_t)), obj.indices.data());
        
        auto* vao = new vertex_array_t();
        vao->bindVBO(ptr, 0, 3, GL_FLOAT, sizeof(float) * (3 + 3 + 2), 0);
        vao->bindVBO(ptr, 1, 2, GL_FLOAT, sizeof(float) * (3 + 3 + 2), sizeof(float) * 3);
        vao->bindVBO(ptr, 2, 3, GL_FLOAT, sizeof(float) * (3 + 3 + 2), sizeof(float) * (3 + 2));
        
        vao->bindElement(indices_vbo);
        
        vaos.emplace_back(vao, obj.indices.size() * 3);
    }
    
    shader = new shader_t(shader_3d_textured_vert, shader_3d_textured_frag);
    
    global_matrices.create_internals();
    resources.load_resources();
    renderer_2d.create();
}

void update(std::int32_t width, std::int32_t height)
{
    global_matrices.update_perspectives(width, height, 90, 0.1, 2000);
    ImGui::ShowDemoWindow();
    ImGui::SetNextWindowSize(ImVec2(200, 100));
    ImGui::Begin("Debug Info Panel", nullptr, ImGuiWindowFlags_NoResize);
    ImGui::Text("%s FPS: %f", ICON_FA_WRENCH, 1.0e9 / static_cast<double>(blt::gfx::getFrameDelta()));
    ImGui::Text("Draw Count: %zu", renderer_2d.draw_count());
    ImGui::Text("Position: %f %f %f", camera.position().x(), camera.position().y(), camera.position().z());
    ImGui::End();
    
    camera.update();
    camera.update_view(global_matrices);
    global_matrices.update();
    
    //local_render(width, height);
    
    shader->bind();
    shader->setMatrix("model", blt::mat4x4());
    glActiveTexture(GL_TEXTURE0);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glEnable(GL_MULTISAMPLE);
    glCullFace(GL_BACK);
    if (auto img = resources.get("niko"))
        img.value()->bind();
    for (auto p : vaos)
    {
        p.first->bind();
        glDrawElements(GL_TRIANGLES, (int)p.second, GL_UNSIGNED_INT, 0);
    }
    
    renderer_2d.render();
}

int main()
{
    blt::gfx::init(blt::gfx::window_data{"My Sexy Window", init, update}.setSyncInterval(1));
    global_matrices.cleanup();
    resources.cleanup();
    renderer_2d.cleanup();
    for (auto p : vaos)
        delete p.first;
    blt::gfx::cleanup();
    return 0;
}
