#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include "../UI/RuUI_Font.h"
#include "../../Tools/dxtLib/dxtutility.h"

#define FONT_CANONICAL_SIZE					64
#define FONT_BITMAP_SIZE					128
#define FONT_TEXTURE_WIDTH					1024
#define FONT_TEXTURE_HEIGHT					1024
#define FONT_TEXTURE_UNIT_SIZE				32

// --------------------------------------------------------------------------------------
// static member value
// CRuFontType
CRuArrayList<CRuFontType*>			CRuFontType::s_fontType;
HDC									CRuFontType::s_hDC;
HFONT								CRuFontType::s_hCurrentFont;
HBITMAP								CRuFontType::s_hbmBitmap;
DWORD*								CRuFontType::s_pBitmapBits;

CRuImageFX_Resampler_32				CRuFontType::s_imageResampler;


// CRuFontTexture
CRuHashMap<int, CRuFontTexture*>	CRuFontTexture::s_fontTexIndex(1024);
CRuStack<CRuFontTexture*>			CRuFontTexture::s_freeFont;
CRuFontTexture*						CRuFontTexture::s_unitTexture;
CRuTextureD3D*						CRuFontTexture::s_pRuTexture;
bool								CRuFontTexture::s_isLockRally;
RuFontTextureCallBack				CRuFontTexture::s_rallyFunc;
DWORD*								CRuFontTexture::s_pPixels;
CRuArrayList<int>					CRuFontTexture::s_removedFontTexList;

// CRuFontChar
CRuHashMap<int, CRuFontChar *>		CRuFontChar::s_fontChar(4096);


// --------------------------------------------------------------------------------------
CRuFontType::CRuFontType()
{
	m_hFont			= NULL;	
	m_fontStr		= NULL;
	m_height		= 0;
	m_outLine		= ruFONTOUTLINE_NONE;
	m_weight		= ruFONTWEIGHT_NORMAL;
	m_serial		= 0;
}

// --------------------------------------------------------------------------------------
CRuFontType::~CRuFontType()
{
	if ( m_fontStr )
	{
		delete m_fontStr;
		m_fontStr = NULL;
	}

	if ( m_hFont )
	{
		DeleteObject( m_hFont );
		m_hFont = NULL;
	}	
}

// --------------------------------------------------------------------------------------
void CRuFontType::Initialize()
{
	s_fontType.Clear();	
	s_hDC			= NULL;
	s_hbmBitmap		= NULL;
	s_hCurrentFont	= NULL;
	s_pBitmapBits	= NULL;

	// Prepare to create a bitmap
	BITMAPINFO bmi;
	ZeroMemory( &bmi.bmiHeader,  sizeof(BITMAPINFOHEADER) );
	bmi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
	bmi.bmiHeader.biWidth       =  FONT_BITMAP_SIZE;
	bmi.bmiHeader.biHeight      = -FONT_BITMAP_SIZE;
	bmi.bmiHeader.biPlanes      = 1;
	bmi.bmiHeader.biCompression = BI_RGB;
	bmi.bmiHeader.biBitCount    = 32;

	// Create generic device contexts
	s_hDC				= CreateCompatibleDC( NULL );

	// Create a bitmap for the target font size
	s_hbmBitmap			= CreateDIBSection( s_hDC, &bmi, DIB_RGB_COLORS, (VOID**) &s_pBitmapBits, NULL, 0 );	

	// Setup target DC mapping mode and select target font bitmap into it
	SetMapMode(   s_hDC, MM_TEXT );
	SelectObject( s_hDC, s_hbmBitmap );

	// Set text properties
	SetTextColor( s_hDC, RGB(255, 255, 255) );
	SetBkColor(   s_hDC, 0x00000000 );
	SetTextAlign( s_hDC, TA_TOP );	
}

// --------------------------------------------------------------------------------------
void CRuFontType::Shutdown()
{	
	for(INT32 i = 0; i < s_fontType.Count(); ++i)
	{
		delete s_fontType[i];
	}

	s_fontType.Clear();
	s_hCurrentFont	= NULL;
	s_pBitmapBits	= NULL;

	if ( s_hbmBitmap ) {
		DeleteObject( s_hbmBitmap );
		s_hbmBitmap = NULL;
	}	

	if ( s_hDC ) {
		DeleteDC( s_hDC );
		s_hDC = NULL;
	}	
}

// --------------------------------------------------------------------------------------
int CALLBACK EnumFontProc( ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *lpntme, DWORD FontType, LPARAM lParam )
{
	int a = 0;
	return 0;
}

// --------------------------------------------------------------------------------------
// 建立 WINDOW 字型
CRuFontType* CRuFontType::GetFontType(const char *fontNameStr, int height, RuFontWeight weight, RuFontOutline RuFontOutline)
{
	if (fontNameStr == NULL || height < 6)
		return NULL;

	vector<CRuFontType*>::iterator iter;

	for(INT32 i = 0; i < s_fontType.Count(); ++i)
	{
		if(	strcmp(s_fontType[i]->m_fontStr, fontNameStr) == 0 && s_fontType[i]->m_outLine == RuFontOutline && s_fontType[i]->m_weight == weight)
			return s_fontType[i];
	}

	DWORD fw;
	switch ( weight )
	{
	case ruFONTWEIGHT_THIN:
		fw = FW_THIN;
		break;
	case ruFONTWEIGHT_NORMAL:
		fw = FW_NORMAL;
		break;
	case ruFONTWEIGHT_BOLD:
		fw = FW_BOLD;
		break;
	}

	// Create New Font
	static int lastSerial = 1;
	HFONT hFont = NULL;
	
	//int nLogPixelsY = GetDeviceCaps(s_hDC, LOGPIXELSY);	
	//int nHeight = FONT_CANONICAL_SIZE * nLogPixelsY / 72;
	int nHeight = FONT_CANONICAL_SIZE;
	if ( (hFont = CreateFont(
		nHeight,
		0,
		0,
		0,
		fw,
		false,
		false,
		false,
		DEFAULT_CHARSET,
		OUT_DEFAULT_PRECIS,
		CLIP_DEFAULT_PRECIS,
		DEFAULT_QUALITY,
		DEFAULT_PITCH | FF_DONTCARE,
		fontNameStr )) == NULL )
	{
		return NULL;
	}

	s_hCurrentFont = hFont;
	SelectObject(s_hDC, s_hCurrentFont);
	
	TEXTMETRIC textMetric;
	GetTextMetrics(s_hDC, &textMetric);

	CRuFontType *pFontType		= new CRuFontType;
	pFontType->m_height			= height;
	pFontType->m_outLine		= RuFontOutline;
	pFontType->m_weight			= weight;
	pFontType->m_hFont			= hFont;
	pFontType->m_serial			= lastSerial++;
	pFontType->m_fontStr		= new char[strlen(fontNameStr) + 1];
	strcpy( pFontType->m_fontStr, fontNameStr );
	s_fontType.Add(pFontType);

	return pFontType;
}

// --------------------------------------------------------------------------------------
void CRuFontType::PaintToMem(const WCHAR wchar, DWORD *pPixels, SIZE &size)
{		
	static DWORD outTemp[16384];
	assert(m_hFont || s_hDC || pPixels);

	if ( s_hCurrentFont != m_hFont )
	{
		s_hCurrentFont = m_hFont;
		SelectObject( s_hDC, s_hCurrentFont );
	}

	// Output source font to DC
	ExtTextOutW(s_hDC, 0, 0, ETO_OPAQUE, NULL, &wchar, 1, NULL);

	SIZE srcSize;

	// Get text extents for target font
	GetTextExtentPoint32W(s_hDC, &wchar, 1, &srcSize);
	
	if ( srcSize.cx > srcSize.cy )
	{
		size.cx = FONT_TEXTURE_UNIT_SIZE;
		size.cy = size.cx * srcSize.cy / srcSize.cx;
	}
	else
	{
		size.cy = FONT_TEXTURE_UNIT_SIZE;
		size.cx = size.cy * srcSize.cx / srcSize.cy;
	}	
	
	// Resample source font into target font
	s_imageResampler.ResampleImage(
		s_pBitmapBits,
		srcSize.cx,
		srcSize.cy,
		FONT_BITMAP_SIZE,
		outTemp,
		size.cx,
		size.cy,
		FONT_TEXTURE_UNIT_SIZE);

	int dxtWidth, dxtHeight;
	for ( dxtWidth = 2; dxtWidth < size.cx; dxtWidth <<= 1 );
	for ( dxtHeight = 2; dxtHeight < size.cy; dxtHeight <<= 1 );

	DWORD *pSrc = outTemp;
	for ( int h = 0; h < dxtHeight ; h++ )
	{
		if ( h < size.cy )
		{
			for (int w = 0; w < dxtWidth; w++)
			{
				if (w < size.cx && pSrc[w] & 0xFF)
					pPixels[w] = 0xFFFFFF | ((pSrc[w] & 0xFF) << 24);
				else
					pPixels[w] = 0x00FFFFFF;
			}
		}
		else
			memset(pPixels, 0, sizeof(DWORD) * dxtWidth);

		pSrc += FONT_TEXTURE_UNIT_SIZE;
		pPixels += dxtWidth;
	}

	/*
	DWORD *pSrc = s_pBitmapBits;
	for ( int h = 0; h < srcSize.cy; h++ )
	{
		for ( int w = 0; w < srcSize.cx; w++ )
		{
			if ( pSrc[w] & 0xFF )
				pSrc[w] = 0xFFFFFF | ((pSrc[w] & 0xFF) << 24);
			else
				pSrc[w] = 0;
		}
		pSrc += FONT_BITMAP_SIZE;
	}
	*/

	/*
	size.cx = min(srcSize.cx, FONT_TEXTURE_UNIT_SIZE);
	size.cy = min(srcSize.cy, FONT_TEXTURE_UNIT_SIZE);	
	*/
	/*
	DWORD *pSrc = s_pBitmapBits;
	if ( size.cy > m_height )
	{
		// 修正字元顯示高度
		pSrc += FONT_BITMAP_SIZE * (size.cy - m_height);
		size.cy = m_height;
		for ( pow.cy = 1; pow.cy < size.cy; pow.cy <<= 1 );
	}
	*/	
	
	/*
	DWORD *pSrc = s_pBitmapBits;
	for ( int h = 0; h < FONT_TEXTURE_UNIT_SIZE; h++ )
	{
		if ( h < size.cy )
		{
			for ( int w = 0; w < FONT_TEXTURE_UNIT_SIZE; w++ )
			{
				if ( w < size.cx && pSrc[w] & 0xFF )
					pPixels[w] = 0xFFFFFF | ((pSrc[w] & 0xFF) << 24);
				else
					pPixels[w] = 0;
			}
		}
		else
			memset(pPixels, 0, sizeof(DWORD) * FONT_TEXTURE_UNIT_SIZE);

		pSrc += FONT_BITMAP_SIZE;
		pPixels += FONT_TEXTURE_UNIT_SIZE;
	}
	*/
}

// --------------------------------------------------------------------------------------
CRuFontTexture::CRuFontTexture()
{
	m_pFontChar	= NULL;
	m_rect.Set(0.0f, 0.0f, 0.0f, 0.0f);
}

// --------------------------------------------------------------------------------------
CRuFontTexture::~CRuFontTexture()
{
}

// --------------------------------------------------------------------------------------
bool CRuFontTexture::Initialize( RuFontTextureCallBack func )
{
	int size, row, col;
	s_fontTexIndex.Clear();	
	s_freeFont.Clear();
	s_isLockRally	= false;
	s_pRuTexture	= NULL;
	s_rallyFunc		= NULL;

	/*
	if ( ( s_pRuTexture = (CRuTextureD3D*)g_ruEngineD3D->CreateTexture( FONT_TEXTURE_WIDTH,
		FONT_TEXTURE_HEIGHT, 1, ruSURFACEFMT_DXT5, ruRESPOOL_MANAGED ) ) == NULL )
		return false;
	*/
	if ( (s_pRuTexture = (CRuTextureD3D*)g_ruSceneRenderer->Ru2D_GetReservedDXT5Texture()) == NULL  )
		return false;
		
	// 計算貼圖總存放字元個數
	row = ((FONT_TEXTURE_HEIGHT / 4) * 3) / FONT_TEXTURE_UNIT_SIZE;
	col	= FONT_TEXTURE_WIDTH / FONT_TEXTURE_UNIT_SIZE;
	size = row * col;
	s_unitTexture = new CRuFontTexture[size];
	CRuFontTexture* fontTexture;
	for ( int i = 0; i < size; i++ )
	{
		fontTexture = s_unitTexture + i;
		fontTexture->m_rect.m_left		= (float)((i % col) * FONT_TEXTURE_UNIT_SIZE);
		fontTexture->m_rect.m_top		= (float)((FONT_TEXTURE_HEIGHT / 4) + (i / col) * FONT_TEXTURE_UNIT_SIZE);
		fontTexture->m_rect.m_right		= fontTexture->m_rect.m_left;
		fontTexture->m_rect.m_bottom	= fontTexture->m_rect.m_top;
		fontTexture->m_pFontChar		= NULL;
		s_freeFont.Push(fontTexture);
	}

	s_pPixels = new DWORD[FONT_TEXTURE_UNIT_SIZE * FONT_TEXTURE_UNIT_SIZE * 2];	
	s_rallyFunc = func;
	return true;
}

// --------------------------------------------------------------------------------------
void CRuFontTexture::Shutdown()
{
	s_rallyFunc = NULL;
	s_fontTexIndex.Clear();	
	s_freeFont.Clear();
	
	if ( s_unitTexture )
	{
		delete [] s_unitTexture;
		s_unitTexture = NULL;
	}

	if ( s_pPixels )
	{
		delete s_pPixels;
		s_pPixels = NULL;
	}
}

// --------------------------------------------------------------------------------------
bool CRuFontTexture::ReadFontTexture( CRuFontChar *pFontChar )
{
	if ( s_pRuTexture == NULL && pFontChar == NULL )
		return false;

	CRuFontType *pFontType = pFontChar->m_pFontType;	
	int key = (pFontType->GetSerial() & 0xFFFF) << 16 | pFontChar->m_char;
	DWORD keyHash = RuGlobalHashGen().GetHashCode(key);
	if(s_fontTexIndex.Get(key, keyHash, pFontChar->m_pFontTex))
	{
		return true;
	}

	CRuFontTexture* pFontTexture = GetFreeFontTexture();
	if ( pFontTexture == NULL )
	{
		return false;
	}
	
	SIZE fontSize;
	pFontType->PaintToMem(pFontChar->m_char, s_pPixels, fontSize);
	pFontTexture->m_rect.m_right = pFontTexture->m_rect.m_left + fontSize.cx;
	pFontTexture->m_rect.m_bottom = pFontTexture->m_rect.m_top + fontSize.cy;
	
	/*
	// 檢查字型大小
	int max_height = max( size.cx, size.cy );
	if ( max_height > FONT_TEXTURE_UNIT_SIZE )
		return false;
	*/	

	int dxtWidth, dxtHeight;
	for ( dxtWidth = 2; dxtWidth < fontSize.cx; dxtWidth <<= 1 );
	for ( dxtHeight = 2; dxtHeight < fontSize.cy; dxtHeight <<= 1 );
	
	// compress source image(32bits) to DXT5
	int compressSize, mipmapLevels;
	CompressionOptions options;	
	options.textureFormat		= kDXT5;
	options.mipMapGeneration	= kNoMipMaps;
	BYTE *pDxt = CompressDXT((BYTE*)s_pPixels, dxtWidth, dxtHeight, compressSize, mipmapLevels, &options);
	if ( pDxt )
	{
		BYTE *pRow = NULL;
		INT32 pitch;
		if ( s_pRuTexture->Lock( 0, (void**)&pRow, &pitch ) )
		{
			int x = (int)pFontTexture->m_rect.m_left;
			int y = (int)pFontTexture->m_rect.m_top;
			int width = min(dxtWidth, FONT_TEXTURE_UNIT_SIZE);
			BYTE *pDest = pRow + (y * pitch / 4) + x * 4;
			BYTE *pSrc	= pDxt;

			for ( int h = 0; h < FONT_TEXTURE_UNIT_SIZE; h += 4 )
			{
				memcpy(pDest, pSrc, width * 4);
				pDest += pitch;
				pSrc += dxtWidth * 4;
			}

			s_pRuTexture->Unlock(0);

			// FontChar 記錄 Texture 及字元大小資訊
			pFontChar->m_pFontTex = pFontTexture;
			pFontChar->m_y = (float)pFontType->GetHeight();
			pFontChar->m_x = (float)pFontChar->m_y * fontSize.cx / fontSize.cy;

			// FontTexture 記錄那一個 FontChar 取用這筆資料
			pFontTexture->m_pFontChar = pFontChar;

			// 設定資料已被取用
			s_fontTexIndex.Add(key, keyHash, pFontTexture);

			return true;
		}

		delete [] pDxt;
	}

	Recycle( pFontTexture );
	return false;
}

// --------------------------------------------------------------------------------------
void CRuFontTexture::PaintBegin()
{		
	g_ruSceneRenderer->Ru2D_SetTexture( s_pRuTexture );	
	g_ruSceneRenderer->Ru2D_SetEffectType( ru2DFXTYPE_BLIT_FILTER );
	//g_ruSceneRenderer->Ru2D_SetEffectType( ru2DFXTYPE_BLIT );
	g_ruSceneRenderer->Ru2D_SetEffectParameter( ru2DFXPARAM_ROTATE, ru2DROTATE_NONE );
}

// --------------------------------------------------------------------------------------
CRuFontTexture* CRuFontTexture::GetFreeFontTexture()
{	
	if ( s_freeFont.IsEmpty() )
		Rally();

	if ( s_freeFont.IsEmpty() == FALSE )
		return s_freeFont.Pop();

	return NULL;
}

// --------------------------------------------------------------------------------------
bool CRuFontTexture::Recycle( CRuFontTexture* pTexture )
{
	if ( pTexture->m_pFontChar )
	{
		pTexture->m_pFontChar->m_pFontTex = NULL;
		pTexture->m_pFontChar = NULL;
	}

	s_freeFont.Push(pTexture);
	return true;
}

// --------------------------------------------------------------------------------------
void CRuFontTexture::Rally()
{
	if ( !s_isLockRally )
	{
		s_isLockRally = true;

		int val = rand() % 3;

		s_removedFontTexList.Clear();

		s_fontTexIndex.ForAllElements(RallyCallback, reinterpret_cast<void *>(&val));

		for(INT32 i = 0; i < s_removedFontTexList.Count(); ++i)
		{
			INT32 key = s_removedFontTexList[i];
			DWORD keyHash = RuGlobalHashGen().GetHashCode(key);
			s_fontTexIndex.Remove(key, keyHash);
		}

		if ( s_rallyFunc )
			s_rallyFunc();

		s_isLockRally = false;
	}
}

// --------------------------------------------------------------------------------------
BOOL CRuFontTexture::RallyCallback(const void *key, const void *data, void *userData)
{
	CRuFontTexture *fontTexture = *((CRuFontTexture **) data);

	int &val = *reinterpret_cast<int *>(userData);

	if ( val > 1 )
	{
		val = 0;
		if ( Recycle( fontTexture ) )
			s_removedFontTexList.Add(*((int *) key));
	}
	else
		val++;

	return TRUE;
}

// --------------------------------------------------------------------------------------
CRuFontChar::CRuFontChar()
{
	m_pFontTex		= NULL;
	m_pFontType		= NULL;
	m_char			= 0;
	m_x				= 0;
	m_y				= 0;
}

// --------------------------------------------------------------------------------------
CRuFontChar::~CRuFontChar()
{
}

// --------------------------------------------------------------------------------------
void CRuFontChar::Initialize( RuFontTextureCallBack func )
{
	CRuFontType::Initialize();
	CRuFontTexture::Initialize( func );

	s_fontChar.Clear();
}

// --------------------------------------------------------------------------------------
void CRuFontChar::Shutdown()
{
	s_fontChar.Clear();

	CRuFontTexture::Shutdown();
	CRuFontType::Shutdown();	
}

// --------------------------------------------------------------------------------------
// 取得字元結構
CRuFontChar* CRuFontChar::GetFontChar( CRuFontType *pFontType, WCHAR wchar )
{
	assert( pFontType );

	int key = (pFontType->GetSerial() & 0xFFFF) << 16 | wchar;
	DWORD keyHash = RuGlobalHashGen().GetHashCode(key);
	CRuFontChar *fontChar = NULL;
	if(s_fontChar.Get(key, keyHash, fontChar))
	{
		return fontChar;
	}

	fontChar = ruNEW CRuFontChar();

	fontChar->m_char		= wchar;
	fontChar->m_pFontType	= pFontType;
	fontChar->m_x			= 0.0f;
	fontChar->m_y			= 0.0f;

	s_fontChar.Add(key, keyHash, fontChar);
	CRuFontTexture::ReadFontTexture(fontChar);

	return fontChar;
}

// --------------------------------------------------------------------------------------
// 繪出字串
bool CRuFontChar::DrawText( CRuFontType* pFontType, float x, float y, int color, const WCHAR* textStr )
{	
	if ( pFontType == NULL || textStr == NULL )
		return false;

	float x_draw = x;
	float y_draw = y;

	PaintBegin();
	for ( int i = 0; textStr[i]; i++ )
	{
		if ( textStr[i] == L'\n' )
		{
			x_draw = (float)x;
			y_draw += (float)pFontType->GetHeight();
		}
		else if ( textStr[i] >= L' ' )
		{
			CRuFontChar* pChar = GetFontChar( pFontType, textStr[i] );
			if ( pChar )
			{
				pChar->Paint( x_draw, y_draw, 1.0f, color );
				x_draw += (float)pChar->GetXSize();
			}
		}
	}
	return true;
}

// ----------------------------------------------------------------------------------
bool CRuFontChar::DrawTextA( CRuFontType* pFontType, float x, float y, int color, const char* textStr )
{
	// 轉換為雙字元組
	WCHAR* tmp = TransToWChar(textStr);
	bool result = DrawText( pFontType, x, y, color, tmp );
	if ( tmp ) delete [] tmp;
	return result;
}

// ----------------------------------------------------------------------------------
char *TransToChar(const WCHAR* text)
{
	if (text)
	{
		int size = wcslen(text);
		char* tmp = new char[size*2+1];
		WideCharToMultiByte(CP_ACP, 0, text, size+1, tmp, size*2+1, NULL, NULL);
		return tmp;
	}
	return NULL;
}

// ----------------------------------------------------------------------------------
WCHAR *TransToWChar(const char* text)
{	
	if (text)
	{
		int size = strlen(text);
		WCHAR* tmp = new WCHAR[size+1];
		MultiByteToWideChar(CP_ACP, 0, text, size+1, tmp, size+1);
		return tmp;
	}
	return NULL;
}
