#include "GLTexture.hpp"
#include "gl_util.hpp"

#include <GL/glew.h>
#include <stdexcept>
#include <memory>

uint Texture::MAX_RESOLUTION = 1024; // Window.initialize overrides it

GLTexture::GLTexture(uint id, uint width, uint height) 
    : Texture(width, height), id(id) {
}

GLTexture::GLTexture(const ubyte* data, uint width, uint height, ImageFormat imageFormat) 
    : Texture(width, height) {
    glGenTextures(1, &id);
    glBindTexture(GL_TEXTURE_2D, id);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    GLenum format = gl::to_glenum(imageFormat);
    glTexImage2D(
        GL_TEXTURE_2D, 0, format, width, height, 0,
        format, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data)
    );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glGenerateMipmap(GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
    glBindTexture(GL_TEXTURE_2D, 0);
}

GLTexture::~GLTexture() {
    glDeleteTextures(1, &id);
}

void GLTexture::bind(){
    glBindTexture(GL_TEXTURE_2D, id);
}

void GLTexture::unbind() {
    glBindTexture(GL_TEXTURE_2D, 0);
}

void GLTexture::reload(const ubyte* data){
    glBindTexture(GL_TEXTURE_2D, id);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
        GL_RGBA, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data));
    glBindTexture(GL_TEXTURE_2D, 0);
}

std::unique_ptr<ImageData> GLTexture::readData() {
    auto data = std::make_unique<ubyte[]>(width * height * 4);
    glBindTexture(GL_TEXTURE_2D, id);
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.get());
    glBindTexture(GL_TEXTURE_2D, 0);
    return std::make_unique<ImageData>(ImageFormat::rgba8888, width, height, data.release());
}

void GLTexture::setNearestFilter() {
    bind();
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glBindTexture(GL_TEXTURE_2D, 0);
}

std::unique_ptr<GLTexture> GLTexture::from(const ImageData* image) {
    uint width = image->getWidth();
    uint height = image->getHeight();
    void* data = image->getData();
    return std::make_unique<GLTexture>(static_cast<ubyte*>(data), width, height, image->getFormat());
}

uint GLTexture::getId() const {
    return id;
}
