/******************************************************************************
 * $Id: gfx_font_opengl.cc,v 1.103 2006/10/10 04:14:55 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Font class (OpenGL).
 * @remarks	This code originated from GL-117 and was modified by Jim E. Brooks.
 *          Original authors: Thomas A. Drexl et al.
 *			GL-117 is a flight simulator program distributed under the GPL license.
 *
 *          Major modifications by J. Brooks:
 *          - rendering is done in 2D instead of 3D
 *          - TGA font data is precompiled (loading from TGA files still works)
 *          - added justification relative to a viewport edge
 *          - based on FontDesc class
 *          - limitation: newline/tabs no longer handled specially
 *
 *          Define DEBUG=1 to get error messages.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 ******************************************************************************/

#define GFX_FONT_OPENGL_CC 1
#include "gfx.hh"
#include "gfx_stream.hh"
#include "gfx_gfxsys.hh"
#include "gfx_rgb.hh"
#include "gfx_font.hh"
#include "gfx_texture.hh"
#include "gfx_tga.hh"
#include "gfx_gui.hh"
namespace gfx {

/*****************************************************************************
 * Constructor.
 *****************************************************************************/
Font::Font( FontDesc* desc, Gui* gui )
{
	mErrors		= 0;
	mGui		= gui;
	mFontDesc	= desc;
	mPadding	= 4;
	mHeight		= desc->mHeight;
	mStart		= desc->mStart;
	SetZoom( desc->mZoom );
	mLetters	= desc->mLetters;
	CLEAR( mLetterx, sizeof(mLetterx) );
	CLEAR( mLettery, sizeof(mLettery) );
	CLEAR( mLetterw, sizeof(mLetterw) );

	// Font data is stored in executable itself in Targa format.
	// Load Targa data into Texture object.
	Targa targa( desc->mData, desc->mDataLen ); ASSERT( targa.IfValid() );
	PostProcess( targa );
	ExtractLetters( targa );
	mTexture = new Texture2( targa, DO_MIPMAP ); ASSERT( targa.GetBufLen() == mTexture->GetByteCnt() );
	mTexture->SetMinFilter( Texture::eMinFilter_LINEAR_MIPMAP_LINEAR );
	mTexture->SetMagFilter( Texture::eMagFilter_LINEAR );
}
Font::~Font()
{
	delete mTexture;
}

/*****************************************************************************
 * Post-process font image (vestige from GL-117).
 *****************************************************************************/
void
Font::PostProcess( Targa& targa /*IN/OUT*/ )
{
	uchar* data    = targa.GetBuf();
	uint   width   = targa.GetWidth();
	uint   height  = targa.GetHeight();

	for ( uint i = 0; i < height; ++i )
	{
		for ( uint i2 = 0; i2 < width; ++i2 )
	    {
			uint n2 = (i * width + i2) * 4;
			ASSERT( n2+3 < targa.GetBufLen() );

			// alpha=255 or 0
			if ( data[n2+0] + data[n2+1] + data[n2+2] < 30 )
				data[n2+3] = 0;
			else
				data[n2+3] = 255;
		}
	}
}

/*****************************************************************************
 * Set font zoom (size).
 *****************************************************************************/
void
Font::SetZoom( float z )
{
	z -= 0.2;
	mZoom = z * 16;
}

/*****************************************************************************
 * This function is described at gfx_font.hh.
 *****************************************************************************/
int
Font::Print( const string& text, float x, float y, eFontMode mode, const RGBA rgba )
{
	return Print( text.c_str(), text.size(), x, y, mode, rgba );
}
int
Font::Print( const string& text, float x, float y, eFontMode mode )
{
	return Print( text.c_str(), text.size(), x, y, mode );
}
int
Font::Print( const char* text, uint textLen, float x, float y, eFontMode mode, const RGBA rgba )
{
	GFX_RGBA( rgba );
    return Print( text, textLen, x, y, mode );
}
int
Font::Print( const char* text, uint textLen, float x, float y, eFontMode mode )
{
	const uint texWidth  = mTexture->GetWidth();
	const uint texHeight = mTexture->GetHeight();

	if ( mode & eFontMode_MASK_JUSTIFY_CORNER )
	{
		x = ABS(x);
		y = ABS(y);
	}

	mErrors				= 0;
	const int	left	= int(mGui->mViewport.x                    ) + mPadding;
	const int	right	= int(mGui->mViewport.x + mGui->mViewport.w) - mPadding;
	const int	top		= int(mGui->mViewport.y + mGui->mViewport.h) - mPadding;
	const int	bottom	= int(mGui->mViewport.y                    ) + mPadding;
	const float zoom2	= mZoom * mGui->GetZoom() * mGui->GetZoomViewport();

	// Optionally justify font relative to a window corner.
	float xz;
	float yz;
	switch ( mode & eFontMode_MASK_JUSTIFY_CORNER )
	{
		case eFontMode_NW: default:
		xz = left + x*zoom2;
		yz = top - (y+1)*zoom2;
		break;

		case eFontMode_NE:
		xz = x + right - textLen*zoom2;
		yz = top - (y+1)*zoom2;
		break;

		case eFontMode_SE:
		xz = x + right - textLen*zoom2;
		yz = bottom + y*zoom2;
		break;

		case eFontMode_SW:
		xz = left + x*zoom2;
		yz = bottom + y*zoom2;
		break;
	}

	if ( mode & eFontMode_MASK_JUSTIFY_CORNER )
	{
		// To right-justify, calculate a X offset to compensate for
		// the varying widths of letters.  The ratio of X font coords
		// to 2D X coords is calculated to get the X compensation.
		// This compensation also helps left-justification.
		float xz2 = xz;
		for ( uint i = 0; i < textLen; i++ ) // for every letter
		{
			if ( text[i] >= mStart  &&  text[i] < mStart+mLetters )
			{
				int ci = (int) (text[i] - mStart);
				xz2 += zoom2 * mLetterw[ci] / mHeight; // add width
			}
			else
			{
				xz2 += 0.5 * zoom2; // add width
			}
		}

		if ( mode & eFontMode_MASK_RIGHT )
			xz += right - xz2;
		else // MASK_LEFT
			xz += left - xz;

		// xz was translated to the left or right edge of the window.
		// Add the passed X font coord.
		if ( mode & eFontMode_MASK_LEFT )
			xz += x * zoom2;
		else // MASK_RIGHT
			xz -= x * zoom2;
	}
	else if ( mode & eFontMode_CENTER )
	{
		// Calculate X offset for center-justification.
		float xw = 0;
		for ( uint i = 0; i < textLen; i++ )
		{
			if ( text[i] >= mStart  &&  text[i] < mStart+mLetters )
			{
				int ci = (int) (text[i] - mStart);
				xw += zoom2 * mLetterw[ci] / mHeight;
			}
			else if ( text[i] == '\t' )
			{
				int xzint = (int) (xw * 10.0);
				xzint -= xzint & 3; // modulo 4
				xzint += 4;
				xw = (float) xzint * zoom2;
			}
			else
			{
				xw += 0.5 * zoom2;
			}
		}
		xz = (mGui->mViewport.w * 0.5) - int(xw * 0.5);
		yz = (top>>1) - y*zoom2;
	}

	mRect.x1 = xz;
	mRect.y1 = yz;

	bool savedLighting = GFX_IF_ENABLED( GP_LIGHTING );
	GFX_DISABLE( GP_LIGHTING );
	GFX_BEGIN_2D( &mGui->mViewport );
	GFX_BEGIN_BLEND();
	GFX_BEGIN( GP_QUADS, mTexture );

	// Draw every letter.
	// Newline and tab chars are ignored unlike GL-117.
	// Newline is impractical to support here due to right-justification.
	for ( uint i = 0; i < textLen; i++ )
	{
		if ( text[i] >= mStart  &&  text[i] < mStart+mLetters )
		{
			int ci = (int) (text[i] - mStart);
			float tx = (float) mLetterx[ci] / texWidth;
			float ty = 1.0 - (float) mLettery[ci] / texHeight;
			float tx2 = (float) tx + (float) mLetterw[ci] / texWidth;
			float ty2 = (float) ty - (float) mHeight / texHeight;
			float xi = zoom2 * mLetterw[ci] / mHeight;
			float yi = zoom2;
			mTexture->Map( tx,  ty2 ); GFX_VERTEX_2D(  xz, yz           );
			mTexture->Map( tx,  ty  ); GFX_VERTEX_2D(  xz,      yz + yi );
			mTexture->Map( tx2, ty  ); GFX_VERTEX_2D(  xz + xi, yz + yi );
			mTexture->Map( tx2, ty2 ); GFX_VERTEX_2D(  xz + xi, yz      );
			xz += xi;
		}
		else
		{
			xz += 0.5 * zoom2;
		}
	}

	mRect.x2 = xz;
	mRect.y2 = yz + zoom2;

	GFX_END( mTexture );
	GFX_END_BLEND();
	GFX_END_2D();
	GFX_RESTORE( GP_LIGHTING, savedLighting );

	return mErrors;
}

/*****************************************************************************
 * Error handler.
 *****************************************************************************/
int
Font::Error( int err )
{
	mErrors |= err;
#if DEBUG
	static int errorsShown = 0;
	if ( errorsShown++ < 10 ) *gStreamDebug << "Font: errors=" << mErrors << endl;
#endif
	return mErrors;
}

/*****************************************************************************
 * T. Drexl:
 * Each font is stored in one bitmap, width=height=2^n!
 * Use GIMP or equiv and put the ASCII letters of your favourite font
 * there, ascending order, start whereever you want. Write as many
 * letters as possible per line, each separated by at least one whitespace.
 * Look at the font*.tga files for examples. The max letter height is fixed.
 *****************************************************************************/
bool
Font::IsPixel( const Targa& targa, int x, int y )
{
	y = targa.GetHeight() - y;
	int idx = (y * targa.GetWidth() + x) * 4;
	if ( targa.ReadBuf(idx) > 50 && targa.ReadBuf(idx+1) > 50 && targa.ReadBuf(idx+2) > 50 )
		return true;
	return false;
}

/*****************************************************************************
 * Extract letters.
 *****************************************************************************/
void
Font::ExtractLetters( const Targa& targa )
{
	int		i;
	int		mode	= 0;
	bool	modeon	= 0;
	int		x		= 0;
	int		y		= 0;
	int		xs		= 0;
	int		xe		= 0;
	int		n		= 0;
	int		width   = targa.GetWidth();
	int		height  = targa.GetHeight();

	for (;;)
	{
		mode++;
		for ( i = y + 2; i <= y + mHeight - 2; i += 2 )
		{
			if ( IsPixel( targa, x, i ) )
			{
				mode = 0;
			}
		}
		if ( mode == 0 )
		{
			if ( ! modeon )
			{
				xs = x;
				modeon = true;
			}
		}
		if ( mode >= 5 )
		{
			if ( modeon )
			{
				xe = x;
				modeon = false;
				mLetterx[n] = xs - 2;
				mLettery[n] = y;
				mLetterw[n] = xe - xs + 1;
				n++;
				if ( n >= mLetters ) return;
			}
		}
		x++;
		if ( x >= width )
		{
			if ( modeon )
			{
				xe = x;
				modeon = false;
				mLetterx[n] = xs - 2;
				mLettery[n] = y;
				mLetterw[n] = xe - xs + 3;
				n++;
				if ( n >= mLetters ) return;
			}
			y += mHeight;
			x = 0;
		}
		if ( y >= height ) return;
	}
}

} // namespace gfx
