/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

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.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
///////////////////////////////////////////////////////////////////////////////
//																			 //
//	plTextGenerator Class Functions											 //
//	Cyan, Inc.																 //
//																			 //
//// Version History //////////////////////////////////////////////////////////
//																			 //
//	12.13.2001 mcn - Created.  												 //
//																			 //
///////////////////////////////////////////////////////////////////////////////

#include "hsWindows.h"
#include "hsTypes.h"
#include "hsMatrix44.h"
#include "pnKeyedObject/hsKeyedObject.h"
#include "plTextGenerator.h"
#include "plGImage/plMipmap.h"
#include "plPipeline/hsGDeviceRef.h"
#include "pnMessage/plRefMsg.h"

#include "plgDispatch.h"
#include "hsResMgr.h"

// Because tempKeys haven't been fixed yet (mf says to blame Eric Ellis), reffing
// objects when we have a tempKey (or they have a tempKey) just don't work. In
// fact, it will do nasty things like crashing on shutdown. Until then, we simply
// won't do the refs. Note that this is BAD, but given the only time we currently
// use these objects are very limited, controlled cases that *should* be okay
// for now, we should be reasonably safe. For now.
//#define MCN_DO_REFS

//// Constructor & Destructor /////////////////////////////////////////////////

plTextGenerator::plTextGenerator()
{
	fHost = nil;
}

plTextGenerator::plTextGenerator( plMipmap *host, UInt16 width, UInt16 height )
{
	fHost = nil;
	Attach( host, width, height );
}

plTextGenerator::~plTextGenerator()
{
	// This also won't work until tempKeys work, since the mipmap will be gone by 
	// this time, in which case, calling Detach() crashes
#ifdef MCN_DO_REFS
	Detach();
#endif
}

//// Attach ///////////////////////////////////////////////////////////////////
//	Grab onto a plMipmap, suck the texture out of it and replace it with our
//	own.		

void	plTextGenerator::Attach( plMipmap *host, UInt16 width, UInt16 height )
{
	UInt16		textWidth, textHeight;


	hsAssert( fHost == nil, "Attempting to attach an already attached plTextGenerator" );

	fHost = host;

	/// Suck the old texture data out
	fHost->Reset();

	/// Make some new

	// Note that we need POW-2 textures, so we go for the next one up that will
	// fit what we need
	for( textWidth = 1; textWidth < width; textWidth <<= 1 );
	for( textHeight = 1; textHeight < height; textHeight <<= 1 );

	fWidth = width;
	fHeight = height;
	fHost->fImage = (void *)IAllocateOSSurface( textWidth, textHeight );
	fHost->SetConfig( plMipmap::kARGB32Config );
	fHost->fWidth = textWidth;
	fHost->fHeight = textHeight;
	fHost->fPixelSize = 32;
	fHost->fRowBytes = textWidth * 4;
	fHost->fNumLevels = 1;
	fHost->fFlags |= plMipmap::kUserOwnsBitmap | plMipmap::kDontThrowAwayImage;
	fHost->fCompressionType = plMipmap::kUncompressed;
	fHost->fUncompressedInfo.fType = plMipmap::UncompressedInfo::kRGB8888;
	fHost->IBuildLevelSizes();
	fHost->fTotalSize = fHost->GetLevelSize( 0 );

	// Destroy the old texture ref, since it's probably completely nutsoid at this point.
	// This should force the pipeline to recreate one more suitable for our use
	fHost->SetDeviceRef( nil );

	// Some init color
	hsColorRGBA	color;
	color.Set( 0.f, 0.f, 0.f, 1.f );
	ClearToColor( color );
	FlushToHost();

#ifdef MCN_DO_REFS
	/// Of course, brilliantly enough, if we did an attach on the constructor, we don't have a key
	/// yet, so we better give ourselves one before we can call AddViaNotify()
	if( GetKey() == nil )
	{
		char	str[ 256 ];
		sprintf( str, "plTextGen:%s", fHost->GetKeyName() );
		hsgResMgr::ResMgr()->NewKey( str, this, plLocation::kGlobalFixedLoc );
	}

	/// Send ourselves a passive ref of the mipmap, so we get notified if and when it goes away
	hsgResMgr::ResMgr()->AddViaNotify( fHost->GetKey(), TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, 0, 0 ), plRefFlags::kActiveRef );
#endif
	/// All done!
}

//// IAllocateOSSurface ///////////////////////////////////////////////////////
//	OS-specific. Allocates a rectangular bitmap of the given dimensions that
//	the OS can draw text into. Returns a pointer to the pixels.

UInt32		*plTextGenerator::IAllocateOSSurface( UInt16 width, UInt16 height )
{
#if HS_BUILD_FOR_WIN32

	BITMAPINFO	bmi;


	// Create a new DC and bitmap that we can draw characters to
	memset( &bmi.bmiHeader, 0, sizeof( BITMAPINFOHEADER ) );
	bmi.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
	bmi.bmiHeader.biWidth = width;
	bmi.bmiHeader.biHeight = -(int)height;
	bmi.bmiHeader.biPlanes = 1;
	bmi.bmiHeader.biCompression = BI_RGB;
	bmi.bmiHeader.biBitCount = 32;
	
	fWinRGBDC = CreateCompatibleDC( nil );
	fWinRGBBitmap = CreateDIBSection( fWinRGBDC, &bmi, DIB_RGB_COLORS, (void **)&fWinRGBBits, nil, 0 );
	SetMapMode( fWinRGBDC, MM_TEXT );
	SetBkMode( fWinRGBDC, TRANSPARENT );
	SetTextAlign( fWinRGBDC, TA_TOP | TA_LEFT );

	SelectObject( fWinRGBDC, fWinRGBBitmap );

	// Now create a second DC/bitmap combo, this one for writing alpha values to
	memset( &bmi.bmiHeader, 0, sizeof( BITMAPINFOHEADER ) );
	bmi.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
	bmi.bmiHeader.biWidth = width;
	bmi.bmiHeader.biHeight = -(int)height;
	bmi.bmiHeader.biPlanes = 1;
	bmi.bmiHeader.biCompression = BI_RGB;
	bmi.bmiHeader.biBitCount = 8;
	
	fWinAlphaDC = CreateCompatibleDC( nil );
	fWinAlphaBitmap = CreateDIBSection( fWinAlphaDC, &bmi, DIB_RGB_COLORS, (void **)&fWinAlphaBits, nil, 0 );
	SetMapMode( fWinAlphaDC, MM_TEXT );
	SetBkMode( fWinAlphaDC, TRANSPARENT );
	SetTextAlign( fWinAlphaDC, TA_TOP | TA_LEFT );

	SelectObject( fWinAlphaDC, fWinAlphaBitmap );

	return (UInt32 *)fWinRGBBits;
#endif
}

//// Detach ///////////////////////////////////////////////////////////////////
//	Release the mipmap unto itself.

void	plTextGenerator::Detach( void )
{
	if( fHost == nil )
		return;
//	hsAssert( fHost != nil, "Attempting to detach unattached host" );

	SetFont( nil, 0 );
	IDestroyOSSurface();

	fHost->Reset();
	fHost->fFlags &= ~( plMipmap::kUserOwnsBitmap | plMipmap::kDontThrowAwayImage );

	// Destroy the old texture ref, since we're no longer using it
	fHost->SetDeviceRef( nil );

	plMipmap	*oldHost = fHost;

	fHost = nil;

#ifdef MCN_DO_REFS
	// Now send ourselves a unref msg, just in case we were called directly (if this was done by 
	// message, we'll get called a few times, but that's ok, we're set up to handle that, and it
	// won't happen 'cept on destruction so the speed penalty shouldn't be a problem)
	GetKey()->Release( oldHost->GetKey() );
#endif
}

//// IDestroyOSSurface ////////////////////////////////////////////////////////
//	Opposite of allocate. DUH!

void	plTextGenerator::IDestroyOSSurface( void )
{
#if HS_BUILD_FOR_WIN32

	fHost->fImage = nil;	// DeleteObject() will get rid of it for us
	DeleteObject( fWinRGBBitmap );
	DeleteDC( fWinRGBDC );

	DeleteObject( fWinAlphaBitmap );
	DeleteDC( fWinAlphaDC );
	
#endif
}

//// ClearToColor /////////////////////////////////////////////////////////////

void	plTextGenerator::ClearToColor( hsColorRGBA &color )
{
	int		i;
	UInt32	*data = (UInt32 *)fHost->fImage;
	UInt32	hexColor = color.ToARGB32();

#if HS_BUILD_FOR_WIN32
	GdiFlush();
#endif

	for( i = 0; i < fHost->fWidth * fHost->fHeight; i++ )
		data[ i ] = hexColor;

	// Fill our alpha bitmap as well, since we use that too
#if HS_BUILD_FOR_WIN32
	memset( fWinAlphaBits, (UInt8)( color.a * 255.f ), fHost->fWidth * fHost->fHeight );
#endif
}

//// SetFont //////////////////////////////////////////////////////////////////
//	OS-specific. Load the given font for drawing the text with.

void	plTextGenerator::SetFont( const char *face, UInt16 size, hsBool antiAliasRGB )
{
#if HS_BUILD_FOR_WIN32
	if( fWinFont != nil )
	{
		DeleteObject( fWinFont );
		fWinFont = nil;
	}
	if( fWinAlphaFont != nil )
	{
		DeleteObject( fWinAlphaFont );
		fWinAlphaFont = nil;
	}

	if( face != nil )
	{
		int nHeight = -MulDiv( size, GetDeviceCaps( fWinRGBDC, LOGPIXELSY ), 72 );
		fWinFont = CreateFont( nHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
								CLIP_DEFAULT_PRECIS, antiAliasRGB ? ANTIALIASED_QUALITY : DEFAULT_QUALITY, VARIABLE_PITCH, face );
		hsAssert( fWinFont != nil, "Cannot create Windows font for plTextGenerator" );

		// The font for the alpha channel is identical except that it's antialiased, whereas the RGB version isn't.
		fWinAlphaFont = CreateFont( nHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
								CLIP_DEFAULT_PRECIS, (!antiAliasRGB) ? ANTIALIASED_QUALITY : DEFAULT_QUALITY, VARIABLE_PITCH, face );
		hsAssert( fWinAlphaFont != nil, "Cannot create Windows font for plTextGenerator" );

		SelectObject( fWinRGBDC, fWinFont );
		SelectObject( fWinAlphaDC, fWinAlphaFont );
	}
#endif
}

//// SetTextColor /////////////////////////////////////////////////////////////
//	blockRGB basically forces the RGB channel to write in blocks instead of
//	actual characters. This isn't useful unless you're relying on the alpha
//	channel to do the text (opaque text and transparent background), in which
//	case you want plenty of block color in your RGB channel because it'll get
//	alpha-ed out by the alpha channel.

void	plTextGenerator::SetTextColor( hsColorRGBA &color, hsBool blockRGB )
{
#if HS_BUILD_FOR_WIN32
	int r = (int)(color.r * 255.f);
	int g = (int)(color.g * 255.f);
	int b = (int)(color.b * 255.f);
	int a = (int)(color.a * 255.f);

	if( blockRGB )
	{
		::SetBkColor( fWinRGBDC, RGB( r, g, b ) );
		::SetBkMode( fWinRGBDC, OPAQUE );
	}
	else
		::SetBkMode( fWinRGBDC, TRANSPARENT );

	::SetTextColor( fWinRGBDC, RGB( r, g, b ) );
	::SetTextColor( fWinAlphaDC, RGB( a, a, a ) );
#endif
}

//// DrawString ///////////////////////////////////////////////////////////////

void	plTextGenerator::DrawString( UInt16 x, UInt16 y, const char *text )
{
	wchar_t *wText = hsStringToWString(text);
	DrawString(x,y,wText);
	delete [] wText;
}

void	plTextGenerator::DrawString( UInt16 x, UInt16 y, const wchar_t *text )
{
#if HS_BUILD_FOR_WIN32
	
	::TextOutW( fWinRGBDC, x, y, text, wcslen( text ) );
	::TextOutW( fWinAlphaDC, x, y, text, wcslen( text ) );

#endif
}

//// DrawClippedString ////////////////////////////////////////////////////////

void	plTextGenerator::DrawClippedString( Int16 x, Int16 y, const char *text, UInt16 width, UInt16 height )
{
	wchar_t *wText = hsStringToWString(text);
	DrawClippedString(x,y,wText,width,height);
	delete [] wText;
}

void	plTextGenerator::DrawClippedString( Int16 x, Int16 y, const wchar_t *text, UInt16 width, UInt16 height )
{
#if HS_BUILD_FOR_WIN32
	
	RECT	r;
	::SetRect( &r, x, y, x + width, y + height );

	::ExtTextOutW( fWinRGBDC, x, y, ETO_CLIPPED, &r, text, wcslen( text ), nil );
	::ExtTextOutW( fWinAlphaDC, x, y, ETO_CLIPPED, &r, text, wcslen( text ), nil );

#endif
}

//// DrawClippedString ////////////////////////////////////////////////////////

void	plTextGenerator::DrawClippedString( Int16 x, Int16 y, const char *text, UInt16 clipX, UInt16 clipY, UInt16 width, UInt16 height )
{
	wchar_t *wText = hsStringToWString(text);
	DrawClippedString(x,y,wText,clipX,clipY,width,height);
	delete [] wText;
}

void	plTextGenerator::DrawClippedString( Int16 x, Int16 y, const wchar_t *text, UInt16 clipX, UInt16 clipY, UInt16 width, UInt16 height )
{
#if HS_BUILD_FOR_WIN32
	
	RECT	r;
	::SetRect( &r, clipX, clipY, clipX + width, clipY + height );

	::ExtTextOutW( fWinRGBDC, x, y, ETO_CLIPPED, &r, text, wcslen( text ), nil );
	::ExtTextOutW( fWinAlphaDC, x, y, ETO_CLIPPED, &r, text, wcslen( text ), nil );

#endif
}

//// DrawWrappedString ////////////////////////////////////////////////////////

void	plTextGenerator::DrawWrappedString( UInt16 x, UInt16 y, const char *text, UInt16 width, UInt16 height )
{
	wchar_t *wText = hsStringToWString(text);
	DrawWrappedString(x,y,wText,width,height);
	delete [] wText;
}

void	plTextGenerator::DrawWrappedString( UInt16 x, UInt16 y, const wchar_t *text, UInt16 width, UInt16 height )
{
#if HS_BUILD_FOR_WIN32
	
	RECT	r;
	::SetRect( &r, x, y, x + width, y + height );

//	HBRUSH brush = ::CreateSolidBrush( RGB( 255, 255, 255 ) );
//	::FillRect( fWinRGBDC, &r, brush );
//	::DeleteObject( brush );
	::DrawTextW( fWinRGBDC, text, wcslen( text ), &r, 
				DT_TOP | DT_LEFT | DT_NOPREFIX | DT_WORDBREAK );
	::DrawTextW( fWinAlphaDC, text, wcslen( text ), &r, 
				DT_TOP | DT_LEFT | DT_NOPREFIX | DT_WORDBREAK );

#endif
}

//// CalcStringWidth //////////////////////////////////////////////////////////

UInt16		plTextGenerator::CalcStringWidth( const char *text, UInt16 *height )
{
	wchar_t *wText = hsStringToWString(text);
	UInt16 retVal = CalcStringWidth(wText,height);
	delete [] wText;
	return retVal;
}

UInt16		plTextGenerator::CalcStringWidth( const wchar_t *text, UInt16 *height )
{
#if HS_BUILD_FOR_WIN32

	SIZE size;
	::GetTextExtentPoint32W( fWinRGBDC, text, wcslen( text ), &size );

	if( height != nil )
		*height = (UInt16)size.cy;

	return (UInt16)size.cx;
#endif
}

//// CalcWrappedStringSize ////////////////////////////////////////////////////

void	plTextGenerator::CalcWrappedStringSize( const char *text, UInt16 *width, UInt16 *height )
{
	wchar_t *wText = hsStringToWString(text);
	CalcWrappedStringSize(wText,width,height);
	delete [] wText;
}

void	plTextGenerator::CalcWrappedStringSize( const wchar_t *text, UInt16 *width, UInt16 *height )
{
#if HS_BUILD_FOR_WIN32

	RECT	r;
	::SetRect( &r, 0, 0, *width, 0 );

	::DrawTextW( fWinRGBDC, text, wcslen( text ), &r, DT_TOP | DT_LEFT | DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT );

	*width = (UInt16)(r.right);
	if( height != nil )
		*height = (UInt16)r.bottom;
#endif
}

//// FillRect /////////////////////////////////////////////////////////////////

void	plTextGenerator::FillRect( UInt16 x, UInt16 y, UInt16 width, UInt16 height, hsColorRGBA &color )
{
#if HS_BUILD_FOR_WIN32

	RECT	rc;
	::SetRect( &rc, x, y, x + width, y + height );

	int r = (int)(color.r * 255.f);
	int g = (int)(color.g * 255.f);
	int b = (int)(color.b * 255.f);
	int a = (int)(color.a * 255.f);

	HBRUSH brush = ::CreateSolidBrush( RGB( r, g, b ) );
	::FillRect( fWinRGBDC, &rc, brush );
	::DeleteObject( brush );

	brush = ::CreateSolidBrush( RGB( a, a, a ) );
	::FillRect( fWinAlphaDC, &rc, brush );
	::DeleteObject( brush );
#endif
}

//// FrameRect ////////////////////////////////////////////////////////////////

void	plTextGenerator::FrameRect( UInt16 x, UInt16 y, UInt16 width, UInt16 height, hsColorRGBA &color )
{
#if HS_BUILD_FOR_WIN32

	RECT	rc;
	::SetRect( &rc, x, y, x + width, y + height );

	int r = (int)(color.r * 255.f);
	int g = (int)(color.g * 255.f);
	int b = (int)(color.b * 255.f);
	int a = (int)(color.a * 255.f);

	HBRUSH brush = ::CreateSolidBrush( RGB( r, g, b ) );
	::FrameRect( fWinRGBDC, &rc, brush );
	::DeleteObject( brush );

	brush = ::CreateSolidBrush( RGB( a, a, a ) );
	::FrameRect( fWinAlphaDC, &rc, brush );
	::DeleteObject( brush );
#endif
}

//// FlushToHost //////////////////////////////////////////////////////////////

void	plTextGenerator::FlushToHost( void )
{
#if HS_BUILD_FOR_WIN32
	// Flush the GDI first, to make sure it's done
	GdiFlush();

	// Now copy our alpha channel over. I hate the GDI
	UInt32		i = fHost->fWidth * fHost->fHeight;
	UInt32		*dest = fWinRGBBits;
	UInt8		*src = fWinAlphaBits;

/*	while( i-- )
	{
		*dest &= 0x00ffffff;
		*dest |= ( *src ) << 24;
//		*dest |= ( *dest << 16 ) & 0xff000000;
		dest++;
		src++;
	}
*/
	do
	{
		i--;
		dest[ i ] &= 0x00ffffff;
		dest[ i ] |= src[ i ] << 24;
	} while( i );
#endif

	// Dirty the mipmap's deviceRef, if there is one
	if( fHost->GetDeviceRef() != nil )
		fHost->GetDeviceRef()->SetDirty( true );
}

//// GetTextWidth/Height //////////////////////////////////////////////////////

UInt16	plTextGenerator::GetTextWidth( void )
{
	return ( fHost != nil ) ? (UInt16)(fHost->fWidth) : 0;
}

UInt16	plTextGenerator::GetTextHeight( void )
{
	return ( fHost != nil ) ? (UInt16)(fHost->fHeight) : 0;
}

//// GetLayerTransform ////////////////////////////////////////////////////////
//	Since the textGen can actually create a texture bigger than you were expecting,
//	you want to be able to apply a layer texture transform that will compensate. This
//	function will give you that transform. Just feed it into plLayer->SetTransform().

hsMatrix44	plTextGenerator::GetLayerTransform( void )
{
	hsMatrix44	xform;
	hsVector3	scale;

	scale.Set( (float)GetWidth() / (float)GetTextWidth(), 
			   (float)GetHeight() / (float)GetTextHeight(), 1.f );

	xform.MakeScaleMat( &scale );
	return xform;
}

//// MsgReceive ///////////////////////////////////////////////////////////////

hsBool	plTextGenerator::MsgReceive( plMessage *msg )
{
#ifdef MCN_DO_REFS
	plGenRefMsg	*refMsg = plGenRefMsg::ConvertNoRef( msg );
	if( refMsg != nil )
	{
		if( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest ) )
		{
			// Don't do anything--already did an attach
		}
		else if( refMsg->GetContext() & ( plRefMsg::kOnDestroy | plRefMsg::kOnRemove ) )
		{
			Detach();
		}
		return true;
	}
#endif

	return hsKeyedObject::MsgReceive( msg );
}