#include <cassert>
#include <string> // For memset

#include "FTGLTextureFont.h"
#include "FTTextureGlyph.h"


inline GLuint NextPowerOf2( GLuint in)
{
     in -= 1;

     in |= in >> 16;
     in |= in >> 8;
     in |= in >> 4;
     in |= in >> 2;
     in |= in >> 1;

     return in + 1;
}


FTGLTextureFont::FTGLTextureFont( const char* fontFilePath)
:   FTFont( fontFilePath),
    maximumGLTextureSize(0),
    textureWidth(0),
    textureHeight(0),
    glyphHeight(0),
    glyphWidth(0),
    padding(3),
    xOffset(0),
    yOffset(0)
{
    remGlyphs = numGlyphs = face.GlyphCount();
}


FTGLTextureFont::FTGLTextureFont( const unsigned char *pBufferBytes, size_t bufferSizeInBytes)
:   FTFont( pBufferBytes, bufferSizeInBytes),
    maximumGLTextureSize(0),
    textureWidth(0),
    textureHeight(0),
    glyphHeight(0),
    glyphWidth(0),
    padding(3),
    xOffset(0),
    yOffset(0)
{
    remGlyphs = numGlyphs = face.GlyphCount();
}


FTGLTextureFont::~FTGLTextureFont()
{
    glDeleteTextures( textureIDList.size(), static_cast<const GLuint*>(&textureIDList[0]));
}


FTGlyph* FTGLTextureFont::MakeGlyph( unsigned int glyphIndex)
{
    FT_GlyphSlot ftGlyph = face.Glyph( glyphIndex, FT_LOAD_NO_HINTING);
    
    if( ftGlyph)
    {
        glyphHeight = static_cast<int>( charSize.Height());
        glyphWidth = static_cast<int>( charSize.Width());
        
        if( textureIDList.empty())
        {
            textureIDList.push_back( CreateTexture());
            xOffset = yOffset = padding;
        }
        
        if( xOffset > ( textureWidth - glyphWidth))
        {
            xOffset = padding;
            yOffset += glyphHeight;
            
            if( yOffset > ( textureHeight - glyphHeight))
            {
                textureIDList.push_back( CreateTexture());
                yOffset = padding;
            }
        }
        
        FTTextureGlyph* tempGlyph = new FTTextureGlyph( ftGlyph, textureIDList[textureIDList.size() - 1],
                                                        xOffset, yOffset, textureWidth, textureHeight);
        xOffset += static_cast<int>( tempGlyph->BBox().upperX - tempGlyph->BBox().lowerX + padding);
        
        --remGlyphs;
        return tempGlyph;
    }
    
    err = face.Error();
    return NULL;
}


void FTGLTextureFont::CalculateTextureSize()
{
    if( !maximumGLTextureSize)
    {
        glGetIntegerv( GL_MAX_TEXTURE_SIZE, static_cast<GLint*>(&maximumGLTextureSize));
        assert(maximumGLTextureSize); // If you hit this then you have an invalid OpenGL context.
    }
    
    textureWidth = NextPowerOf2( (remGlyphs * glyphWidth) + ( padding * 2));
    textureWidth = textureWidth > maximumGLTextureSize ? maximumGLTextureSize : textureWidth;
    
    int h = static_cast<int>( (textureWidth - ( padding * 2)) / glyphWidth);
        
    textureHeight = NextPowerOf2( (( numGlyphs / h) + 1) * glyphHeight);
    textureHeight = textureHeight > maximumGLTextureSize ? maximumGLTextureSize : textureHeight;
}


GLuint FTGLTextureFont::CreateTexture()
{   
    CalculateTextureSize();
    
    int totalMemory = textureWidth * textureHeight;
    unsigned char* textureMemory = new unsigned char[totalMemory];
    memset( textureMemory, 0, totalMemory);

    GLuint textID;
    glGenTextures( 1, static_cast<GLuint*>(&textID));

    glBindTexture( GL_TEXTURE_2D, textID);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA, textureWidth, textureHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, textureMemory);

    delete [] textureMemory;

    return textID;
}


bool FTGLTextureFont::FaceSize( const unsigned int size, const unsigned int res)
{
    if( !textureIDList.empty())
    {
        glDeleteTextures( textureIDList.size(), static_cast<const GLuint*>(&textureIDList[0]));
        textureIDList.clear();
        remGlyphs = numGlyphs = face.GlyphCount();
    }

    return FTFont::FaceSize( size, res);
}


void FTGLTextureFont::Render( const char* string)
{   
    glPushAttrib( GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT);
    
    glEnable(GL_BLEND);
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // GL_ONE

    FTTextureGlyph::ResetActiveTexture();
    
    FTFont::Render( string);

    glPopAttrib();
}


void FTGLTextureFont::Render( const wchar_t* string)
{   
    glPushAttrib( GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT);
    
    glEnable(GL_BLEND);
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // GL_ONE
    
    FTTextureGlyph::ResetActiveTexture();
    
    FTFont::Render( string);
    
    glPopAttrib();
}
