/*
 * This source file is part of KEOS (Free 3D Engine)
 * For the latest info, see http://www.keosengine.org/
 * E-mails : thierry.vouriot@keosengine.org, yeri@keosengine.org
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA, or go to
 * http://www.gnu.org/copyleft/lesser.txt.
 *
 */

#include "KeosFontManager.h"
#include "KeosLogger.h"
#include "KeosRenderSystem.h"
#include "KeosColor.h"
#include "KeosGraphicString.h"
#include "KeosMediaManager.h"
#include <ft2build.h>
#include FT_FREETYPE_H

namespace Keos
{

    //=======================================================================
    // CFontManager implementation
    //=======================================================================

    //-----------------------------------------------------------------------
    CFontManager::CFontManager()
    {

    }

    //-----------------------------------------------------------------------
    CFontManager::~CFontManager()
    {
        UnloadFonts();
    }

    //-----------------------------------------------------------------------
    void CFontManager::Initialize()
    {
        // Vertex declaration
        TDeclarationElement Decl[] =
        {
            {0, ELT_USAGE_POSITION,  ELT_TYPE_FLOAT3},
            {0, ELT_USAGE_DIFFUSE,   ELT_TYPE_COLOR},
            {0, ELT_USAGE_TEXCOORD0, ELT_TYPE_FLOAT2}
        };
        m_Declaration = Renderer->CreateVertexDeclaration(Decl);

        // Vertex buffer
        m_VertexBuffer = Renderer->CreateVertexBuffer<TVertex>(NbCharMax * 4, BUF_DYNAMIC);

        // Index buffer
        std::vector<TIndex> Indices;
        for (TIndex i = 0; i < NbCharMax; ++i)
        {
            Indices.push_back(i * 4 + 0);
            Indices.push_back(i * 4 + 1);
            Indices.push_back(i * 4 + 2);
            Indices.push_back(i * 4 + 2);
            Indices.push_back(i * 4 + 1);
            Indices.push_back(i * 4 + 3);
        }
        m_IndexBuffer = Renderer->CreateIndexBuffer((int)Indices.size(), 0, &Indices[0]);
    }

    //-----------------------------------------------------------------------
    void CFontManager::LoadFont(const String& strFontFile, const String& strFontName, int nQuality)
    {
        // Log
        ILogger::Log("Load of the font (size=%d): %s", nQuality, strFontFile.c_str());

        // Search the file in the search paths of the MediaManager
        CFile FontFile = CMediaManager::Instance().FindMedia(CFile(strFontFile));

        uint nFontSize = nQuality;
 
        // These are the characters that get stored. The last one ('\xFF')
        // indicates the picture used to draw 'unknown' characters.
        const String strChars(  "abcdefghijklmnopqrstuvwxyz"
                                "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                                "1234567890~!@#$%^&*()-=+;:"
                                "'\",./?[]|\\ <>`\xFF");
        // Margins around characters to prevent them from 'bleeding' into
        // each other.
        const uint nMargin = 3;
        uint nImageHeight = 0, nImageWidth = 256;

        // This initializes FreeType
        FT_Library library;
        if (FT_Init_FreeType(&library) != 0)
            LOADINGFAILED_EXCEPT(strFontFile, "FreeType2 error : Could not initialize FreeType2 library","CFontManager::LoadFont");
           
        // Load the font
        FT_Face face;
        if (FT_New_Face(library, FontFile.Fullname().c_str(), 0, &face) != 0)
            LOADINGFAILED_EXCEPT(strFontFile, "FreeType2 error : Could not load font file","CFontManager::LoadFont");

        // Abort if this is not a 'true type', scalable font.
        if (!(face->face_flags & FT_FACE_FLAG_SCALABLE) ||
            !(face->face_flags & FT_FACE_FLAG_HORIZONTAL))
            LOADINGFAILED_EXCEPT(strFontFile, "FreeType2 error : Error setting font size","CFontManager::LoadFont");

        // Set the font size
        FT_Set_Pixel_Sizes(face, (FT_UInt)nFontSize, 0);

        // First we go over all the characters to find the max descent
        // and ascent (space required above and below the base of a
        // line of text) and needed image size. There are simpler methods
        // to obtain these with FreeType but they are unreliable.
        int nMaxDescent = 0, nMaxAscent = 0;
        uint nSpaceOnLine = nImageWidth - nMargin, nLines = 1;

        for (uint i = 0; i < strChars.size(); ++i)
        {
            // Look up the character in the font file.
            uint nCharIndex = FT_Get_Char_Index(face, static_cast<unsigned int>(strChars[i]));
            if (strChars[i] == '\xFF')
                nCharIndex = 0;

            // Render the current glyph.
            FT_Load_Glyph(face, (FT_UInt)nCharIndex, FT_LOAD_DEFAULT);
            FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);

            uint nAdvance = (face->glyph->metrics.horiAdvance >> 6) + nMargin;
            // If the line is full go to the next line
            if (nAdvance > nSpaceOnLine){
                nSpaceOnLine = nImageWidth - nMargin;
                ++nLines;
            }
            nSpaceOnLine -= nAdvance;

            nMaxAscent = std::max(face->glyph->bitmap_top, nMaxAscent);
            nMaxDescent = std::max(face->glyph->bitmap.rows -
                           face->glyph->bitmap_top, nMaxDescent);
        }

        m_Fonts[strFontName].LineHeight = nMaxAscent + nMaxDescent;

        // Compute how high the texture has to be.
        uint nNeededImageHeight = (nMaxAscent + nMaxDescent + nMargin) * nLines + nMargin;
        // Get the first power of two in which it fits.
        nImageHeight = 16;
        while (nImageHeight < nNeededImageHeight)
            nImageHeight *= 2;

        // Allocate memory for the texture, and set it to 0
        unsigned char* pImage = new unsigned char[nImageHeight * nImageWidth];
        for (uint i = 0; i < nImageHeight * nImageWidth; ++i)
            pImage[i] = 0;

        m_Fonts[strFontName].TextureLineHeight = static_cast<float>(m_Fonts[strFontName].LineHeight) / nImageHeight;

        // Make the glyph table.
        m_Fonts[strFontName].Glyphs = new Glyph[strChars.size()];
        for (uint i = 0; i != 256; ++i)
            m_Fonts[strFontName].GlyphsTable[i] = NULL;

        // These are the position at which to draw the next glyph
        uint x = nMargin, y = nMargin + nMaxAscent;

        // Drawing loop
        for (uint i = 0; i < strChars.size(); ++i)
        {
            uint nCharIndex = FT_Get_Char_Index(face, static_cast<unsigned int>(strChars[i]));
            if (strChars[i] == '\xFF')
                nCharIndex = 0;

            // Render the glyph
            FT_Load_Glyph(face, (FT_UInt)nCharIndex, FT_LOAD_DEFAULT);
            FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);

            // See whether the character fits on the current line
            uint nAdvance = (face->glyph->metrics.horiAdvance >> 6) + nMargin;
            if (nAdvance > nImageWidth - x)
            {
                x = nMargin;
                y += (nMaxAscent + nMaxDescent + nMargin);
            }

            m_Fonts[strFontName].Glyphs[i].Tex_x1 = static_cast<float>(x) / nImageWidth;
            m_Fonts[strFontName].Glyphs[i].Tex_x2 = static_cast<float>(x + (nAdvance - nMargin)) / nImageWidth;
            m_Fonts[strFontName].Glyphs[i].Tex_y1 = static_cast<float>(y - nMaxAscent) / nImageHeight;
            m_Fonts[strFontName].Glyphs[i].Advance = (nAdvance - nMargin);

            m_Fonts[strFontName].GlyphsTable[(unsigned char)strChars[i]] = m_Fonts[strFontName].Glyphs + i;

            // Copy the image gotten from FreeType onto the texture
            // at the correct position
            for (int nRow = 0; nRow < face->glyph->bitmap.rows; ++nRow)
            {
                for (int nPixel = 0; nPixel < face->glyph->bitmap.width; ++nPixel)
                {
                    pImage[(x + face->glyph->bitmap_left + nPixel) +
                        (y - face->glyph->bitmap_top + nRow) * nImageWidth] =
                    face->glyph->bitmap.buffer[nPixel + nRow * face->glyph->bitmap.pitch];
                }
            }
    
            x += nAdvance;    
        }

        // All chars that do not have their own glyph are set to point to
        // the default glyph.
        Glyph* pDefaultGlyph = m_Fonts[strFontName].GlyphsTable[(unsigned char)'\xFF'];
        // We must have the default character (stored under '\xFF')
        if (pDefaultGlyph == NULL)
            throw std::runtime_error("Font file contains no default glyph");
        for (uint i = 0; i != 256; ++i){
            if (m_Fonts[strFontName].GlyphsTable[i] == NULL)
            m_Fonts[strFontName].GlyphsTable[i] = pDefaultGlyph;
        }

        CImage ImgFont(TVector2I(nImageWidth, nImageHeight), PXF_L8, pImage);
        delete[] pImage;

        //CMediaManager::Instance().SaveMediaToFile(&ImgFont, "test.bmp");

        // Create image in PXF_A8L8 format
        CImage ImgFont2(TVector2I(nImageWidth, nImageHeight), PXF_A8L8);
        for (uint i = 0; i < nImageWidth; ++i)
            for (uint j = 0; j < nImageHeight; ++j)
            {
                unsigned char CurPixel[1];
                ImgFont.GetPixel(i, j, CurPixel);
                ImgFont2.SetPixel(i, j, CColor(CurPixel[0], CurPixel[0], CurPixel[0], CurPixel[0]));
            }
		
        // Texture creation
        m_Fonts[strFontName].Texture.CreateFromImage(ImgFont2, PXF_A8L8, TEX_NOMIPMAP, strFontName);

        FT_Done_FreeType(library);   
    }

    //-----------------------------------------------------------------------
    void CFontManager::UnloadFonts()
    {
        for(
            TFontsMap::iterator it = m_Fonts.begin();
            it != m_Fonts.end();
            ++it )
        {
            delete[] it->second.Glyphs;
        }
        m_Fonts.clear();
    }

    //-----------------------------------------------------------------------
    void CFontManager::DrawString(const CGraphicString& String)
    {
        // Empty text
        if (String.Text == "")
            return;

        // Initilialize render part
        if (!m_Declaration)
            Initialize();
		
        if (m_Fonts.find(String.Font) == m_Fonts.end())
			LOADINGFAILED_EXCEPT(String.Font, "This font has not been loaded with LoadFont function", "CFontManager::GetFont");

        // Get the font
        const TFont& CurFont = m_Fonts[String.Font];

        // Variables
        float         x       = static_cast<float>(String.Position.x);
        float         y       = static_cast<float>(String.Position.y);
        unsigned long Color   = Renderer->ConvertColor(String.Color);

        // Lock vertex buffer
        TVertex* Vertices = m_VertexBuffer.Lock(0, 0, LOCK_WRITEONLY);

        // For each character
        int nbChars = 0;
        for (String::const_iterator i = String.Text.begin(); (i != String.Text.end()) && (nbChars < NbCharMax); ++i)
        {
            unsigned char c = *i;
            Glyph* pGlyph = CurFont.GlyphsTable[c];

            // Specials characters
            switch (c)
            {
                case '\n' :
                    x  = static_cast<float>(String.Position.x);
                    y += CurFont.LineHeight;
                    continue;

                case '\r' :
                    x = static_cast<float>(String.Position.x);
                    continue;

                case '\t' :
                    x += 4 * CurFont.GlyphsTable[' ']->Advance;
                    continue;

                // Vertical tabulation
                case '\v' :
                    y += 4 * CurFont.LineHeight;
                    continue;

                case ' ' :
                    x += CurFont.GlyphsTable[' ']->Advance;
                    continue;
            }

            // Position
            Vertices[nbChars * 4 + 0].Position.Set(x, y, 0);
            Vertices[nbChars * 4 + 1].Position.Set(x + pGlyph->Advance, y, 0);
            Vertices[nbChars * 4 + 2].Position.Set(x, y + CurFont.LineHeight, 0);
            Vertices[nbChars * 4 + 3].Position.Set(x + pGlyph->Advance, y + CurFont.LineHeight, 0);
            
            // Color
            Vertices[nbChars * 4 + 0].Diffuse = Color;
            Vertices[nbChars * 4 + 1].Diffuse = Color;
            Vertices[nbChars * 4 + 2].Diffuse = Color;
            Vertices[nbChars * 4 + 3].Diffuse = Color;

            // Texture coordinates
            Vertices[nbChars * 4 + 0].TexCoords.Set(pGlyph->Tex_x1, pGlyph->Tex_y1);
            Vertices[nbChars * 4 + 1].TexCoords.Set(pGlyph->Tex_x2, pGlyph->Tex_y1);
            Vertices[nbChars * 4 + 2].TexCoords.Set(pGlyph->Tex_x1, pGlyph->Tex_y1 + CurFont.TextureLineHeight);
            Vertices[nbChars * 4 + 3].TexCoords.Set(pGlyph->Tex_x2, pGlyph->Tex_y1 + CurFont.TextureLineHeight);

            x += pGlyph->Advance;
            ++nbChars;
        }

        // Unlock vertex buffer
        m_VertexBuffer.Unlock();

        // Render setup
        //Renderer->SetupAlphaBlending(BLEND_SRCCOLOR, BLEND_INVSRCCOLOR);
        Renderer->SetupAlphaBlending(BLEND_SRCALPHA, BLEND_INVSRCALPHA);
        Renderer->SetupTextureUnit(0, TXO_COLOR_MODULATE, TXA_TEXTURE, TXA_DIFFUSE);
        Renderer->SetupTextureUnit(0, TXO_ALPHA_MODULATE, TXA_TEXTURE, TXA_DIFFUSE);
        Renderer->Enable(RENDER_ALPHABLEND, true);
        Renderer->Enable(RENDER_ZWRITE, false);

        // Display text
        Renderer->SetDeclaration(m_Declaration);
        Renderer->SetTexture(0, CurFont.Texture.GetTexture());
        Renderer->SetVertexBuffer(0, m_VertexBuffer);
        Renderer->SetIndexBuffer(m_IndexBuffer);
        Renderer->DrawIndexedPrimitives(PT_TRIANGLELIST, 0, nbChars * 2);
        Renderer->SetTexture(0, NULL);

        // Restoring render options
        Renderer->Enable(RENDER_ZWRITE, true);
        Renderer->Enable(RENDER_ALPHABLEND, false);
    }

    //-----------------------------------------------------------------------
    TVector2I CFontManager::GetStringPixelSize(const CGraphicString& String)
    {
		// Empty text
        if (String.Text == "")
            return TVector2I(0, 0);

        if (m_Fonts.find(String.Font) == m_Fonts.end())
			LOADINGFAILED_EXCEPT(String.Font, "This font has not been loaded with LoadFont function", "CFontManager::GetFont");

        // Get the font
        const TFont& CurFont = m_Fonts[String.Font];

        // Vector containing the length of every line
        std::vector<int> Lengths;

        // Return value
        TVector2I Size(0, 0);

        unsigned char c;

        // For each character
        for (std::string::const_iterator i = String.Text.begin(); i != String.Text.end(); ++i)
        {
            c = *i;

            switch (c)
            {
                case '\n' :
                    Lengths.push_back(Size.x);
                    Size.y += (int)CurFont.LineHeight;
                    Size.x = 0;
                    break;

                case '\r' :
                    Lengths.push_back(Size.x);
                    Size.x = 0;
                    break;

                case '\t' :
                    Size.x += (int)(4 * CurFont.GlyphsTable[' ']->Advance);
                    break;

                // Vertical tabulation
                case '\v' :
                    Size.y += (int)(4 * CurFont.LineHeight);
                    break;

                default :
                    Size.x += (int)CurFont.GlyphsTable[c]->Advance;
                    break;
            }
        }

        if (c != '\n' && c != '\v' && c != '\r')
            Size.y += (int)CurFont.LineHeight;

        Lengths.push_back(Size.x);
        Size.x = *std::max_element(Lengths.begin(), Lengths.end());

        return Size;
    }

	//-----------------------------------------------------------------------
	size_t CFontManager::GetFontHeight(const String& strFontName)
	{
		if (m_Fonts.find(strFontName) == m_Fonts.end())
			LOADINGFAILED_EXCEPT(strFontName, "This font has not been loaded with LoadFont function", "CFontManager::GetFont");

		// Get the font
		return m_Fonts[strFontName].LineHeight;
	}

} // namespace Keos
