#include "graphics/opengl/render_context.hpp"

#include "logging/engine_logger.hpp"

#include <glad/gl.h>
#include <GLFW/glfw3.h>

using namespace kuiper;
using namespace kuiper::gl;

void gl::initialise() {
    int version = gladLoadGL(glfwGetProcAddress);
    if (!version) {
        throw std::runtime_error("Failed to load & resolve OpenGL symbols with GLAD");
    }

    // Debug logging
#if !defined(NDEBUG)
    GLint flags = 0;
    glGetIntegerv(GL_CONTEXT_FLAGS, &flags);

    if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
        glEnable(GL_DEBUG_OUTPUT);
        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
        glDebugMessageCallback(gl::debug_callback, nullptr);
        glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
    }
#endif

    auto logger = engine_logger::get();
    logger.debug("OpenGL version information:");
    logger.debug("\tVendor: {}", (const char*) glGetString(GL_VENDOR));
    logger.debug("\tRenderer: {}", (const char*) glGetString(GL_RENDERER));
    logger.debug("\tVersion: {}", (const char*) glGetString(GL_VERSION));
    logger.debug("\tGLSL version: {}", (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION));

#if !defined(NDEBUG)
    dump_hardware_info();
#endif

    return;
}

void gl::dump_hardware_info() {
    auto& logger = engine_logger::get();

    logger.debug("OpenGL driver information:");
    logger.debug("\tGL_MAX_FRAGMENT_UNIFORM_BLOCKS: {}", render_context::get<GLint>(GL_MAX_FRAGMENT_UNIFORM_BLOCKS));
    logger.debug("\tGL_MAX_UNIFORM_BLOCK_SIZE: {}", render_context::get<GLint>(GL_MAX_UNIFORM_BLOCK_SIZE));
    logger.debug("\tGL_UNIFORM_BUFFER_OFFSET_ALIGNMENT: {}",
                 render_context::get<GLint>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT));
    logger.debug("\tGL_MAX_COLOR_ATTACHMENTS: {}", render_context::get<GLint>(GL_MAX_COLOR_ATTACHMENTS));
    logger.debug("\tGL_MAX_DRAW_BUFFERS: {}", render_context::get<GLint>(GL_MAX_DRAW_BUFFERS));
    logger.debug("\tGL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: {}",
                 render_context::get<GLint>(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS));
    logger.debug("\tGL_MAX_TEXTURE_IMAGE_UNITS: {}", render_context::get<GLint>(GL_MAX_TEXTURE_IMAGE_UNITS));
    logger.debug("\tGL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: {}",
                 render_context::get<GLint>(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS));

    return;
}

#if !defined(NDEBUG)
void gl::debug_callback(GLenum        source,
                        GLenum        type,
                        GLuint        id,
                        GLenum        severity,
                        GLsizei       length,
                        const GLchar* message,
                        const void*   userParam) noexcept {
    auto logger = engine_logger::get();
    logger.error("OpenGL error: {}", message);
}
#endif

void render_context::bind_framebuffer(const framebuffer::s_ptr& fb) noexcept {
    return fb->bind();
}

void render_context::bind_default_framebuffer() noexcept {
    return glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void render_context::set_viewport_size(std::uint32_t width, std::uint32_t height) noexcept {
    return glViewport(0, 0, static_cast<GLint>(width), static_cast<GLint>(height));
}

void render_context::enable_feature(GLenum feature) noexcept {
    return glEnable(feature);
}

void render_context::disable_feature(GLenum feature) noexcept {
    return glDisable(feature);
}
