#include "gl_texture_load.h"

#include "../../vendor/stb/stb_image.h"

#include <cstring>
#include <string>

namespace fn {

namespace {

// Error thread-local — cada thread mantiene su propio mensaje.
thread_local std::string g_last_error;

bool ends_with_ci(const char* s, const char* suffix) {
    if (!s || !suffix) return false;
    size_t ls = std::strlen(s);
    size_t lf = std::strlen(suffix);
    if (lf > ls) return false;
    const char* a = s + ls - lf;
    for (size_t i = 0; i < lf; i++) {
        char ca = a[i]; char cb = suffix[i];
        if (ca >= 'A' && ca <= 'Z') ca = char(ca - 'A' + 'a');
        if (cb >= 'A' && cb <= 'Z') cb = char(cb - 'A' + 'a');
        if (ca != cb) return false;
    }
    return true;
}

GLuint upload_ldr(const unsigned char* pixels, int w, int h, bool srgb) {
    GLuint id = 0;
    glGenTextures(1, &id);
    if (id == 0) return 0;
    glBindTexture(GL_TEXTURE_2D, id);
    GLint internal = srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8;
    glTexImage2D(GL_TEXTURE_2D, 0, internal, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glGenerateMipmap(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, 0);
    return id;
}

GLuint upload_hdr(const float* pixels, int w, int h) {
    GLuint id = 0;
    glGenTextures(1, &id);
    if (id == 0) return 0;
    glBindTexture(GL_TEXTURE_2D, id);
    // GL_RGBA16F + GL_FLOAT — GL 3.0+. En GL 2.1 no hay GL_RGBA16F; aceptamos
    // la limitacion (driver moderno).
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, w, h, 0, GL_RGBA, GL_FLOAT, pixels);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glGenerateMipmap(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, 0);
    return id;
}

void set_error(const char* msg) {
    g_last_error = msg ? msg : "unknown error";
}

} // namespace

GlTexture gl_texture_load(const char* path, bool flip_y, bool srgb) {
    GlTexture tex{};
    if (!path || !*path) {
        set_error("gl_texture_load: empty path");
        return tex;
    }

    stbi_set_flip_vertically_on_load(flip_y ? 1 : 0);

    if (ends_with_ci(path, ".hdr")) {
        int w = 0, h = 0, ch = 0;
        float* px = stbi_loadf(path, &w, &h, &ch, 4);
        if (!px) {
            set_error(stbi_failure_reason());
            return tex;
        }
        tex.id = upload_hdr(px, w, h);
        tex.w = w; tex.h = h; tex.channels = ch;
        stbi_image_free(px);
        if (tex.id == 0) set_error("glGenTextures returned 0");
        else g_last_error.clear();
        return tex;
    }

    int w = 0, h = 0, ch = 0;
    unsigned char* px = stbi_load(path, &w, &h, &ch, 4); // forzamos RGBA
    if (!px) {
        set_error(stbi_failure_reason());
        return tex;
    }
    tex.id = upload_ldr(px, w, h, srgb);
    tex.w = w; tex.h = h; tex.channels = ch;
    stbi_image_free(px);
    if (tex.id == 0) set_error("glGenTextures returned 0");
    else g_last_error.clear();
    return tex;
}

GlTexture gl_texture_load_from_memory(const unsigned char* data, int size,
                                      bool flip_y, bool srgb) {
    GlTexture tex{};
    if (!data || size <= 0) {
        set_error("gl_texture_load_from_memory: empty buffer");
        return tex;
    }
    stbi_set_flip_vertically_on_load(flip_y ? 1 : 0);

    int w = 0, h = 0, ch = 0;
    unsigned char* px = stbi_load_from_memory(data, size, &w, &h, &ch, 4);
    if (!px) {
        set_error(stbi_failure_reason());
        return tex;
    }
    tex.id = upload_ldr(px, w, h, srgb);
    tex.w = w; tex.h = h; tex.channels = ch;
    stbi_image_free(px);
    if (tex.id == 0) set_error("glGenTextures returned 0");
    else g_last_error.clear();
    return tex;
}

void gl_texture_destroy(GlTexture& tex) {
    if (tex.id != 0) {
        glDeleteTextures(1, &tex.id);
        tex.id = 0;
    }
    tex.w = tex.h = tex.channels = 0;
}

const char* gl_texture_last_error() {
    return g_last_error.c_str();
}

void gl_texture_bind_uniform(GLuint program, const char* name,
                             const GlTexture& tex, int unit) {
    if (program == 0 || !name || !tex.ok()) return;
    glActiveTexture(GL_TEXTURE0 + unit);
    glBindTexture(GL_TEXTURE_2D, tex.id);
    GLint loc = glGetUniformLocation(program, name);
    if (loc >= 0) glUniform1i(loc, unit);
}

} // namespace fn
