#include "glm/detail/qualifier.hpp"
#include "glm/ext/matrix_clip_space.hpp"
#include "glm/ext/matrix_transform.hpp"
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>

#include <iostream>
#include <fstream>
#include <ratio>
#include <vector>
#include <chrono>

#include <cimg_fb.hpp>
#include <mesh.hpp>
#include <obj_loader.hpp>
#include <object.hpp>

int DIM_WIDTH = 1280;
int DIM_HEIGHT = 720;

namespace gb {
    /*
     * prototype Camera
     * const glm::mat4& view_mat(void) const
     */
    class flat_camera
    {
    public:
        explicit flat_camera(unsigned int dimx, unsigned int dimy)
        {
            // camara pos
            m_mat = glm::lookAt(glm::vec3 { 2.f, 2.f, 2.f }, { 0.f, 0.f, 0.f }, { 0.f, 0.f, 1.f });
            // perspective
            m_mat = glm::perspective(
                    glm::radians(60.f),
                    float(1.f * DIM_WIDTH / DIM_HEIGHT),
                    0.1f,
                    100.f
                    ) * m_mat;

            // (-1, 1) to (0, 1)
            m_mat = glm::scale(glm::mat4(1), { 0.5f, 0.5f, 0.5f }) * m_mat;
            m_mat = glm::translate(glm::mat4(1), glm::vec3({0.5f, 0.5f, 0.f})) * m_mat;

            // viewport transformation
            m_mat = glm::mat4({1.f, 0.f, 0.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}) * m_mat;
            m_mat = glm::translate(glm::mat4(1), glm::vec3({0.f, 1.f, 0.f})) * m_mat;
            m_mat = glm::scale(glm::mat4(1), {1.f * dimx, 1.f * dimy, 1}) * m_mat;
        }

        const glm::mat4& view_mat(void) const
        {
            return m_mat;
        }
    private:
        glm::mat4 m_mat;
    };

    template <typename Framebuffer>
    static void draw_mesh(Framebuffer& fb, const gb::mesh_compute& mesh)
    {
        glm::vec<4, int> pos({0, 0, 0, 1});
        glm::vec4 pos_f({0.f, 0.f, 0.f, 1.f});

        using color_t = glm::vec<3, unsigned char>;

        static int color_index = 0;
        static color_t colors[] =  { color_t { 0x88, 0xcc, 0xff }, color_t { 0xff, 0xaa, 0xaa }, color_t {0xaa, 0xff, 0xaa} };

        for (pos.y = 0, pos_f.y = 0.f; pos.y < fb.dimy(); ++pos.y, ++pos_f.y) {
            for (pos.x = 0, pos_f.x = 0; pos.x < fb.dimx(); ++pos.x, ++pos_f.x) {
                if (mesh.sample(pos_f)) {
                    fb.set_pixel(pos, colors[color_index]);
                }
            }
        }

        if (++color_index == 3) {
            color_index = 0;
        }
    }

static std::chrono::steady_clock::time_point time_start;
void bench_start(void)
{
    time_start = std::chrono::steady_clock::now();
}

void bench_end(const char* msg)
{
    auto end = std::chrono::steady_clock::now();
    std::cout << "[Benchmark] " << std::chrono::duration_cast<std::chrono::milliseconds>(end - time_start).count() << "ms in " << msg << std::endl;
}
}

int main(void) {
    std::vector<gb::Object> rendering_queue;
    gb::obj_loader loader;

    {
        std::fstream f_model("./res/Banana.obj", std::ios::in);
        rendering_queue.push_back(loader.load_object(f_model));
        std::cout << "finish preparing the model" << std::endl;
    }


    gb::cimg_framebuffer fb("output.bmp", DIM_WIDTH, DIM_HEIGHT);
    gb::flat_camera camera(DIM_WIDTH, DIM_HEIGHT);

    gb::bench_start();
    for (auto& obj : rendering_queue)
    {
        obj.draw(fb, camera);
    }
    gb::bench_end("rendering");

    return 0;
}
