#pragma once

#include <cstdint>

#include "renderer_gl/opengl_capabilities.h"

namespace pp::legacy::gl_shader {

inline void use_opengl_program(std::uint32_t program) noexcept
{
    glUseProgram(static_cast<GLuint>(program));
}

inline void delete_opengl_program(std::uint32_t program) noexcept
{
    glDeleteProgram(static_cast<GLuint>(program));
}

inline void set_opengl_uniform_4fv(std::int32_t location, std::int32_t count, const float* values) noexcept
{
    glUniform4fv(static_cast<GLint>(location), static_cast<GLsizei>(count), values);
}

inline void set_opengl_uniform_3fv(std::int32_t location, std::int32_t count, const float* values) noexcept
{
    glUniform3fv(static_cast<GLint>(location), static_cast<GLsizei>(count), values);
}

inline void set_opengl_uniform_2fv(std::int32_t location, std::int32_t count, const float* values) noexcept
{
    glUniform2fv(static_cast<GLint>(location), static_cast<GLsizei>(count), values);
}

inline void set_opengl_uniform_matrix_4fv(
    std::int32_t location,
    std::int32_t count,
    std::uint8_t transpose,
    const float* values) noexcept
{
    glUniformMatrix4fv(
        static_cast<GLint>(location),
        static_cast<GLsizei>(count),
        static_cast<GLboolean>(transpose),
        values);
}

inline void set_opengl_uniform_1i(std::int32_t location, std::int32_t value) noexcept
{
    glUniform1i(static_cast<GLint>(location), static_cast<GLint>(value));
}

inline void set_opengl_uniform_1f(std::int32_t location, float value) noexcept
{
    glUniform1f(static_cast<GLint>(location), value);
}

inline std::int32_t get_opengl_attrib_location(std::uint32_t program, const char* name) noexcept
{
    return static_cast<std::int32_t>(glGetAttribLocation(static_cast<GLuint>(program), name));
}

inline std::uint32_t create_opengl_shader(std::uint32_t stage) noexcept
{
    return static_cast<std::uint32_t>(glCreateShader(static_cast<GLenum>(stage)));
}

inline void set_opengl_shader_source(
    std::uint32_t shader,
    std::int32_t count,
    const char* const* sources) noexcept
{
    glShaderSource(
        static_cast<GLuint>(shader),
        static_cast<GLsizei>(count),
        reinterpret_cast<const GLchar* const*>(sources),
        nullptr);
}

inline void compile_opengl_shader(std::uint32_t shader) noexcept
{
    glCompileShader(static_cast<GLuint>(shader));
}

inline void query_opengl_shader_integer(
    std::uint32_t shader,
    std::uint32_t query,
    std::int32_t* value) noexcept
{
    glGetShaderiv(
        static_cast<GLuint>(shader),
        static_cast<GLenum>(query),
        reinterpret_cast<GLint*>(value));
}

inline void get_opengl_shader_info_log(
    std::uint32_t shader,
    std::int32_t capacity,
    std::int32_t* length,
    char* info_log) noexcept
{
    glGetShaderInfoLog(
        static_cast<GLuint>(shader),
        static_cast<GLsizei>(capacity),
        reinterpret_cast<GLsizei*>(length),
        reinterpret_cast<GLchar*>(info_log));
}

inline void delete_opengl_shader(std::uint32_t shader) noexcept
{
    glDeleteShader(static_cast<GLuint>(shader));
}

inline std::uint32_t create_opengl_program() noexcept
{
    return static_cast<std::uint32_t>(glCreateProgram());
}

inline void attach_opengl_shader(std::uint32_t program, std::uint32_t shader) noexcept
{
    glAttachShader(static_cast<GLuint>(program), static_cast<GLuint>(shader));
}

inline void link_opengl_program(std::uint32_t program) noexcept
{
    glLinkProgram(static_cast<GLuint>(program));
}

inline void bind_opengl_attrib_location(std::uint32_t program, std::uint32_t location, const char* name) noexcept
{
    glBindAttribLocation(static_cast<GLuint>(program), static_cast<GLuint>(location), name);
}

inline void query_opengl_program_integer(
    std::uint32_t program,
    std::uint32_t query,
    std::int32_t* value) noexcept
{
    glGetProgramiv(
        static_cast<GLuint>(program),
        static_cast<GLenum>(query),
        reinterpret_cast<GLint*>(value));
}

inline void get_opengl_program_info_log(
    std::uint32_t program,
    std::int32_t capacity,
    std::int32_t* length,
    char* info_log) noexcept
{
    glGetProgramInfoLog(
        static_cast<GLuint>(program),
        static_cast<GLsizei>(capacity),
        reinterpret_cast<GLsizei*>(length),
        reinterpret_cast<GLchar*>(info_log));
}

inline void get_opengl_active_uniform(
    std::uint32_t program,
    std::uint32_t index,
    std::int32_t capacity,
    std::int32_t* length,
    std::int32_t* size,
    std::uint32_t* type,
    char* name) noexcept
{
    glGetActiveUniform(
        static_cast<GLuint>(program),
        static_cast<GLuint>(index),
        static_cast<GLsizei>(capacity),
        reinterpret_cast<GLsizei*>(length),
        reinterpret_cast<GLint*>(size),
        reinterpret_cast<GLenum*>(type),
        reinterpret_cast<GLchar*>(name));
}

inline std::int32_t get_opengl_uniform_location(std::uint32_t program, const char* name) noexcept
{
    return static_cast<std::int32_t>(glGetUniformLocation(static_cast<GLuint>(program), name));
}

inline pp::renderer::gl::OpenGlProgramUseDispatch program_use_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlProgramUseDispatch {
        .use_program = use_opengl_program,
    };
}

inline pp::renderer::gl::OpenGlProgramDeleteDispatch program_delete_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlProgramDeleteDispatch {
        .use_program = use_opengl_program,
        .delete_program = delete_opengl_program,
    };
}

inline pp::renderer::gl::OpenGlUniformVec4Dispatch uniform_vec4_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlUniformVec4Dispatch {
        .uniform_4fv = set_opengl_uniform_4fv,
    };
}

inline pp::renderer::gl::OpenGlUniformVec3Dispatch uniform_vec3_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlUniformVec3Dispatch {
        .uniform_3fv = set_opengl_uniform_3fv,
    };
}

inline pp::renderer::gl::OpenGlUniformVec2Dispatch uniform_vec2_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlUniformVec2Dispatch {
        .uniform_2fv = set_opengl_uniform_2fv,
    };
}

inline pp::renderer::gl::OpenGlUniformMat4Dispatch uniform_mat4_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlUniformMat4Dispatch {
        .uniform_matrix_4fv = set_opengl_uniform_matrix_4fv,
    };
}

inline pp::renderer::gl::OpenGlUniformIntDispatch uniform_int_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlUniformIntDispatch {
        .uniform_1i = set_opengl_uniform_1i,
    };
}

inline pp::renderer::gl::OpenGlUniformFloatDispatch uniform_float_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlUniformFloatDispatch {
        .uniform_1f = set_opengl_uniform_1f,
    };
}

inline pp::renderer::gl::OpenGlAttributeLocationDispatch attribute_location_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlAttributeLocationDispatch {
        .get_attrib_location = get_opengl_attrib_location,
    };
}

inline pp::renderer::gl::OpenGlShaderCompileDispatch shader_compile_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlShaderCompileDispatch {
        .create_shader = create_opengl_shader,
        .shader_source = set_opengl_shader_source,
        .compile_shader = compile_opengl_shader,
        .get_shader_integer = query_opengl_shader_integer,
        .get_shader_info_log = get_opengl_shader_info_log,
    };
}

inline pp::renderer::gl::OpenGlShaderDeleteDispatch shader_delete_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlShaderDeleteDispatch {
        .delete_shader = delete_opengl_shader,
    };
}

inline pp::renderer::gl::OpenGlProgramLinkDispatch program_link_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlProgramLinkDispatch {
        .create_program = create_opengl_program,
        .attach_shader = attach_opengl_shader,
        .delete_shader = delete_opengl_shader,
        .link_program = link_opengl_program,
        .get_attrib_location = get_opengl_attrib_location,
        .bind_attrib_location = bind_opengl_attrib_location,
        .get_program_integer = query_opengl_program_integer,
        .get_program_info_log = get_opengl_program_info_log,
    };
}

inline pp::renderer::gl::OpenGlProgramIntegerDispatch program_integer_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlProgramIntegerDispatch {
        .get_program_integer = query_opengl_program_integer,
    };
}

inline pp::renderer::gl::OpenGlActiveUniformDispatch active_uniform_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlActiveUniformDispatch {
        .get_active_uniform = get_opengl_active_uniform,
    };
}

inline pp::renderer::gl::OpenGlUniformLocationDispatch uniform_location_dispatch() noexcept
{
    return pp::renderer::gl::OpenGlUniformLocationDispatch {
        .get_uniform_location = get_opengl_uniform_location,
    };
}

} // namespace pp::legacy::gl_shader
