#include "FTFont.h"
#include "gfx/Texture.h"
#include "gfx/RenderDevice.h"
#include "img/ImageData.h"
#include "BFApp.h"

#include "freetype/ftsizes.h"

#include "util/AllocDebug.h"

USING_NS_BF;

const int FT_PAGE_WIDTH = 1024;
const int FT_PAGE_HEIGHT = 1024;

//const int FT_PAGE_WIDTH = 128;
//const int FT_PAGE_HEIGHT = 128;

static FT_Library gFTLibrary = NULL;
static FTFontManager gFTFontManager;

FTFont::FTFont()
{
	mFace = NULL;
	mFaceSize = NULL;
	mHeight = 0;
	mAscent = 0;
	mDescent = 0;
	mMaxAdvance = 0;
}

FTFont::~FTFont()
{
	Dispose(false);
}

void FTFont::Dispose(bool cacheRetain)
{
	if (mFaceSize != NULL)
	{
		BF_ASSERT((mFaceSize->mRefCount > 0) && (mFaceSize->mRefCount < 1000000));
		mFaceSize->mRefCount--;

		if (mFaceSize->mRefCount == 0)
		{
			mFace->mFaceSizes.Remove(mFaceSize->mPointSize);
			delete mFaceSize;

			if (!cacheRetain)
			{
				if (mFace->mFaceSizes.IsEmpty())
				{
					bool removed = gFTFontManager.mFaces.Remove(mFace->mFileName);
					BF_ASSERT(removed);
					delete mFace;
				}
			}
		}
		mFaceSize = NULL;
	}
}

FTFontManager::FaceSize::~FaceSize()
{
	if (mFTSize != 0)
		FT_Done_Size(mFTSize);
}

FTFontManager::Face::~Face()
{
	if (mFTFace != 0)
		FT_Done_Face(mFTFace);
}

FTFontManager::FTFontManager()
{
	for (int i = 0; i < 256; i++)
	{
		uint8 whiteVal = (uint8)(pow(i / 255.0f, 0.75) * 255.0f);
		uint8 blackVal = (uint8)(pow(i / 255.0f, 0.9) * 255.0f);

		mWhiteTab[i] = whiteVal;
		mBlackTab[i] = blackVal;
	}
}

FTFontManager::~FTFontManager()
{
	for (auto page : mPages)
		delete page;
	mPages.Clear();
	for (auto faceKV : mFaces)
		delete faceKV.mValue;
	mFaces.Clear();
}

void FTFontManager::DoClearCache()
{
	for (auto page : mPages)
		delete page;
	mPages.Clear();

	for (auto itr = mFaces.begin(); itr != mFaces.end(); )
	{
		auto face = itr->mValue;
		if (face->mFaceSizes.IsEmpty())
		{
			delete face;
			itr = mFaces.Remove(itr);
		}
		else
			++itr;
	}
}

void FTFontManager::ClearCache()
{
	gFTFontManager.DoClearCache();
}

FTFontManager::Page::~Page()
{
	mTexture->Release();
	//delete mTexture;
}

/*FTFontManager::Glyph::~Glyph()
{
	//delete mTextureSegment;
}*/

bool FTFont::Load(const StringImpl& fileName, float pointSize)
{
	if (gFTLibrary == NULL)
		FT_Init_FreeType(&gFTLibrary);

	FTFontManager::Face* face = NULL;

	String key = fileName;
	void* memPtr = NULL;
	int memLen = 0;
	bool isMemory = ParseMemorySpan(fileName, memPtr, memLen, &key);

	FTFontManager::Face** facePtr = NULL;
	if (gFTFontManager.mFaces.TryAdd(key, NULL, &facePtr))
	{
		face = new FTFontManager::Face();
		*facePtr = face;

		face->mFileName = key;
		FT_Face ftFace = NULL;

		String useFileName = fileName;
		int faceIdx = 0;
		int atPos = (int)useFileName.IndexOf('@', 1);
		if (atPos != -1)
		{
			faceIdx = atoi(useFileName.c_str() + atPos + 1);
			useFileName.RemoveToEnd(atPos);
		}
		
		if (isMemory)		
		{			
			FT_New_Memory_Face(gFTLibrary, (FT_Byte*)memPtr, memLen, faceIdx, &ftFace);
		}
		else
		{ 
			FT_New_Face(gFTLibrary, useFileName.c_str(), faceIdx, &ftFace);
		}
		
		face->mFTFace = ftFace;
	}
	else
	{
		face = *facePtr;
	}
	if (face->mFTFace == NULL)
		return false;

	mFace = face;

	FTFontManager::FaceSize** faceSizePtr = NULL;
	if (face->mFaceSizes.TryAdd(pointSize, NULL, &faceSizePtr))
	{
		//OutputDebugStrF("Created face %s %f\n", fileName.c_str(), pointSize);

		mFaceSize = new FTFontManager::FaceSize();
		*faceSizePtr = mFaceSize;

		FT_Size ftSize = NULL;
		FT_New_Size(mFace->mFTFace, &ftSize);
		FT_Activate_Size(ftSize);
		auto error = FT_Set_Char_Size(mFace->mFTFace, 0, (int)(pointSize * 64), 72, 72);

		mFaceSize->mFace = mFace;
		mFaceSize->mFTSize = ftSize;
		mFaceSize->mPointSize = pointSize;
	}
	else
	{
		mFaceSize = *faceSizePtr;
	}
	mFaceSize->mRefCount++;

	mHeight = mFaceSize->mFTSize->metrics.height / 64;
	mAscent = mFaceSize->mFTSize->metrics.ascender / 64;
	mDescent = mFaceSize->mFTSize->metrics.descender / 64;
	mMaxAdvance = mFaceSize->mFTSize->metrics.max_advance / 64;

	return true;
}

TextureSegment* BF_CALLTYPE Gfx_CreateTextureSegment(TextureSegment* textureSegment, int srcX, int srcY, int srcWidth, int srcHeight);

FTFontManager::Glyph* FTFont::AllocGlyph(int charCode, bool allowDefault)
{
	FT_Activate_Size(mFaceSize->mFTSize);

	int glyph_index = FT_Get_Char_Index(mFace->mFTFace, charCode);
	if ((glyph_index == 0) && (!allowDefault))
		return NULL;

	auto error = FT_Load_Glyph(mFace->mFTFace, glyph_index, FT_LOAD_NO_BITMAP);
	if (error != FT_Err_Ok)
		return NULL;

	error = FT_Render_Glyph(mFace->mFTFace->glyph, FT_RENDER_MODE_NORMAL);
	if (error != FT_Err_Ok)
		return NULL;

	auto& bitmap = mFace->mFTFace->glyph->bitmap;

	if ((bitmap.rows > FT_PAGE_HEIGHT) || (bitmap.width > FT_PAGE_WIDTH))
	{
		return NULL;
	}

	FTFontManager::Page* page = NULL;

	if (!gFTFontManager.mPages.empty())
	{
		page = gFTFontManager.mPages.back();
		if (page->mCurX + (int)bitmap.width > page->mTexture->mWidth)
		{
			// Move down to next row
			page->mCurX = 0;
			page->mCurY += page->mMaxRowHeight;
			page->mMaxRowHeight = 0;
		}

		if (page->mCurY + (int)bitmap.rows > page->mTexture->mHeight)
		{
			// Doesn't fit
			page = NULL;
		}
	}

	if (page == NULL)
	{
		page = new FTFontManager::Page();
		gFTFontManager.mPages.push_back(page);
	}

	//auto glyph = new FTFontManager::Glyph();

	static FTFontManager::Glyph staticGlyph;
	auto glyph = &staticGlyph;

	auto ftFace = mFace->mFTFace;

	glyph->mXAdvance = ftFace->glyph->advance.x / 64;
	glyph->mXOffset = ftFace->glyph->bitmap_left;
	glyph->mYOffset = ftFace->size->metrics.ascender / 64 - ftFace->glyph->bitmap_top;
	glyph->mPage = page;
	glyph->mX = page->mCurX;
	glyph->mY = page->mCurY;
	glyph->mWidth = bitmap.width;
	glyph->mHeight = bitmap.rows;

	if (page->mTexture == NULL)
	{
		ImageData* img = new ImageData();
		img->CreateNew(FT_PAGE_WIDTH, FT_PAGE_HEIGHT);
		auto* bits = img->mBits;
		for (int i = 0; i < FT_PAGE_HEIGHT * FT_PAGE_WIDTH; i++)
		{
			if (i % 3 == 0)
				bits[i] = 0xFFFF0000;
			else if (i % 3 == 1)
				bits[i] = 0xFF00FF00;
			else
				bits[i] = 0xFF0000FF;
		}
		page->mTexture = gBFApp->mRenderDevice->LoadTexture(img, TextureFlag_NoPremult);
		img->Deref();
	}

	if (bitmap.width > 0)
	{
		ImageData* img = new ImageData();
		img->CreateNew(bitmap.width, bitmap.rows);
		auto* bits = img->mBits;
		int width = img->mWidth;
		for (int y = 0; y < (int)bitmap.rows; y++)
		{
			for (int x = 0; x < (int)bitmap.width; x++)
			{
				uint8 val = bitmap.buffer[y * bitmap.pitch + x];

				uint8 whiteVal = gFTFontManager.mWhiteTab[val];
				uint8 blackVal = gFTFontManager.mBlackTab[val];

				bits[y * width + x] = ((int32)whiteVal << 24) |
					((int32)blackVal) | ((int32)0xFF << 8) | ((int32)0xFF << 16);
			}
		}
		page->mTexture->Blt(img, page->mCurX, page->mCurY);
		img->Deref();
	}

	page->mCurX += bitmap.width;
	page->mMaxRowHeight = std::max(page->mMaxRowHeight, (int)bitmap.rows);

	auto texture = page->mTexture;
	texture->AddRef();

	TextureSegment* textureSegment = new TextureSegment();
	textureSegment->mTexture = texture;
	textureSegment->mU1 = (glyph->mX / (float)texture->mWidth);
	textureSegment->mV1 = (glyph->mY / (float)texture->mHeight);
	textureSegment->mU2 = ((glyph->mX + glyph->mWidth) / (float)texture->mWidth);
	textureSegment->mV2 = ((glyph->mY + glyph->mHeight) / (float)texture->mHeight);
	textureSegment->mScaleX = (float)abs(glyph->mWidth);
	textureSegment->mScaleY = (float)abs(glyph->mHeight);

	glyph->mTextureSegment = textureSegment;
	return glyph;
}

int FTFont::GetKerning(int charA, int charB)
{
	FT_Activate_Size(mFaceSize->mFTSize);
	FT_Vector kerning;
	int glyph_indexA = FT_Get_Char_Index(mFace->mFTFace, charA);
	int glyph_indexB = FT_Get_Char_Index(mFace->mFTFace, charB);
	FT_Get_Kerning(mFace->mFTFace, glyph_indexA, glyph_indexB, FT_KERNING_DEFAULT, &kerning);
	return kerning.x / 64;
}

void FTFont::Release(bool cacheRetain)
{
	Dispose(cacheRetain);
	delete this;
}


//////////////////////////////////////////////////////////////////////////

BF_EXPORT FTFont* BF_CALLTYPE FTFont_Load(const char* fileName, float pointSize)
{
	auto ftFont = new FTFont();
	if (!ftFont->Load(fileName, pointSize))
	{
		delete ftFont;
		return NULL;
	}

	return ftFont;
}

BF_EXPORT void BF_CALLTYPE FTFont_ClearCache()
{
	FTFontManager::ClearCache();
}

BF_EXPORT void BF_CALLTYPE FTFont_Delete(FTFont* ftFont, bool cacheRetain)
{
	ftFont->Release(cacheRetain);
}

BF_EXPORT FTFontManager::Glyph* BF_CALLTYPE FTFont_AllocGlyph(FTFont* ftFont, int charCode, bool allowDefault)
{
	return ftFont->AllocGlyph(charCode, allowDefault);
}

BF_EXPORT int BF_CALLTYPE FTFont_GetKerning(FTFont* ftFont, int charCodeA, int charCodeB)
{
	auto kerning = ftFont->GetKerning(charCodeA, charCodeB);
	return kerning;
}
