#include "pch.h"
#include "log.h"
#include "texture.h"
#include "util.h"
#include "app.h"

std::map<uint16_t, Texture2D> TextureManager::m_textures;

bool TextureManager::load(const char* path, bool generate_mipmaps)
{
    uint16_t id = const_hash(path);
    auto t = m_textures.find(id);
    if (t == m_textures.end() || !m_textures[id].ready())
    {
        if (!m_textures[id].load(path))
            return false;
    }
    if (generate_mipmaps && !m_textures[id].has_mips)
        m_textures[id].create_mipmaps();
    return true;
}

void TextureManager::assign(uint16_t id, GLuint tex, int w/* = -1*/, int h/* = -1*/, GLuint internal_format/* = GL_RGBA8*/, GLuint format/* = GL_RGBA*/)
{
    m_textures[id].assign(tex, w, h, internal_format, format);
}

Texture2D& TextureManager::get(uint16_t id)
{
    return m_textures[id];
}

void TextureManager::invalidate()
{
    for (auto& t : m_textures)
    {
        t.second.destroy();
    }
    m_textures.clear();
}

bool Texture2D::create(int width, int height, GLint internal_format, GLint format, const uint8_t* data)
{
    destroy();
    m_width = width;
    m_height = height;
    m_format = format;
    m_iformat = internal_format;
    glGenTextures(1, &m_tex);
    //LOG("TEX create %d", m_tex);
    bind();
    auto ifmt = GL_UNSIGNED_BYTE;
    if (internal_format == GL_RGBA32F) ifmt = GL_FLOAT;
    if (internal_format == GL_RGBA16F) ifmt = GL_HALF_FLOAT;
    glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, ifmt, data);
    unbind();
    return true;
}
bool Texture2D::create(const Image& img)
{
    static GLint formats[] = { GL_RED, GL_RG, GL_RGB, GL_RGBA };
    static GLint iformats[] = { GL_R8, GL_RG8, GL_RGB8, GL_RGBA8 };
    return create(img.width, img.height, iformats[img.comp - 1], formats[img.comp - 1], img.data());
}

void Texture2D::create_mipmaps()
{
    bind();
    glGenerateMipmap(GL_TEXTURE_2D);
    unbind();
    has_mips = true;
}

void Texture2D::assign(GLuint tex, int w/* = -1*/, int h/* = -1*/, GLuint internal_format/* = GL_RGBA8*/, GLuint format/* = GL_RGBA*/)
{
    m_tex = tex;
    m_width = w;
    m_height = h;
    m_format = format;
    m_iformat = internal_format;
}

bool Texture2D::load(std::string filename)
{
    LOG("load texture %s", filename.c_str());
    Image img;
    if (!img.load(filename))
        return false;
    return create(img);
}

bool Texture2D::load_file(std::string filename)
{
    LOG("load texture %s", filename.c_str());
    Image img;
    if (!img.load_file(filename))
        return false;
    return create(img);
}

void Texture2D::update(const uint8_t* data)
{
    bind();
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, m_format, GL_UNSIGNED_BYTE, data);
}

glm::vec2 Texture2D::size() const
{
    return { m_width, m_height };
}

Texture2D::~Texture2D()
{
    if (auto_destroy)
    {
        LOG("Texture2D auto destroy");
        App::I.async_start();
        destroy();
        App::I.async_end();
    }
}

bool Sampler::create(GLint filter /*= GL_LINEAR*/, GLint wrap /*= GL_CLAMP_TO_EDGE*/)
{
#if USE_SAMPLER
    glGenSamplers(1, &id);
#endif // USE_SAMPLER
    if (id == 0)
        return false;
    set(filter, wrap);
    return true;
}
void Sampler::set(GLint filter /*= GL_LINEAR*/, GLint wrap /*= GL_CLAMP_TO_EDGE*/)
{
#if USE_SAMPLER
    glSamplerParameteri(id, GL_TEXTURE_WRAP_S, wrap);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_T, wrap);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_R, wrap);
    glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, filter);
    glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, filter);
#endif // USE_SAMPLER
}
void Sampler::set_filter(GLint filter_min, GLint filter_mag)
{
#if USE_SAMPLER
    glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, filter_min);
    glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, filter_mag);
#endif // USE_SAMPLER
}
void Sampler::set_border(glm::vec4 rgba)
{
#if USE_SAMPLER && !defined(__IOS__) && !defined(__ANDROID__)
    glSamplerParameterfv(id, GL_TEXTURE_BORDER_COLOR, glm::value_ptr(rgba));
#endif // USE_SAMPLER
}
void Sampler::bind(int unit) const
{
    current_unit = unit;
#if USE_SAMPLER
    glBindSampler(unit, id);
#endif // USE_SAMPLER
}
void Sampler::unbind()
{
#if USE_SAMPLER
    glBindSampler(current_unit, 0);
#endif // USE_SAMPLER
}

