/******************************************************************************
*	File		-	texture.h
*	Author		-	Joey Pollack
*	Date		-	2022/07/14 (y/m/d)
*	Mod Date	-	2022/07/14 (y/m/d)
*	Description	-	OpenGL texture 2D
******************************************************************************/

#include "texture.h"
#include <glad/gl.h>
#include <utils/logger.h>

namespace lunarium
{
    Texture::Texture()
        : mGLID(0), mWidth(0), mHeight(0), mFormat(TextureFormat::RGBA)
    {

    }

    
    i32 Texture::GetMaxSize()
    {
        i32 max_size = 0;
        glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_size);
        return max_size;
    }

    Texture* Texture::Create(u8* data, u32 width, u32 height, TextureFormat format, bool clamp)
    {
        if (((i32)width) < 1 || ((i32)height) < 1)
        {
            Logger::Error(LogCategory::GRAPHICS, "Failed to create texture, width and/or height values invalid: %d, %d", width, height);
            return nullptr;
        }

        u32 formats[3] = { GL_RED, GL_RGB, GL_RGBA };
        Texture* t = new Texture();

        t->mFormat = format;
        t->mWidth = width;
        t->mHeight = height;

        glCreateTextures(GL_TEXTURE_2D, 1, &t->mGLID);
        t->Bind();
        
        glTexImage2D(GL_TEXTURE_2D, 0, formats[(int)format], width, height, 0, formats[(int)format], GL_UNSIGNED_BYTE, data);
        int error = glGetError();
        if (error > 0)
        {
            Logger::Error(LogCategory::GRAPHICS, "Error setting texture data: %d", error);
        }

        GLint param = clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT;

        glGenerateMipmap(GL_TEXTURE_2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, param);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, param);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        t->Unbind();
        return t;
    }
    
    void Texture::Destroy(Texture** ppTex)
    {
        if (!(*ppTex))
        {
            return;
        }

        (*ppTex)->Unbind();
        glDeleteTextures(1, &(*ppTex)->mGLID);
        delete (*ppTex);
        (*ppTex) = nullptr;
    }
    

    void Texture::Bind(u32 slot) const
    {
       // glBindTextureUnit(slot, mGLID); 
        glActiveTexture(slot + GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mGLID);
    }
    
    void Texture::Unbind() const
    {
        glBindTexture(GL_TEXTURE_2D, 0); 
    }

    
    void Texture::FlipVertically()
    {
        i32 components = NumComponents();

        u8* pixel_buffer = GetPixels();
        u8* flipped_buffer = new u8[mWidth * mHeight * components];

        for (int i = 0; i < mWidth; ++i)
        {
            for (int j = 0; j < mHeight; ++j)
            {
                for (int k = 0; k < components; ++k)
                {
                    flipped_buffer[(i + j * mWidth) * components + k] = pixel_buffer[(i + (mHeight - 1 - j) * mWidth) * components + k];
                }
            }
        }

        u32 formats[3] = { GL_RED, GL_RGB, GL_RGBA };
        Bind();
        glTexImage2D(GL_TEXTURE_2D, 0, formats[(int)mFormat], mWidth, mHeight, 0, formats[(int)mFormat], GL_UNSIGNED_BYTE, flipped_buffer);
        Unbind();
        int error = glGetError();
        if (error > 0)
        {
            Logger::Error(LogCategory::GRAPHICS, "Error setting texture data: %d", error);
        }
    }
    

    u32 Texture::GetGLID() const
    {
        return mGLID;
    }
    
    u64 Texture::GetGLID64() const
    {
        return (u64) mGLID;
    }

    TextureFormat Texture::GetFormat() const
    {
        return mFormat;
    }

    u32 Texture::GetWidth() const
    {
        return mWidth;
    }

    u32 Texture::GetHeight() const
    {
        return mHeight;
    }

    
    u8* Texture::GetPixels() const
    {
        u32 format_size[3] = { 1, 3, 4 };
        u32 formats[3] = { GL_RED, GL_RGB, GL_RGBA };
        u8* buffer = new u8[mWidth * mHeight * format_size[(int)mFormat]];

        Bind();
        glGetTexImage(GL_TEXTURE_2D, 0, formats[(int)mFormat], GL_UNSIGNED_BYTE, (void*)buffer);
        Unbind();

        return buffer;
    }

    i32 Texture::NumComponents() const
    {
        u32 components_size[3] = { 1, 3, 4 };
        return components_size[(int)mFormat];

    }

}