/*
vgui_font.cpp - fonts management
Copyright (C) 2011 Uncle Mike

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.
*/

#include "common.h"
#include "vgui_draw.h"
#include "vgui_main.h"

int FontCache::s_pFontPageSize[FONT_PAGE_SIZE_COUNT] = { 16, 32, 64, 128 };

FontCache::FontCache() : m_CharCache( 0, 256, CacheEntryLessFunc )
{
	CacheEntry_t listHead = { 0, 0 };

	m_LRUListHeadIndex = m_CharCache.Insert( listHead );
	m_CharCache[m_LRUListHeadIndex].nextEntry = m_LRUListHeadIndex;
	m_CharCache[m_LRUListHeadIndex].prevEntry = m_LRUListHeadIndex;

	for( int i = 0; i < FONT_PAGE_SIZE_COUNT; i++ )
	{
		m_pCurrPage[i] = -1;
	}
}

bool FontCache::CacheEntryLessFunc( CacheEntry_t const &lhs, CacheEntry_t const &rhs )
{
	if( lhs.font < rhs.font )
		return true;
	else if( lhs.font > rhs.font )
		return false;

	return ( lhs.ch < rhs.ch );
}

bool FontCache::GetTextureForChar( Font *font, char ch, int *textureID, float **texCoords )
{
	static CacheEntry_t cacheitem;

	cacheitem.font = font;
	cacheitem.ch = ch;

	Assert( texCoords != NULL );

	*texCoords = cacheitem.texCoords;

	HCacheEntry cacheHandle = m_CharCache.Find( cacheitem );

	if( m_CharCache.IsValidIndex( cacheHandle ))
	{
		// we have an entry already, return that
		int page = m_CharCache[cacheHandle].page;
		*textureID = m_PageList[page].textureID;
		*texCoords = m_CharCache[cacheHandle].texCoords;
		return true;
	}

	// get the char details
	int fontTall = font->getTall();
	int a, b, c;
	font->getCharABCwide( (byte)ch, a, b, c );
	int fontWide = b;

	// get a texture to render into
	int page, drawX, drawY, twide, ttall;
	if( !AllocatePageForChar( fontWide, fontTall, page, drawX, drawY, twide, ttall ))
		return false;

	// create a buffer and render the character into it
	int nByteCount = s_pFontPageSize[FONT_PAGE_SIZE_COUNT-1] * s_pFontPageSize[FONT_PAGE_SIZE_COUNT-1] * 4;
	byte * rgba = (byte *)Z_Malloc( nByteCount );
	font->getCharRGBA( (byte)ch, 0, 0, fontWide, fontTall, rgba );

	// upload the new sub texture 
	VGUI_BindTexture( m_PageList[page].textureID );
	VGUI_UploadTextureBlock( m_PageList[page].textureID, drawX, drawY, rgba, fontWide, fontTall );

	// set the cache info
	cacheitem.page = page;
	
	double adjust =  0.0f; // the 0.5 texel offset is done in CMatSystemTexture::SetMaterial()

	cacheitem.texCoords[0] = (float)((double)drawX / ((double)twide + adjust));
	cacheitem.texCoords[1] = (float)((double)drawY / ((double)ttall + adjust));
	cacheitem.texCoords[2] = (float)((double)(drawX + fontWide) / (double)twide);
	cacheitem.texCoords[3] = (float)((double)(drawY + fontTall) / (double)ttall);

	m_CharCache.Insert( cacheitem );

	// return the data
	*textureID = m_PageList[page].textureID;
	// memcpy( texCoords, cacheitem.texCoords, sizeof( float ) * 4 );
	return true;
}

int FontCache::ComputePageType( int charTall ) const
{
	for( int i = 0; i < FONT_PAGE_SIZE_COUNT; i++ )
	{
		if( charTall < s_pFontPageSize[i] )
			return i;
	}
	return -1;
}

bool FontCache::AllocatePageForChar( int charWide, int charTall, int &pageIndex, int &drawX, int &drawY, int &twide, int &ttall )
{
	// see if there is room in the last page for this character
	int nPageType = ComputePageType( charTall );
	pageIndex = m_pCurrPage[nPageType];

	int nNextX = 0;
	bool bNeedsNewPage = true;

	if( pageIndex > -1 )
	{
		Page_t &page = m_PageList[pageIndex];

		nNextX = page.nextX + charWide;

		// make sure we have room on the current line of the texture page
		if( nNextX > page.wide )
		{
			// move down a line
			page.nextX = 0;
			nNextX = charWide;
			page.nextY += page.fontHeight + 1;
		}

		bNeedsNewPage = (( page.nextY + page.fontHeight + 1 ) > page.tall );
	}
	
	if( bNeedsNewPage )
	{
		// allocate a new page
		pageIndex = m_PageList.AddToTail();
		Page_t &newPage = m_PageList[pageIndex];
		m_pCurrPage[nPageType] = pageIndex;

		newPage.textureID = VGUI_GenerateTexture();

		newPage.fontHeight = s_pFontPageSize[nPageType];
		newPage.wide = 256;
		newPage.tall = 256;
		newPage.nextX = 0;
		newPage.nextY = 0;

		nNextX = charWide;

		// create empty texture                    
		VGUI_CreateTexture( newPage.textureID, 256, 256 );
	}

	// output the position
	Page_t &page = m_PageList[pageIndex];
	drawX = page.nextX;
	drawY = page.nextY;
	twide = page.wide;
	ttall = page.tall;

	// update the next position to draw in
	page.nextX = nNextX + 1;

	return true;
}