#pragma once

#include <glad/gl.h>

#include <glm/vec2.hpp>

#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <memory>

namespace kuiper
{

namespace gl
{

    enum class texture_type : std::uint8_t {
        none = 0,
        tex_2d
    };

    class texture {
      public:
        using s_ptr = std::shared_ptr<texture>;

        explicit texture(GLuint id, GLenum target)
            : m_id(id), m_target(target) {}
        ~texture() {
            destroy();
        }

        texture(const texture& other)                = delete;
        texture& operator=(const texture& other)     = delete;
        texture(texture&& other) noexcept            = default;
        texture& operator=(texture&& other) noexcept = default;

        /// Makes a new, uninitialised texture
        static s_ptr make(texture_type type = texture_type::tex_2d) noexcept;
        /// Makes a placeholder 4x4 texture
        static s_ptr make_placeholder() noexcept;
        /// Makes a texture from a path to an encoded image
        static s_ptr from_image_path(const std::filesystem::path& image_path, bool flip_y = true) noexcept;
        /// Makes a texture from a pointer to encoded image data
        static s_ptr from_image_data(const std::uint8_t* buffer, std::size_t length, bool flip_y = true) noexcept;

      public:
        /// Explicitly delete the OpenGL texture object
        void destroy() noexcept;
        /// Bind the texture
        void bind() noexcept;
        /// Upload image data to the texture object on the GPU
        void upload(
            GLint internal_fmt, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* data) noexcept;
        /// Resize underlying texture buffer
        void resize(std::uint32_t width, std::uint32_t height) noexcept;
        /// Set a texture parameter
        void set_param(GLenum name, GLint param) noexcept;
        /// Generate texture mipmaps
        void gen_mipmaps() noexcept;

        /// Get the dimensions of the texture
        glm::uvec2 dimensions() const noexcept {
            return {m_width, m_height};
        }

        GLint internal_format() const noexcept {
            return m_internal_fmt;
        }

        GLenum pixel_format() const noexcept {
            return m_pixel_fmt;
        }

        GLenum pixel_type() const noexcept {
            return m_pixel_type;
        }

        /// Get the internal OpenGL object name
        GLuint id() const noexcept {
            return m_id;
        }

      private:
        GLuint        m_id {0};
        GLenum        m_target {0};
        std::uint32_t m_width {0};
        std::uint32_t m_height {0};
        GLint         m_internal_fmt {0};
        GLenum        m_pixel_fmt {0};
        GLenum        m_pixel_type {0};
    };

} // namespace gl

} // namespace kuiper
