//	FreetypeFontAtlas.cpp - Uses freetype to generate a font atlas.
//  ----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2012  Niall Moody
//	
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 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 General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#ifdef WIN32
//So we can use std::copy without MSVC complaining, even though there's no
//danger in what we're doing.
#pragma warning(push)
#pragma warning(disable : 4996)
#endif

#include "FreetypeFontAtlas.h"
#include "DebugHeaders.h"
#include "MiscHelper.h"
#include "UTF8File.h"

#include <algorithm>
#include <sstream>

#ifdef WIN32
#pragma warning(pop)
#endif

using std::wstringstream;
using std::make_pair;
using std::wstring;
using std::string;
using std::copy;

//------------------------------------------------------------------------------
FreetypeFontAtlas::Creator FreetypeFontAtlas::creator;

//------------------------------------------------------------------------------
FreetypeFontAtlas::FreetypeFontAtlas(const FilePath& fontFile,
									 float fontSize,
									 FontMetrics& metrics,
									 const wstring& instanceName):
name(instanceName),
atlasSize(0),
imageData(0),
imageDataSize(0)
{
	int i;
	int x, y;
	FT_Error err;
	string narrowPath;
	wstring atlasName(fontFile.getName());
	FT_Library& lib = FreetypeSingleton::getLibrary();

	DebugStatement(L"FreetypeFontAtlas: Initialising atlas for " << fontFile.getPath());

	//Load font face.
	UTF8File::wstringToChar(fontFile.getPath(), narrowPath);
	err = FT_New_Face(lib, narrowPath.c_str(), 0, &fontFace);
	if(err == FT_Err_Unknown_File_Format)
	{
		DebugStatement(L"FreetypeFontAtlas: " << fontFile.getPath() << " is of an unknown format.");
	}
	else if(err)
	{
		DebugStatement(L"FreetypeFontAtlas: Error loading font face: " << fontFile.getPath() << "; error code = " << err);
	}
	else
		DebugStatement(L"FreetypeFontAtlas: Loaded font face.");

	if(!err)
	{
		//Set the font size.
		err = FT_Set_Char_Size(fontFace,
							   0,
							   (FT_F26Dot6)(fontSize*64.0f),
							   0,
							   0); //FT_Set_Char_Size(face, width in points, height in points, dpi width, dpi height)
		if(err)
		{
			DebugStatement(L"FreetypeFontAtlas: Error setting char size: " << fontFile.getPath() << "; error code = " << err);
		}
		else
		{
			//These are the glyphs we load by default.
			const wchar_t initialGlyphs [] = L" !\"#£$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
			const int initialSize = 99; //****UPDATE THIS IF THE ABOVE STRING CHANGES****
			int maxWidth, maxHeight;
			int numFontsPerLine;

			DebugStatement(L"FreetypeFontAtlas: Character size set to " << (fontSize*64.0f));

			//Setup images.
			images = new AtlasImage[initialSize];
			numImages = initialSize;

			//Calculate the size of the texture.
			{
				int tempint;
				float tempf;

				maxWidth = 0;
				maxHeight = 0;

				//Find the max size of a glyph.
				for(i=0;i<initialSize;++i)
				{
					err = FT_Load_Char(fontFace, initialGlyphs[i], FT_LOAD_DEFAULT);
					if(err)
						DebugStatement(L"FreetypeFontAtlas: Could not load char: " << initialGlyphs[i]);

					if((fontFace->glyph->metrics.horiAdvance/64) > (fontFace->glyph->metrics.width/64))
						tempint = (fontFace->glyph->metrics.horiAdvance/64) + 1; //+1 to leave a pixel gap between glyphs.
					else
						tempint = (fontFace->glyph->metrics.width/64) + 1;
					if(tempint > maxWidth)
						maxWidth = tempint;

					if((fontFace->glyph->metrics.height/64) > maxHeight)
						maxHeight = (fontFace->glyph->metrics.height/64) + 1;
				}
				lineHeight = maxHeight;

				//Calculate the size of the texture atlas.
				tempf = sqrtf((float)initialSize);
				numFontsPerLine = (int)tempf+1;
				atlasSize = (numFontsPerLine*((maxWidth > maxHeight) ? maxWidth : maxHeight));
				atlasSize = powerOfTwo(atlasSize);

				DebugStatement("FreetypeFontAtlas: Font " << fontFile.getNameWithExtension() << ":" << fontSize << " atlasSize = " << atlasSize);
				if(atlasSize > 1024)
					DebugStatement("FreetypeFontAtlas: Warning: Font atlas dimensions greater than 1024: (" << atlasSize << ")");
			}

			//Create the blank image data.
			{
				imageDataSize = atlasSize*atlasSize*4;
				imageData = new unsigned char[imageDataSize];

				//Blank atlasData.
				for(i=0;i<imageDataSize;i+=4)
				{
					imageData[i] = 0;
					imageData[i+1] = 0;
					imageData[i+2] = 0;
					imageData[i+3] = 0;
				}
			}

			//Copy the glyphs into the image data array.
			{
				x = 0;
				y = 0;

				for(i=0;i<initialSize;++i)
				{
					wstringstream tempstr;

					//This loads the glyph and renders it.
					err = FT_Load_Char(fontFace, initialGlyphs[i], FT_LOAD_RENDER);
					if(err)
					{
						//Ignore this, because we'd have generated the same error above.
						//DebugStatement(L"FreetypeFontAtlas: Could not load char: " << initialGlyphs[i]);
					}

					//Copy the glyph into our atlas image.
					drawFTBitmap(fontFace->glyph->bitmap, x, y);

					//Fill out the glyph's AtlasImage.
					tempstr << instanceName << initialGlyphs[i];
					images[i].name = tempstr.str();
					images[i].atlasName = atlasName;
					images[i].position.x = (float)x/(float)atlasSize;
					images[i].position.y = (float)y/(float)atlasSize;
					images[i].size.x = (float)(fontFace->glyph->metrics.width/64)/(float)atlasSize;
					images[i].size.y = (float)(fontFace->glyph->metrics.height/64)/(float)atlasSize;

					//Add a GlyphMetrics for this glyph.
					{
						GlyphMetrics glyphMetrics;

						glyphMetrics.image = images[i].name;
						glyphMetrics.width = (float)fontFace->glyph->metrics.width/64;
						glyphMetrics.xOffset = (float)fontFace->glyph->metrics.horiBearingX/64;
						glyphMetrics.advance = (float)fontFace->glyph->metrics.horiAdvance/64;
						//glyphMetrics.yBearing = (float)fontFace->glyph->metrics.horiBearingY/64;
						glyphMetrics.yBearing = (float)fontFace->glyph->bitmap_top;
						//glyphMetrics.lineSkip = (float)fontFace->height/64;
						glyphMetrics.lineSkip = (float)lineHeight;

						metrics.insert(make_pair(initialGlyphs[i], glyphMetrics));
					}

					x += maxWidth;
					if((x+maxWidth) >= atlasSize)
					{
						x = 0;
						y += maxHeight;
					}
					glyphsEnd.x = (float)x;
					glyphsEnd.y = (float)y;
				}
			}

			//...and finally inform our base TextureAtlas of our bits and pieces.
			initialise((const char *)imageData, imageDataSize, numImages, images);

			DebugStatement(L"FreetypeFontAtlas: Atlas initialised.");
		}
	}
}



//------------------------------------------------------------------------------
FreetypeFontAtlas::~FreetypeFontAtlas()
{
	//We do not delete imageData because it will be deleted in
	//Drawer::loadTextureAtlas()
	//delete [] imageData;
	delete [] images;
}

//------------------------------------------------------------------------------
void FreetypeFontAtlas::getMissingGlyphData(wchar_t missingCharacter,
											FontMetrics& metrics,
											AtlasImage **atlasImage,
											unsigned char **imageData,
											int& imageWidth,
											int& imageHeight,
											bool& needNewAtlas)
{
	int i;
	FT_Error err;
	wstringstream tempstr;
	int glyphWidth, glyphHeight;

	DebugStatement(L"FreetypeFontAtlas: Loading missing glyph: " << missingCharacter);

	needNewAtlas = false;

	//Get the glyph's dimensions.
	err = FT_Load_Char(fontFace, missingCharacter, FT_LOAD_DEFAULT);
	if(err)
	{
		DebugStatement(L"FreetypeFontAtlas: Could not load char: " << missingCharacter);

		*atlasImage = 0;
		*imageData = 0;
		imageWidth = 0;
		imageHeight = 0;
	}
	else
	{
		if((fontFace->glyph->metrics.horiAdvance/64) > (fontFace->glyph->metrics.width/64))
			glyphWidth = (fontFace->glyph->metrics.horiAdvance/64) + 1; //+1 to leave a pixel gap between glyphs.
		else
			glyphWidth = (fontFace->glyph->metrics.width/64) + 1;
		glyphHeight = (fontFace->glyph->metrics.height/64);
		if((glyphHeight + 1) > lineHeight)
			lineHeight = glyphHeight + 1;

		//Determine whether we need to add a new texture atlas to accommodate the
		//glyph.
		if(((int)glyphsEnd.x + glyphWidth) > atlasSize)
		{
			glyphsEnd.x = (float)glyphWidth;
			glyphsEnd.y += lineHeight;
			if(((int)glyphsEnd.y + lineHeight) > atlasSize)
			{
				glyphsEnd.y = 0.0f;
				needNewAtlas = true;
			}
		}
		else
			glyphsEnd.x += glyphWidth;

		if(needNewAtlas)
		{
			imageWidth = atlasSize;
			imageHeight = atlasSize;
		}
		else
		{
			imageWidth = (fontFace->glyph->metrics.width/64);
			imageHeight = glyphHeight;
		}

		//Allocate memory for the imageData buffer.
		*imageData = new unsigned char[imageWidth * imageHeight * 4];

		//Blank imageData.
		for(i=0;i<(imageWidth*imageHeight*4);i+=4)
		{
			(*imageData)[i] = 0;
			(*imageData)[i+1] = 0;
			(*imageData)[i+2] = 0;
			(*imageData)[i+3] = 0;
		}

		//This loads the glyph and renders it.
		err = FT_Load_Char(fontFace, missingCharacter, FT_LOAD_RENDER);
		if(err)
		{
			//Ignore this, because we'd have generated the same error above.
			//DebugStatement(L"FreetypeFontAtlas: Could not load char: " << initialGlyphs[i]);
		}

		//Copy the glyph into our atlas image.
		drawFTBitmap(fontFace->glyph->bitmap, *imageData, (imageWidth*4));

		//Resize images so it can hold the new glyph.
		++numImages;
		{
			AtlasImage *newImages = new AtlasImage[numImages];

			copy(images, &(images[numImages-1]), newImages);

			delete [] images;
			images = newImages;
		}

		//Fill out the glyph's AtlasImage.
		tempstr << name << missingCharacter;
		images[numImages-1].name = tempstr.str();
		images[numImages-1].atlasName = name;
		images[numImages-1].position.x = (glyphsEnd.x-(float)glyphWidth)/(float)atlasSize;
		images[numImages-1].position.y = glyphsEnd.y/(float)atlasSize;
		images[numImages-1].size.x = (float)(fontFace->glyph->metrics.width/64)/(float)atlasSize;
		images[numImages-1].size.y = (float)(fontFace->glyph->metrics.height/64)/(float)atlasSize;

		*atlasImage = &(images[numImages-1]);

		//Add a GlyphMetrics for this glyph.
		{
			GlyphMetrics glyphMetrics;

			glyphMetrics.image = (*atlasImage)->name;
			glyphMetrics.width = (float)fontFace->glyph->metrics.width/64;
			glyphMetrics.xOffset = (float)fontFace->glyph->metrics.horiBearingX/64;
			glyphMetrics.advance = (float)fontFace->glyph->metrics.horiAdvance/64;
			glyphMetrics.yBearing = (float)fontFace->glyph->bitmap_top;
			glyphMetrics.lineSkip = (float)lineHeight;

			metrics.insert(make_pair(missingCharacter, glyphMetrics));
		}

		DebugStatement(L"FreetypeFontAtlas: Missing glyph loaded.");
	}
}

//------------------------------------------------------------------------------
void FreetypeFontAtlas::drawFTBitmap(FT_Bitmap& bitmap, int x, int y)
{
	int i, j;
	int k, l;
	uint8_t tempPixel;
	int startX, startY;

	startX = x * 4;
	startY = y*(atlasSize*4);
	for(j=0,l=startY;j<bitmap.rows;++j,l+=(atlasSize*4))
	{
		for(i=0,k=startX;i<bitmap.width;++i,k+=4)
		{
			tempPixel = bitmap.buffer[(j*bitmap.pitch)+i];

			imageData[l+k] = tempPixel;
			imageData[l+k+1] = tempPixel;
			imageData[l+k+2] = tempPixel;
			imageData[l+k+3] = tempPixel;
		}
	}
}

//------------------------------------------------------------------------------
void FreetypeFontAtlas::drawFTBitmap(FT_Bitmap& bitmap,
									 unsigned char *buffer,
									 int bufferPitch)
{
	int i, j;
	int k, l;
	uint8_t tempPixel;

	for(j=0,l=0;j<bitmap.rows;++j,l+=bufferPitch)
	{
		for(i=0,k=0;i<bitmap.width;++i,k+=4)
		{
			tempPixel = bitmap.buffer[(j*bitmap.pitch)+i];

			buffer[l+k] = tempPixel;
			buffer[l+k+1] = tempPixel;
			buffer[l+k+2] = tempPixel;
			buffer[l+k+3] = tempPixel;
		}
	}
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
FT_Library& FreetypeFontAtlas::FreetypeSingleton::getLibrary()
{
	static FreetypeFontAtlas::FreetypeSingleton retval;

	return retval.getLib();
}

//------------------------------------------------------------------------------
FreetypeFontAtlas::FreetypeSingleton::FreetypeSingleton()
{
	FT_Error err;

	err = FT_Init_FreeType(&library);
	if(err)
		DebugStatement(L"FreetypeSingleton: Could not initialise library; err code: " << err);
}

//------------------------------------------------------------------------------
FreetypeFontAtlas::FreetypeSingleton::~FreetypeSingleton()
{
	FT_Error err;

	err = FT_Done_FreeType(library);
	if(err)
		DebugStatement(L"FreetypeSingleton: Could not destroy library; err code: " << err);
}
