/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
#include "Font.h"

#include <glm/gtc/matrix_transform.hpp>

using namespace std;

Font::Font()
{
    bLoaded = false;
}

inline int next_p2(int n){int res = 1; while(res < n)res <<= 1; return res;}

void Font::createChar(int iIndex)
{
    FT_Load_Glyph(mFTFace, FT_Get_Char_Index(mFTFace, iIndex), FT_LOAD_DEFAULT);

    FT_Render_Glyph(mFTFace->glyph, FT_RENDER_MODE_NORMAL);
    FT_Bitmap* pBitmap = &mFTFace->glyph->bitmap;

    int iW = pBitmap->width, iH = pBitmap->rows;
    int iTW = next_p2(iW), iTH = next_p2(iH);

    GLubyte* bData = new GLubyte[iTW*iTH];
    // Copy glyph data and add dark pixels elsewhere
    for(int ch =0;ch<iTH;ch++)
    {
        for (int cw =0;cw<iTW;cw++)
        {
            bData[ch*iTW+cw] = (ch >= iH || cw >= iW) ? 0 : pBitmap->buffer[(iH-ch-1)*iW+cw];
        }
    }

    // And create a texture from it

    mCharTextures[iIndex].createFromData(bData, iTW, iTH, GL_DEPTH_COMPONENT, false);
    mCharTextures[iIndex].setFiltering(TEXTURE_FILTER_MAG_BILINEAR, TEXTURE_FILTER_MIN_BILINEAR);

    mCharTextures[iIndex].setSamplerParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    mCharTextures[iIndex].setSamplerParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    // Calculate glyph data
    mAdvX[iIndex] = mFTFace->glyph->advance.x>>6;
    mBearingX[iIndex] = mFTFace->glyph->metrics.horiBearingX>>6;
    mCharWidth[iIndex] = mFTFace->glyph->metrics.width>>6;

    mAdvY[iIndex] = (mFTFace->glyph->metrics.height - mFTFace->glyph->metrics.horiBearingY)>>6;
    mBearingY[iIndex] = mFTFace->glyph->metrics.horiBearingY>>6;
    mCharHeight[iIndex] = mFTFace->glyph->metrics.height>>6;

    mNewLine = max(mNewLine, int(mFTFace->glyph->metrics.height>>6));

    // Rendering data, texture coordinates are always the same, so now we waste a little memory
    glm::vec2 vQuad[] =
    {
        glm::vec2(0.0f, float(-mAdvY[iIndex]+iTH)),
        glm::vec2(0.0f, float(-mAdvY[iIndex])),
        glm::vec2(float(iTW), float(-mAdvY[iIndex]+iTH)),
        glm::vec2(float(iTW), float(-mAdvY[iIndex]))
    };

    glm::vec2 vTexQuad[] = {glm::vec2(0.0f, 1.0f), glm::vec2(0.0f, 0.0f), glm::vec2(1.0f, 1.0f), glm::vec2(1.0f, 0.0f)};

    // Add this char to VBO
    for (int i =0; i<4;i++)
    {
        mDataBuffer.insert(mDataBuffer.end(), (unsigned char*)(&vQuad[i]), (unsigned char*)(&vQuad[i])+sizeof(glm::vec2));
        mDataBuffer.insert(mDataBuffer.end(), (unsigned char*)(&vTexQuad[i]), (unsigned char*)(&vTexQuad[i])+sizeof(glm::vec2));
    }
    delete[] bData;
}

bool Font::loadFont(string sFile, int iPXSize)
{
    FT_Error bError = FT_Init_FreeType(&mFTLib);

    bError = FT_New_Face(mFTLib, sFile.c_str(), 0, &mFTFace);
    if(bError)return false;
    FT_Set_Pixel_Sizes(mFTFace, iPXSize, iPXSize);
    mLoadedPixelSize = iPXSize;

    glGenVertexArrays(1, &mVAO);
    glBindVertexArray(mVAO);

    glGenBuffers(1,&mBuffer);
    glBindBuffer(GL_ARRAY_BUFFER,mBuffer);
    mDataBuffer.reserve(8192);
    mDataBufferSize = 0;

    for (int i=0;i<128;i++)
    {
        createChar(i);
    }
    bLoaded = true;

    FT_Done_Face(mFTFace);
    FT_Done_FreeType(mFTLib);

    glBufferData(GL_ARRAY_BUFFER, mDataBuffer.size(), &mDataBuffer[0], GL_STATIC_DRAW);
    mDataBuffer.clear();

    return true;
}

bool Font::LoadSystemFont(string sName, int iPXSize)
{
    char buf[512]; 
    string sPath = buf;
    sPath += "./";
    sPath += sName;

    return loadFont(sPath, iPXSize);
}

void Font::printString(string sText, int x, int y, int iPXSize)
{
    if(!bLoaded)return;
    glBindBuffer(GL_ARRAY_BUFFER,mBuffer);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec2)*2, 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec2)*2, (void*)(sizeof(glm::vec2)));
    GLuint iLoc = glGetUniformLocation(mShaderId, "gSampler");

    glUniform1i(iLoc, 0);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    int iCurX = x, iCurY = y;
    if(iPXSize == -1)iPXSize = mLoadedPixelSize;
    float fScale = float(iPXSize)/float(mLoadedPixelSize);
    for (unsigned int i = 0; i<sText.size();i++)
    {
        if(sText[i] == '\n')
        {
            iCurX = x;
            iCurY -= mNewLine*iPXSize/mLoadedPixelSize;
            continue;
        }
        int iIndex = int(sText[i]);
        iCurX += mBearingX[iIndex]*iPXSize/mLoadedPixelSize;
        if(sText[i] != ' ')
        {
            mCharTextures[iIndex].bindTexture(0);
            // glm::mat4 mModelView = glm::mat4(1.0f);
            glm::mat4 mModelView = glm::translate(glm::mat4(1.0f), glm::vec3(float(iCurX), float(iCurY), 0.0f));
            mModelView = glm::scale(mModelView, glm::vec3(fScale));
            GLuint iLoc = glGetUniformLocation(mShaderId, "matrices.modelViewMatrix");
            glUniformMatrix4fv(iLoc, 1, false, (GLfloat*)&mModelView);

            // Draw character
            glDrawArrays(GL_TRIANGLE_STRIP, iIndex*4, 4);
        }

        iCurX += (mAdvX[iIndex]-mBearingX[iIndex])*iPXSize/mLoadedPixelSize;
    }
    glDisable(GL_BLEND);
}

void Font::printFormattedString(int x, int y, int iPXSize, char* sText, ...)
{
    char buf[512];
    va_list ap;
    va_start(ap, sText);
#ifdef _WIN32
    vsprintf_s(buf, sText, ap);
#else
    vsnprintf(buf,sizeof(buf), sText, ap);
#endif
    va_end(ap);
    printString(buf, x, y, iPXSize);
}

void Font::deleteFont()
{
    for (int i=0;i<128;i++)
    {
        mCharTextures[i].deleteTexture();
    }
    glDeleteBuffers(1,&mBuffer);
    mDataBuffer.clear();
    glDeleteVertexArrays(1, &mVAO);
}

void Font::setShaderProgram(unsigned int shaderId)
{
    this->mShaderId = shaderId;
}