/*
 * FreeTypeGX is a wrapper class for libFreeType which renders a compiled
 * FreeType parsable font into a GX texture for Wii homebrew development.
 * Copyright (C) 2008 Armin Tamzarian
 * Modified by Dimok, 2010
 *
 * This file is part of FreeTypeGX.
 *
 * FreeTypeGX 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 3 of the License, or
 * (at your option) any later version.
 *
 * FreeTypeGX 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 FreeTypeGX.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "FreeTypeGX.h"

using namespace std;

/**
 * Convert a short char string to a wide char string.
 *
 * This routine converts a supplied short character string into a wide character string.
 * Note that it is the user's responsibility to clear the returned buffer once it is no longer needed.
 *
 * @param strChar	Character string to be converted.
 * @return Wide character representation of supplied character string.
 */

wchar_t* charToWideChar(const char* strChar)
{
    if(!strChar)
        return NULL;

	wchar_t *strWChar = new (std::nothrow) wchar_t[strlen(strChar) + 1];
	if(!strWChar)
		return NULL;

	int bt = mbstowcs(strWChar, strChar, strlen(strChar));
	if (bt > 0)
	{
		strWChar[bt] = 0;
		return strWChar;
	}

	wchar_t *tempDest = strWChar;
	while((*tempDest++ = *strChar++));

	return strWChar;
}

/**
 * Default constructor for the FreeTypeGX class for WiiXplorer.
 */
FreeTypeGX::FreeTypeGX(const uint8_t* fontBuffer, FT_Long bufferSize)
{
    ftPointSize = 0;

    FT_Init_FreeType(&ftLibrary);
    FT_New_Memory_Face(ftLibrary, (FT_Byte *)fontBuffer, bufferSize, 0, &ftFace);

	setVertexFormat(GX_VTXFMT1);
	ftKerningEnabled = FT_HAS_KERNING(ftFace);
}

/**
 * Default destructor for the FreeTypeGX class.
 */
FreeTypeGX::~FreeTypeGX()
{
	unloadFont();
	FT_Done_Face(ftFace);
	FT_Done_FreeType(ftLibrary);
}

/**
 * Setup the vertex attribute formats for the glyph textures.
 *
 * This function sets up the vertex format for the glyph texture on the specified vertex format index.
 * Note that this function should not need to be called except if the vertex formats are cleared or the specified
 * vertex format index is modified.
 *
 * @param vertexIndex	Vertex format index (GX_VTXFMT*) of the glyph textures as defined by the libogc gx.h header file.
*/
void FreeTypeGX::setVertexFormat(uint8_t vertexInd)
{
	vertexIndex = vertexInd;
	GX_SetVtxAttrFmt(vertexIndex, GX_VA_POS, GX_POS_XYZ, GX_S16, 0);
	GX_SetVtxAttrFmt(vertexIndex, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
	GX_SetVtxAttrFmt(vertexIndex, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
}

/**
 * Clears all loaded font glyph data.
 *
 * This routine clears all members of the font map structure and frees all allocated memory back to the system.
 */
void FreeTypeGX::unloadFont()
{
	if(this->fontData.size() == 0)
		return;

    map<int16_t, map<wchar_t, ftgxCharData> >::iterator itr;
    map<wchar_t, ftgxCharData>::iterator itr2;

	for(itr = fontData.begin(); itr != fontData.end(); itr++)
	{
        for(itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++)
            free(itr2->second.glyphDataTexture);

        itr->second.clear();
	}

	fontData.clear();
	ftgxAlign.clear();
}

/**
 * Caches the given font glyph in the instance font texture buffer.
 *
 * This routine renders and stores the requested glyph's bitmap and relevant information into its own quickly addressible
 * structure within an instance-specific map.
 *
 * @param charCode	The requested glyph's character code.
 * @return A pointer to the allocated font structure.
 */
ftgxCharData * FreeTypeGX::cacheGlyphData(wchar_t charCode, int16_t pixelSize)
{
    map<int16_t, map<wchar_t, ftgxCharData> >::iterator itr;
    map<wchar_t, ftgxCharData>::iterator itr2;

    itr = fontData.find(pixelSize);
    if(itr != fontData.end())
    {
        itr2 = itr->second.find(charCode);

        if(itr2 != itr->second.end())
        {
            return &itr2->second;
        }
    }

	FT_UInt gIndex;
	uint16_t textureWidth = 0, textureHeight = 0;

	if(ftPointSize != pixelSize)
	{
	    ftPointSize = pixelSize;
        FT_Set_Pixel_Sizes(ftFace, 0, ftPointSize);

        //!Cache ascender and decender as well
        map<int16_t, ftgxDataOffset>::iterator itrAlign = ftgxAlign.find(ftPointSize);
        if(itrAlign == ftgxAlign.end())
        {
            ftgxAlign[ftPointSize].ascender = (int16_t) ftFace->size->metrics.ascender>>6;
            ftgxAlign[ftPointSize].descender = (int16_t) ftFace->size->metrics.descender>>6;
            ftgxAlign[ftPointSize].max = 0;
            ftgxAlign[ftPointSize].min = 0;
        }
	}

	gIndex = FT_Get_Char_Index(ftFace, (FT_ULong) charCode);
	if (gIndex != 0 && FT_Load_Glyph(ftFace, gIndex, FT_LOAD_DEFAULT | FT_LOAD_RENDER) == 0)
	{
		if(ftFace->glyph->format == FT_GLYPH_FORMAT_BITMAP)
		{
			FT_Bitmap *glyphBitmap = &ftFace->glyph->bitmap;

			textureWidth = glyphBitmap->width + (4 - glyphBitmap->width % 4) % 4;
			textureHeight = glyphBitmap->rows + (4 - glyphBitmap->rows % 4) % 4;

			fontData[pixelSize][charCode].renderOffsetX = (int16_t) ftFace->glyph->bitmap_left;
			fontData[pixelSize][charCode].glyphAdvanceX = (uint16_t) (ftFace->glyph->advance.x >> 6);
			fontData[pixelSize][charCode].glyphIndex = (uint32_t) gIndex;
			fontData[pixelSize][charCode].textureWidth = (uint16_t) textureWidth;
			fontData[pixelSize][charCode].textureHeight = (uint16_t) textureHeight;
			fontData[pixelSize][charCode].renderOffsetY = (int16_t) ftFace->glyph->bitmap_top;
			fontData[pixelSize][charCode].renderOffsetMax = (int16_t) ftFace->glyph->bitmap_top;
			fontData[pixelSize][charCode].renderOffsetMin = (int16_t) glyphBitmap->rows - ftFace->glyph->bitmap_top;
			fontData[pixelSize][charCode].glyphDataTexture = NULL;

			loadGlyphData(glyphBitmap, &fontData[pixelSize][charCode]);

			return &fontData[pixelSize][charCode];
		}
	}
	return NULL;
}

/**
 * Locates each character in this wrapper's configured font face and proccess them.
 *
 * This routine locates each character in the configured font face and renders the glyph's bitmap.
 * Each bitmap and relevant information is loaded into its own quickly addressible structure within an instance-specific map.
 */
uint16_t FreeTypeGX::cacheGlyphDataComplete(int16_t pixelSize)
{
	uint32_t i = 0;
	FT_UInt gIndex;

	FT_ULong charCode = FT_Get_First_Char( ftFace, &gIndex );
	while ( gIndex != 0 )
	{
		if(cacheGlyphData(charCode, pixelSize) != NULL)
			++i;
		charCode = FT_Get_Next_Char( ftFace, charCode, &gIndex );
	}
	return (uint16_t)(i);
}

/**
 * Loads the rendered bitmap into the relevant structure's data buffer.
 *
 * This routine does a simple byte-wise copy of the glyph's rendered 8-bit grayscale bitmap into the structure's buffer.
 * Each byte is converted from the bitmap's intensity value into the a uint32_t RGBA value.
 *
 * @param bmp	A pointer to the most recently rendered glyph's bitmap.
 * @param charData	A pointer to an allocated ftgxCharData structure whose data represent that of the last rendered glyph.
 *
 *
 * Optimized for RGBA8 use by Dimok.
 */
void FreeTypeGX::loadGlyphData(FT_Bitmap *bmp, ftgxCharData *charData)
{
    int length =  ((((charData->textureWidth+3)>>2)*((charData->textureHeight+3)>>2)*32*2 + 31) & ~31);

	uint8_t * glyphData = (uint8_t *) memalign(32, length);
	if(!glyphData)
        return;

	memset(glyphData, 0x00, length);

	uint8_t *src = (uint8_t *)bmp->buffer;
	uint32_t offset;

	for (int imagePosY = 0; imagePosY < bmp->rows; ++imagePosY)
	{
		for (int imagePosX = 0; imagePosX < bmp->width; ++imagePosX)
		{
		    offset = ((((imagePosY >> 2) * (charData->textureWidth >> 2) + (imagePosX >> 2)) << 5) + ((imagePosY & 3) << 2) + (imagePosX & 3)) << 1;
			glyphData[offset] = *src;
			glyphData[offset+1] = *src;
			glyphData[offset+32] = *src;
			glyphData[offset+33] = *src;
			++src;
		}
	}
	DCFlushRange(glyphData, length);

	charData->glyphDataTexture = glyphData;
}

/**
 * Determines the x offset of the rendered string.
 *
 * This routine calculates the x offset of the rendered string based off of a supplied positional format parameter.
 *
 * @param width	Current pixel width of the string.
 * @param format	Positional format of the string.
 */
int16_t FreeTypeGX::getStyleOffsetWidth(uint16_t width, uint16_t format)
{
	if (format & FTGX_JUSTIFY_LEFT)
		return 0;
	else if (format & FTGX_JUSTIFY_CENTER)
		return -(width >> 1);
	else if (format & FTGX_JUSTIFY_RIGHT)
		return -width;
	return 0;
}

/**
 * Determines the y offset of the rendered string.
 *
 * This routine calculates the y offset of the rendered string based off of a supplied positional format parameter.
 *
 * @param offset	Current pixel offset data of the string.
 * @param format	Positional format of the string.
 */
int16_t FreeTypeGX::getStyleOffsetHeight(int16_t format, uint16_t pixelSize)
{
    map<int16_t, ftgxDataOffset>::iterator itrAlign = ftgxAlign.find(pixelSize);
    if(itrAlign == ftgxAlign.end())
        return 0;

	switch(format & FTGX_ALIGN_MASK)
	{
		case FTGX_ALIGN_TOP:
			return itrAlign->second.ascender;

		case FTGX_ALIGN_MIDDLE:
		default:
			return (itrAlign->second.ascender + itrAlign->second.descender + 1) >> 1;

		case FTGX_ALIGN_BOTTOM:
			return itrAlign->second.descender;

		case FTGX_ALIGN_BASELINE:
			return 0;

		case FTGX_ALIGN_GLYPH_TOP:
			return itrAlign->second.max;

		case FTGX_ALIGN_GLYPH_MIDDLE:
			return (itrAlign->second.max + itrAlign->second.min + 1) >> 1;

		case FTGX_ALIGN_GLYPH_BOTTOM:
			return itrAlign->second.min;
	}
	return 0;
}

/**
 * Processes the supplied text string and prints the results at the specified coordinates.
 *
 * This routine processes each character of the supplied text string, loads the relevant preprocessed bitmap buffer,
 * a texture from said buffer, and loads the resultant texture into the EFB.
 *
 * @param x	Screen X coordinate at which to output the text.
 * @param y Screen Y coordinate at which to output the text. Note that this value corresponds to the text string origin and not the top or bottom of the glyphs.
 * @param text	NULL terminated string to output.
 * @param color	Optional color to apply to the text characters. If not specified default value is ftgxWhite: (GXColor){0xff, 0xff, 0xff, 0xff}
 * @param textStyle	Flags which specify any styling which should be applied to the rendered string.
 * @return The number of characters printed.
 */
uint16_t FreeTypeGX::drawText(int16_t x, int16_t y, int16_t z, const wchar_t *text, int16_t pixelSize, GXColor color, uint16_t textStyle, uint16_t textWidth, uint16_t widthLimit)
{
    if(!text)
        return 0;

	uint16_t fullTextWidth = textWidth > 0 ? textWidth : getWidth(text, pixelSize);
	uint16_t x_pos = x, printed = 0;
	uint16_t x_offset = 0, y_offset = 0;
	GXTexObj glyphTexture;
	FT_Vector pairDelta;

	if(textStyle & FTGX_JUSTIFY_MASK)
	{
		x_offset = getStyleOffsetWidth(fullTextWidth, textStyle);
	}
	if(textStyle & FTGX_ALIGN_MASK)
	{
		y_offset = getStyleOffsetHeight(textStyle, pixelSize);
	}

	int i = 0;

	while (text[i])
	{
	    if(widthLimit > 0 && (x_pos-x) > widthLimit)
            break;

		ftgxCharData* glyphData = cacheGlyphData(text[i], pixelSize);

		if (glyphData != NULL)
		{
			if (ftKerningEnabled && i > 0)
			{
				FT_Get_Kerning(ftFace, fontData[pixelSize][text[i - 1]].glyphIndex, glyphData->glyphIndex, FT_KERNING_DEFAULT, &pairDelta);
				x_pos += pairDelta.x >> 6;
			}

			GX_InitTexObj(&glyphTexture, glyphData->glyphDataTexture, glyphData->textureWidth, glyphData->textureHeight, GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_FALSE);
			copyTextureToFramebuffer(&glyphTexture, glyphData->textureWidth, glyphData->textureHeight, x_pos + glyphData->renderOffsetX + x_offset, y - glyphData->renderOffsetY + y_offset, z, color);

			x_pos += glyphData->glyphAdvanceX;
			++printed;
		}
		++i;
	}

	if(textStyle & FTGX_STYLE_MASK)
	{
		getOffset(text, pixelSize, widthLimit);
		drawTextFeature(x + x_offset, y + y_offset, z, pixelSize, fullTextWidth, &ftgxAlign[pixelSize], textStyle, color);
	}

	return printed;
}

void FreeTypeGX::drawTextFeature(int16_t x, int16_t y, int16_t z, int16_t pixelSize, uint16_t width, ftgxDataOffset *offsetData, uint16_t format, GXColor color)
{
	uint16_t featureHeight = pixelSize >> 4 > 0 ? pixelSize >> 4 : 1;

	if (format & FTGX_STYLE_UNDERLINE)
		this->copyFeatureToFramebuffer(width, featureHeight, x, y + 1, z, color);

	if (format & FTGX_STYLE_STRIKE)
		this->copyFeatureToFramebuffer(width, featureHeight, x, y - ((offsetData->max) >> 1), z, color);
}

/**
 * Processes the supplied string and return the width of the string in pixels.
 *
 * This routine processes each character of the supplied text string and calculates the width of the entire string.
 * Note that if precaching of the entire font set is not enabled any uncached glyph will be cached after the call to this function.
 *
 * @param text	NULL terminated string to calculate.
 * @return The width of the text string in pixels.
 */
uint16_t FreeTypeGX::getWidth(const wchar_t *text, int16_t pixelSize)
{
    if(!text)
        return 0;

	uint16_t strWidth = 0;
	FT_Vector pairDelta;

	int i = 0;
	while (text[i])
	{
		ftgxCharData* glyphData = cacheGlyphData(text[i], pixelSize);

		if (glyphData != NULL)
		{
			if (ftKerningEnabled && (i > 0))
			{
				FT_Get_Kerning(ftFace, fontData[pixelSize][text[i - 1]].glyphIndex, glyphData->glyphIndex, FT_KERNING_DEFAULT, &pairDelta);
				strWidth += pairDelta.x >> 6;
			}

			strWidth += glyphData->glyphAdvanceX;
		}
		++i;
	}
	return strWidth;
}

/**
 * Single char width
*/
uint16_t FreeTypeGX::getCharWidth(const wchar_t wChar, int16_t pixelSize, const wchar_t prevChar)
{
    uint16_t strWidth = 0;
    ftgxCharData * glyphData = cacheGlyphData(wChar, pixelSize);

    if(glyphData != NULL)
    {
        if (ftKerningEnabled && prevChar != 0x0000)
        {
            FT_Vector pairDelta;
            FT_Get_Kerning(ftFace, fontData[pixelSize][prevChar].glyphIndex, glyphData->glyphIndex, FT_KERNING_DEFAULT, &pairDelta);
            strWidth += pairDelta.x >> 6;
        }
        strWidth += glyphData->glyphAdvanceX;
    }

	return strWidth;
}

/**
 * Processes the supplied string and return the height of the string in pixels.
 *
 * This routine processes each character of the supplied text string and calculates the height of the entire string.
 * Note that if precaching of the entire font set is not enabled any uncached glyph will be cached after the call to this function.
 *
 * @param text	NULL terminated string to calculate.
 * @return The height of the text string in pixels.
 */
uint16_t FreeTypeGX::getHeight(const wchar_t *text, int16_t pixelSize)
{
	getOffset(text, pixelSize);

	return ftgxAlign[pixelSize].max - ftgxAlign[pixelSize].min;
}

/**
 * Get the maximum offset above and minimum offset below the font origin line.
 *
 * This function calculates the maximum pixel height above the font origin line and the minimum
 * pixel height below the font origin line and returns the values in an addressible structure.
 *
 * @param text	NULL terminated string to calculate.
 * @param offset returns the max and min values above and below the font origin line
 *
 */
void FreeTypeGX::getOffset(const wchar_t *text, int16_t pixelSize, uint16_t widthLimit)
{
    if(ftgxAlign.find(pixelSize) != ftgxAlign.end())
        return;

	int16_t strMax = 0, strMin = 9999;
	uint16_t currWidth = 0;

	int i = 0;

	while (text[i])
	{
		if(widthLimit > 0 && currWidth >= widthLimit)
            break;

		ftgxCharData* glyphData = cacheGlyphData(text[i], pixelSize);

		if(glyphData != NULL)
		{
			strMax = glyphData->renderOffsetMax > strMax ? glyphData->renderOffsetMax : strMax;
			strMin = glyphData->renderOffsetMin < strMin ? glyphData->renderOffsetMin : strMin;
			currWidth += glyphData->glyphAdvanceX;
		}

		++i;
	}

	if(ftPointSize != pixelSize)
	{
	    ftPointSize = pixelSize;
        FT_Set_Pixel_Sizes(ftFace, 0, ftPointSize);
	}

	ftgxAlign[pixelSize].ascender = ftFace->size->metrics.ascender>>6;
	ftgxAlign[pixelSize].descender = ftFace->size->metrics.descender>>6;
	ftgxAlign[pixelSize].max = strMax;
	ftgxAlign[pixelSize].min = strMin;
}

/**
 * Copies the supplied texture quad to the EFB.
 *
 * This routine uses the in-built GX quad builder functions to define the texture bounds and location on the EFB target.
 *
 * @param texObj	A pointer to the glyph's initialized texture object.
 * @param texWidth	The pixel width of the texture object.
 * @param texHeight	The pixel height of the texture object.
 * @param screenX	The screen X coordinate at which to output the rendered texture.
 * @param screenY	The screen Y coordinate at which to output the rendered texture.
 * @param color	Color to apply to the texture.
 */
void FreeTypeGX::copyTextureToFramebuffer(GXTexObj *texObj, f32 texWidth, f32 texHeight, int16_t screenX, int16_t screenY, int16_t screenZ, GXColor color)
{
	GX_LoadTexObj(texObj, GX_TEXMAP0);
	GX_InvalidateTexAll();

	GX_SetTevOp (GX_TEVSTAGE0, GX_MODULATE);
	GX_SetVtxDesc (GX_VA_TEX0, GX_DIRECT);

	GX_Begin(GX_QUADS, this->vertexIndex, 4);
	GX_Position3s16(screenX, screenY, screenZ);
	GX_Color4u8(color.r, color.g, color.b, color.a);
	GX_TexCoord2f32(0.0f, 0.0f);

	GX_Position3s16(texWidth + screenX, screenY, screenZ);
	GX_Color4u8(color.r, color.g, color.b, color.a);
	GX_TexCoord2f32(1.0f, 0.0f);

	GX_Position3s16(texWidth + screenX, texHeight + screenY, screenZ);
	GX_Color4u8(color.r, color.g, color.b, color.a);
	GX_TexCoord2f32(1.0f, 1.0f);

	GX_Position3s16(screenX, texHeight + screenY, screenZ);
	GX_Color4u8(color.r, color.g, color.b, color.a);
	GX_TexCoord2f32(0.0f, 1.0f);
	GX_End();

    GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
    GX_SetVtxDesc(GX_VA_TEX0, GX_NONE);
}

/**
 * Creates a feature quad to the EFB.
 *
 * This function creates a simple quad for displaying underline or strikeout text styling.
 *
 * @param featureWidth	The pixel width of the quad.
 * @param featureHeight	The pixel height of the quad.
 * @param screenX	The screen X coordinate at which to output the quad.
 * @param screenY	The screen Y coordinate at which to output the quad.
 * @param color	Color to apply to the texture.
 */
void FreeTypeGX::copyFeatureToFramebuffer(f32 featureWidth, f32 featureHeight, int16_t screenX, int16_t screenY, int16_t screenZ, GXColor color)
{
	GX_SetTevOp (GX_TEVSTAGE0, GX_PASSCLR);
	GX_SetVtxDesc (GX_VA_TEX0, GX_NONE);

	GX_Begin(GX_QUADS, this->vertexIndex, 4);
	GX_Position3s16(screenX, screenY, screenZ);
	GX_Color4u8(color.r, color.g, color.b, color.a);

	GX_Position3s16(featureWidth + screenX, screenY, screenZ);
	GX_Color4u8(color.r, color.g, color.b, color.a);

	GX_Position3s16(featureWidth + screenX, featureHeight + screenY, screenZ);
	GX_Color4u8(color.r, color.g, color.b, color.a);

	GX_Position3s16(screenX, featureHeight + screenY, screenZ);
	GX_Color4u8(color.r, color.g, color.b, color.a);
	GX_End();

    GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
	GX_SetVtxDesc(GX_VA_TEX0, GX_NONE);
}