//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=====================================================================================//

#pragma warning( disable : 4244 ) // conversion from 'double' to 'float', possible loss of data

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <malloc.h>
#include "vgui_surfacelib/Win32Font.h"
#include <tier0/dbg.h>
#include <vgui/ISurface.h>
#include <tier0/mem.h>
#include <utlbuffer.h>
#include "FontEffects.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

static OSVERSIONINFO s_OsVersionInfo;
static bool s_bOsVersionInitialized = false;
bool s_bSupportsUnicode = false;

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CWin32Font::CWin32Font() : m_ExtendedABCWidthsCache(256, 0, &ExtendedABCWidthsCacheLessFunc)
{
	m_szName = UTL_INVAL_SYMBOL;
	m_iTall = 0;
	m_iWeight = 0;
	m_iHeight = 0;
	m_iAscent = 0;
	m_iFlags = 0;
	m_iMaxCharWidth = 0;
	m_hFont = NULL;
	m_hDC = NULL;
	m_hDIB = NULL;
	m_bAntiAliased = false;
	m_bUnderlined = false;
	m_iBlur = 0;
	m_iScanLines = 0;
	m_bRotary = false;
	m_bAdditive = false;
	m_rgiBitmapSize[ 0 ] = m_rgiBitmapSize[ 1 ] = 0;

#if defined( _X360 )
	Q_memset( m_ABCWidthsCache, 0, sizeof( m_ABCWidthsCache ) );
#endif

	m_ExtendedABCWidthsCache.EnsureCapacity( 128 );

	if ( !s_bOsVersionInitialized )
	{
		// get the operating system version
		s_bOsVersionInitialized = true;
		memset(&s_OsVersionInfo, 0, sizeof(s_OsVersionInfo));
		s_OsVersionInfo.dwOSVersionInfoSize = sizeof(s_OsVersionInfo);
		GetVersionEx(&s_OsVersionInfo);

		if (s_OsVersionInfo.dwMajorVersion >= 5)
		{
			s_bSupportsUnicode = true;
		}
		else
		{
			s_bSupportsUnicode = false;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CWin32Font::~CWin32Font()
{
	if ( m_hFont )
		::DeleteObject( m_hFont );
	if ( m_hDC )
		::DeleteDC( m_hDC );
	if ( m_hDIB )
		::DeleteObject( m_hDIB );
}

//-----------------------------------------------------------------------------
// Purpose: Font iteration callback function
//			used to determine whether or not a font exists on the system
//-----------------------------------------------------------------------------
extern bool g_bFontFound = false;
int CALLBACK FontEnumProc( 
	const LOGFONT *lpelfe,		// logical-font data
	const TEXTMETRIC *lpntme,	// physical-font data
	DWORD FontType,				// type of font
	LPARAM lParam )				// application-defined data
{
	g_bFontFound = true;
	return 0;
}

//-----------------------------------------------------------------------------
// Purpose: creates the font from windows.  returns false if font does not exist in the OS.
//-----------------------------------------------------------------------------
bool CWin32Font::Create(const char *windowsFontName, int tall, int weight, int blur, int scanlines, int flags)
{
	// setup font properties
	m_szName = windowsFontName;
	m_iTall = tall;
	m_iWeight = weight;
	m_iFlags = flags;
	m_bAntiAliased = (flags & vgui::ISurface::FONTFLAG_ANTIALIAS) ? 1 : 0;
	m_bUnderlined = flags & vgui::ISurface::FONTFLAG_UNDERLINE;
	m_iDropShadowOffset = (flags & vgui::ISurface::FONTFLAG_DROPSHADOW) ? 1 : 0;
	m_iOutlineSize = (flags & vgui::ISurface::FONTFLAG_OUTLINE) ? 1 : 0;
	m_iBlur = blur;
	m_iScanLines = scanlines;
	m_bRotary = (flags & vgui::ISurface::FONTFLAG_ROTARY) ? 1 : 0;
	m_bAdditive = (flags & vgui::ISurface::FONTFLAG_ADDITIVE) ? 1 : 0;

	int charset = (flags & vgui::ISurface::FONTFLAG_SYMBOL) ? SYMBOL_CHARSET : ANSI_CHARSET;

	// hack for japanese win98 support
	if ( !stricmp( windowsFontName, "win98japanese" ) )
	{
		// use any font that contains the japanese charset
		charset = SHIFTJIS_CHARSET;
		m_szName = "Tahoma";
	}

	// create our windows device context
	m_hDC = ::CreateCompatibleDC(NULL);
	Assert( m_hDC );

	// see if the font exists on the system
	LOGFONT logfont;
	logfont.lfCharSet = DEFAULT_CHARSET;
	logfont.lfPitchAndFamily = 0;
	strcpy(logfont.lfFaceName, m_szName.String());
	g_bFontFound = false;
	::EnumFontFamiliesEx(m_hDC, &logfont, &FontEnumProc, 0, 0);
	if (!g_bFontFound)
	{
		// needs to go to a fallback
		m_szName = UTL_INVAL_SYMBOL;
		return false;
	}

	m_hFont = ::CreateFontA(tall, 0, 0, 0, 
								m_iWeight, 
								flags & vgui::ISurface::FONTFLAG_ITALIC, 
								flags & vgui::ISurface::FONTFLAG_UNDERLINE, 
								flags & vgui::ISurface::FONTFLAG_STRIKEOUT, 
								charset, 
								OUT_DEFAULT_PRECIS, 
								CLIP_DEFAULT_PRECIS, 
								m_bAntiAliased ? ANTIALIASED_QUALITY : NONANTIALIASED_QUALITY, 
								DEFAULT_PITCH | FF_DONTCARE, 
								windowsFontName);
	if (!m_hFont)
	{
		Error("Couldn't create windows font '%s'\n", windowsFontName);
		m_szName = UTL_INVAL_SYMBOL;
		return false;
	}

	// set as the active font
	::SetMapMode(m_hDC, MM_TEXT);
	::SelectObject(m_hDC, m_hFont);
	::SetTextAlign(m_hDC, TA_LEFT | TA_TOP | TA_UPDATECP);

	// get info about the font
	::TEXTMETRIC tm;
	memset( &tm, 0, sizeof( tm ) );
	if ( !GetTextMetrics(m_hDC, &tm) )
	{
		m_szName = UTL_INVAL_SYMBOL;
		return false;
	}

	m_iHeight = tm.tmHeight + m_iDropShadowOffset + 2 * m_iOutlineSize;
	m_iMaxCharWidth = tm.tmMaxCharWidth;
	m_iAscent = tm.tmAscent;

	// code for rendering to a bitmap
	m_rgiBitmapSize[0] = tm.tmMaxCharWidth + m_iOutlineSize * 2;
	m_rgiBitmapSize[1] = tm.tmHeight + m_iDropShadowOffset + m_iOutlineSize * 2;

	::BITMAPINFOHEADER header;
	memset(&header, 0, sizeof(header));
	header.biSize = sizeof(header);
	header.biWidth = m_rgiBitmapSize[0];
	header.biHeight = -m_rgiBitmapSize[1];
	header.biPlanes = 1;
	header.biBitCount = 32;
	header.biCompression = BI_RGB;

	m_hDIB = ::CreateDIBSection(m_hDC, (BITMAPINFO*)&header, DIB_RGB_COLORS, (void**)(&m_pBuf), NULL, 0);
	::SelectObject(m_hDC, m_hDIB);

#if defined( _X360 )
	// get char spacing
	// a is space before character (can be negative)
	// b is the width of the character
	// c is the space after the character
	memset(m_ABCWidthsCache, 0, sizeof(m_ABCWidthsCache));
	ABC abc[ABCWIDTHS_CACHE_SIZE];
	Assert(ABCWIDTHS_CACHE_SIZE <= 256);
	if (::GetCharABCWidthsW(m_hDC, 0, ABCWIDTHS_CACHE_SIZE - 1, &abc[0]) || ::GetCharABCWidthsA(m_hDC, 0, ABCWIDTHS_CACHE_SIZE - 1, &abc[0]))
	{	
		// copy out into our formated structure
		for (int i = 0; i < ABCWIDTHS_CACHE_SIZE; i++)
		{
			m_ABCWidthsCache[i].a = abc[i].abcA - m_iBlur - m_iOutlineSize;
			m_ABCWidthsCache[i].b = abc[i].abcB + ((m_iBlur + m_iOutlineSize) * 2) + m_iDropShadowOffset;
			m_ABCWidthsCache[i].c = abc[i].abcC - m_iBlur - m_iDropShadowOffset - m_iOutlineSize;
		}
	}
	else
	{
		Warning("GetCharABCWidths() failed for windows font '%s'\n", windowsFontName);

		// since that failed, it must be fixed width, zero everything so a and c will be zeros, then
		// fill b with the value from TEXTMETRIC
		for (int i = 0; i < ABCWIDTHS_CACHE_SIZE; i++)
		{
			// fallback to old method, no underhangs/overhangs (a/c)
			SIZE size;
			char mbcs[6] = { 0 };
			wchar_t wch = (wchar_t)i;
			::WideCharToMultiByte(CP_ACP, 0, &wch, 1, mbcs, sizeof(mbcs), NULL, NULL);
			if (::GetTextExtentPoint32(m_hDC, mbcs, strlen(mbcs), &size))
			{
				m_ABCWidthsCache[i].b = size.cx;
			}
			else
			{
				// failed to get width, just use the average width
				m_ABCWidthsCache[i].b = (char)tm.tmAveCharWidth;
			}
		}
	}
#endif

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: writes the char into the specified 32bpp texture
//-----------------------------------------------------------------------------
void CWin32Font::GetCharRGBA(wchar_t ch, int rgbaWide, int rgbaTall, unsigned char *rgba)
{
	int a, b, c;
	GetCharABCWidths(ch, a, b, c);

	// set us up to render into our dib
	::SelectObject(m_hDC, m_hFont);

	int wide = b;
	if ( m_bUnderlined )
	{
		wide += ( a + c );
	}

	int tall = m_iHeight;
	GLYPHMETRICS glyphMetrics;
	MAT2 mat2 = { { 0, 1}, { 0, 0}, { 0, 0}, { 0, 1}};
	int bytesNeeded = 0;

	bool bShouldAntialias = m_bAntiAliased;
	// filter out 
	if ( ch > 0x00FF && !(m_iFlags & vgui::ISurface::FONTFLAG_CUSTOM) )
	{
		bShouldAntialias = false;
	}
	if ( !s_bSupportsUnicode )
	{
		// win98 hack, don't antialias some characters that ::GetGlyphOutline() produces bad results for
		if (ch == 'I' || ch == '1')
		{
			bShouldAntialias = false;
		}

		// don't antialias big fonts at all (since win98 often produces bad results)
		if (m_iHeight >= 13)
		{
			bShouldAntialias = false;
		}
	}


	// only antialias latin characters, since it essentially always fails for asian characters
	if (bShouldAntialias)
	{
		// try and get the glyph directly
		::SelectObject(m_hDC, m_hFont);
		bytesNeeded = ::GetGlyphOutline(m_hDC, ch, GGO_GRAY8_BITMAP, &glyphMetrics, 0, NULL, &mat2);
	}

	if (bytesNeeded > 0)
	{
		// take it
		unsigned char *lpbuf = (unsigned char *)_alloca(bytesNeeded);
		::GetGlyphOutline(m_hDC, ch, GGO_GRAY8_BITMAP, &glyphMetrics, bytesNeeded, lpbuf, &mat2);

		// rows are on DWORD boundaries
		wide = glyphMetrics.gmBlackBoxX;
		while (wide % 4 != 0)
		{
			wide++;
		}

		// see where we should start rendering
		int pushDown = m_iAscent - glyphMetrics.gmptGlyphOrigin.y;

		// set where we start copying from
		int xstart = 0;

		// don't copy the first set of pixels if the antialiased bmp is bigger than the char width
		if ((int)glyphMetrics.gmBlackBoxX >= b + 2)
		{
			xstart = (glyphMetrics.gmBlackBoxX - b) / 2;
		}

		// iterate through copying the generated dib into the texture
		for (unsigned int j = 0; j < glyphMetrics.gmBlackBoxY; j++)
		{
			for (unsigned int i = xstart; i < glyphMetrics.gmBlackBoxX; i++)
			{
				int x = i - xstart + m_iBlur + m_iOutlineSize;
				int y = j + pushDown;
				if ((x < rgbaWide) && (y < rgbaTall))
				{
					unsigned char grayscale = lpbuf[(j*wide+i)];

					float r, g, b, a;
					if (grayscale)
					{
						r = g = b = 1.0f;
						a = (grayscale + 0) / 64.0f;
						if (a > 1.0f) a = 1.0f;
					}
					else
					{
						r = g = b = a = 0.0f;
					}

					// Don't want anything drawn for tab characters.
					if (ch == '\t')
					{
						r = g = b = 0;
					}

					unsigned char *dst = &rgba[(y*rgbaWide+x)*4];
					dst[0] = (unsigned char)(r * 255.0f);
					dst[1] = (unsigned char)(g * 255.0f);
					dst[2] = (unsigned char)(b * 255.0f);
					dst[3] = (unsigned char)(a * 255.0f);
				}
			}
		}
	}
	else
	{
		// use render-to-bitmap to get our font texture
		::SetBkColor(m_hDC, RGB(0, 0, 0));
		::SetTextColor(m_hDC, RGB(255, 255, 255));
		::SetBkMode(m_hDC, OPAQUE);
		if ( m_bUnderlined )
		{
			::MoveToEx(m_hDC, 0, 0, NULL);
		}
		else
		{
			::MoveToEx(m_hDC, -a, 0, NULL);
		}

		// render the character
		wchar_t wch = (wchar_t)ch;
		
		if (s_bSupportsUnicode)
		{
			// clear the background first
			RECT rect = { 0, 0, wide, tall};
			::ExtTextOutW( m_hDC, 0, 0, ETO_OPAQUE, &rect, NULL, 0, NULL );

			// just use the unicode renderer
			::ExtTextOutW( m_hDC, 0, 0, 0, NULL, &wch, 1, NULL );
		}
		else
		{
			// clear the background first (it may not get done automatically in win98/ME
			RECT rect = { 0, 0, wide, tall};
			::ExtTextOut(m_hDC, 0, 0, ETO_OPAQUE, &rect, NULL, 0, NULL);

			// convert the character using the current codepage
			char mbcs[6] = { 0 };
			::WideCharToMultiByte(CP_ACP, 0, &wch, 1, mbcs, sizeof(mbcs), NULL, NULL);
			::ExtTextOutA(m_hDC, 0, 0, 0, NULL, mbcs, strlen(mbcs), NULL);
		}

		::SetBkMode(m_hDC, TRANSPARENT);

		if (wide > m_rgiBitmapSize[0])
		{
			wide = m_rgiBitmapSize[0];
		}
		if (tall > m_rgiBitmapSize[1])
		{
			tall = m_rgiBitmapSize[1];
		}

		// iterate through copying the generated dib into the texture
		for (int j = (int)m_iOutlineSize; j < tall - (int)m_iOutlineSize; j++ )
		{
			// only copy from within the dib, ignore the outline border we are artificially adding
			for (int i = (int)m_iOutlineSize; i < wide - (int)m_iDropShadowOffset - (int)m_iOutlineSize; i++)
			{
				if ((i < rgbaWide) && (j < rgbaTall))
				{
					unsigned char *src = &m_pBuf[(i + j*m_rgiBitmapSize[0])*4];
					unsigned char *dst = &rgba[(i + j*rgbaWide)*4];

					// Don't want anything drawn for tab characters.
					unsigned char r, g, b;
					if ( ch == '\t' )
					{
						r = g = b = 0;
					}
					else
					{
						r = src[0];
						g = src[1];
						b = src[2];
					}

					// generate alpha based on luminance conversion
					dst[0] = r;
					dst[1] = g;
					dst[2] = b;
					dst[3] = (unsigned char)((float)r * 0.34f + (float)g * 0.55f + (float)b * 0.11f);
				}
			}
		}

		// if we have a dropshadow, we need to clean off the bottom row of pixels
		// this is because of a bug in winME that writes noise to them, only on the first time the game is run after a reboot
		// the bottom row should guaranteed to be empty to fit the dropshadow
		if ( m_iDropShadowOffset )
		{
			unsigned char *dst = &rgba[((m_iHeight - 1) * rgbaWide) * 4];
			for (int i = 0; i < wide; i++)
			{
				dst[0] = 0;
				dst[1] = 0;
				dst[2] = 0;
				dst[3] = 0;
				dst += 4;
			}
		}
	}

	// apply requested effects in specified order
	ApplyDropShadowToTexture( rgbaWide, rgbaTall, rgba, m_iDropShadowOffset );
	ApplyOutlineToTexture( rgbaWide, rgbaTall, rgba, m_iOutlineSize );
	ApplyGaussianBlurToTexture( rgbaWide, rgbaTall, rgba, m_iBlur );
	ApplyScanlineEffectToTexture( rgbaWide, rgbaTall, rgba, m_iScanLines );
	ApplyRotaryEffectToTexture( rgbaWide, rgbaTall, rgba, m_bRotary );
}

//-----------------------------------------------------------------------------
// Purpose: returns true if the font is equivalent to that specified
//-----------------------------------------------------------------------------
bool CWin32Font::IsEqualTo(const char *windowsFontName, int tall, int weight, int blur, int scanlines, int flags)
{
	if ( !stricmp(windowsFontName, m_szName.String() ) 
		&& m_iTall == tall
		&& m_iWeight == weight
		&& m_iBlur == blur
		&& m_iFlags == flags)
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: returns true only if this font is valid for use
//-----------------------------------------------------------------------------
bool CWin32Font::IsValid()
{
	if ( m_szName.IsValid() && m_szName.String()[0] )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: set the font to be the one to currently draw with in the gdi
//-----------------------------------------------------------------------------
void CWin32Font::SetAsActiveFont(HDC hdc)
{
	Assert( IsValid() );
	::SelectObject( hdc, m_hFont );
}

//-----------------------------------------------------------------------------
// Purpose: gets the abc widths for a character
//-----------------------------------------------------------------------------
void CWin32Font::GetCharABCWidths(int ch, int &a, int &b, int &c)
{
	Assert( IsValid() );
#if defined( _X360 )
	if (ch < ABCWIDTHS_CACHE_SIZE)
	{
		// use the cache entry
		a = m_ABCWidthsCache[ch].a;
		b = m_ABCWidthsCache[ch].b;
		c = m_ABCWidthsCache[ch].c;
	}
	else
#endif
	{

		// look for it in the cache
		abc_cache_t finder = { (wchar_t)ch };

		unsigned short i = m_ExtendedABCWidthsCache.Find(finder);
		if (m_ExtendedABCWidthsCache.IsValidIndex(i))
		{
			a = m_ExtendedABCWidthsCache[i].abc.a;
			b = m_ExtendedABCWidthsCache[i].abc.b;
			c = m_ExtendedABCWidthsCache[i].abc.c;
			return;
		}

		// not in the cache, get from windows (this call is a little slow)
		ABC abc;
		if (::GetCharABCWidthsW(m_hDC, ch, ch, &abc) || ::GetCharABCWidthsA(m_hDC, ch, ch, &abc))
		{
			a = abc.abcA;
			b = abc.abcB;
			c = abc.abcC;
		}
		else
		{
			// wide character version failed, try the old api function
			SIZE size;
			char mbcs[6] = { 0 };
			wchar_t wch = ch;
			::WideCharToMultiByte(CP_ACP, 0, &wch, 1, mbcs, sizeof(mbcs), NULL, NULL);
			if (::GetTextExtentPoint32(m_hDC, mbcs, strlen(mbcs), &size))
			{
				a = c = 0;
				b = size.cx;
			}
			else
			{
				// failed to get width, just use the max width
				a = c = 0;
				b = m_iMaxCharWidth;
			}
		}

		// add to the cache
		finder.abc.a = a - m_iBlur - m_iOutlineSize;
		finder.abc.b = b + ((m_iBlur + m_iOutlineSize) * 2) + m_iDropShadowOffset;
		finder.abc.c = c - m_iBlur - m_iDropShadowOffset - m_iOutlineSize;
		m_ExtendedABCWidthsCache.Insert(finder);
	}
}

//-----------------------------------------------------------------------------
// Purpose: returns the height of the font, in pixels
//-----------------------------------------------------------------------------
int CWin32Font::GetHeight()
{
	Assert( IsValid() );
	return m_iHeight;
}

//-----------------------------------------------------------------------------
// Purpose: returns the requested height of the font
//-----------------------------------------------------------------------------
int CWin32Font::GetHeightRequested()
{
	assert(IsValid());
	return m_iTall;
}

//-----------------------------------------------------------------------------
// Purpose: returns the ascent of the font, in pixels (ascent=units above the base line)
//-----------------------------------------------------------------------------
int CWin32Font::GetAscent()
{
	Assert( IsValid() );
	return m_iAscent;
}

//-----------------------------------------------------------------------------
// Purpose: returns the maximum width of a character, in pixels
//-----------------------------------------------------------------------------
int CWin32Font::GetMaxCharWidth()
{
	Assert( IsValid() );
	return m_iMaxCharWidth;
}

//-----------------------------------------------------------------------------
// Purpose: returns the flags used to make this font, used by the dynamic resizing code
//-----------------------------------------------------------------------------
int CWin32Font::GetFlags()
{
	return m_iFlags;
}

//-----------------------------------------------------------------------------
// Purpose: Comparison function for abc widths storage
//-----------------------------------------------------------------------------
bool CWin32Font::ExtendedABCWidthsCacheLessFunc(const abc_cache_t &lhs, const abc_cache_t &rhs)
{
	return lhs.wch < rhs.wch;
}

//-----------------------------------------------------------------------------
// Purpose: Get the kerned size of a char, for win32 just pass thru for now
//-----------------------------------------------------------------------------
void CWin32Font::GetKernedCharWidth( wchar_t ch, wchar_t chBefore, wchar_t chAfter, float &wide, float &abcA )
{
	int a,b,c;
	GetCharABCWidths(ch, a, b, c );
	wide = ( a + b + c);
	abcA = a;
}