#include "FreeTypeFont.hpp"
#include <boost/assert.hpp>
#include <freetype/ftbitmap.h>
#include <boost/tokenizer.hpp>

#include "../../Surface.hpp"
#include "../../Color.hpp"

#include "../../../Core/TypeUtils.hpp"

using namespace Spiral;
using namespace boost;

const uint32_t kPadding = 3; // raise it 3 pixels

namespace
{
	inline int32_t convert_26_6_to_int32( signed long value )
	{
		return value >> 6;
	}
}

FreeTypeFont::~FreeTypeFont()
{
}

FreeTypeFont::FreeTypeFont( const FreeTypeFont& font ):
Font( font.GetCharWidth(),  convert_26_6_to_int32( font.m_face->size->metrics.height ) ), 
m_library( font.m_library ), m_face( font.m_face ),m_data( font.m_data )
{}

FreeTypeFont::FreeTypeFont( FT_Library library, FT_Face font,const boost::shared_array< boost::int8_t >& data, boost::int32_t width, boost::int32_t height ):
Font( width, convert_26_6_to_int32( font->size->metrics.height ) ),m_library( library ), m_face( font, FT_Done_Face ),m_data( data )
{

}

/*!
   @function  FindMaxStrSize
   @brief     finds the max string size, newlines in the string marks a sub string
   @return    boost::int32_t
   @param     const std::string & str
*/
boost::uint32_t FindMaxStrSize( const wString& str )
{
	uint32_t firstPos = 0,lastPos = 0;
	uint32_t len = 0;

	typedef tokenizer< char_separator<wChar>, wString::const_iterator, wString > wTokenizer;
	
	wTokenizer tokens( str,char_separator<wChar>( L"\n" ) );
	for( wTokenizer::iterator itr = tokens.begin(); itr != tokens.end(); ++itr )
	{
		uint32_t curlen = (*itr).length();
		if( curlen > len )
			len = curlen;
	}

	return ( len == 0 ? str.length() : len );
}

void FreeTypeFont::DoCalcSurfaceSize( const wString& str, boost::int32_t& surfWidth, boost::int32_t& surfHeight )
{
	surfWidth = FindMaxStrSize( str ) * convert_26_6_to_int32( m_face->max_advance_width / 2 );
	surfHeight = GetCharHeight() * ( 1 + count_element_occurences( str.begin(), str.end(), '\n' ) ) + GetCharHeight();
}
																															  

struct SurfacePosition
{
	SurfacePosition( boost::int32_t x_, boost::int32_t y_ ):
	x( x_ ),y( y_ ){}
	SurfacePosition():x(0),y(0){}

	boost::int32_t x,y;
};

inline void StorePixelRgba( unsigned char* currentLine, const Rgba& color, unsigned char* bitmap_surf )
{
	currentLine[0] = color.GetByteComponent( 0 );
	currentLine[1] = color.GetByteComponent( 1 );
	currentLine[2] = color.GetByteComponent( 2 );
	currentLine[3] = *bitmap_surf;
}

inline void StorePixelRgb( unsigned char* currentLine, const Rgba& color, unsigned char* bitmap_surf )
{
	currentLine[0] = static_cast< unsigned char >( color[0] * (*bitmap_surf) );
	currentLine[1] = static_cast< unsigned char >( color[1] * (*bitmap_surf) );
	currentLine[2] = static_cast< unsigned char >( color[2] * (*bitmap_surf) );
}

template< void PixelStorePolicy( unsigned char*, const Rgba&, unsigned char*), int32_t elementMinSize >
void RenderBitmapToSurf( const SurfacePosition& position, const FT_Bitmap& bitmap, const Rgba& color, boost::shared_ptr< Surface >& surface )
{
	int32_t rows = bitmap.rows;
	int32_t surfacePitch = surface->GetBytesPerLine();
	int32_t elementSize = surface->GetElementSize();

	BOOST_ASSERT( elementSize >= elementMinSize );
	unsigned char* surf = reinterpret_cast<unsigned char*>( surface->GetData() ) + position.y * surfacePitch + position.x * elementSize;
	unsigned char* bitmap_surf = bitmap.buffer;

	int32_t col = bitmap.width;
	unsigned char* currentSurfLine = surf;
	unsigned char* currentBitMapLine = bitmap.buffer;

	while( rows-- != 0 ) 
	{
		do 
		{
			if( *currentBitMapLine != 0 )
			{
				// store the pixel
				PixelStorePolicy(currentSurfLine, color, currentBitMapLine);
			}

			currentSurfLine += elementSize;
			++currentBitMapLine;
		} while ( --col != 0 );

		surf += surfacePitch;
		bitmap_surf += bitmap.pitch;
		col = bitmap.width;
		currentSurfLine = surf;
		currentBitMapLine = bitmap_surf;
	}
}

void RenderBitmapOpaque( const SurfacePosition& pos, const FT_Bitmap& bitmap, const Rgba& color, boost::shared_ptr< Surface >& surface )
{
	RenderBitmapToSurf< StorePixelRgb, 3 >( pos, bitmap, color, surface );
}

void RenderBitmapAlpha( const SurfacePosition& pos, const FT_Bitmap& bitmap, const Rgba& color, boost::shared_ptr< Surface >& surface )
{
	RenderBitmapToSurf< StorePixelRgba, 4 >( pos, bitmap, color, surface );
}

struct TextData
{
	TextData( FT_Face face_, const boost::shared_ptr< Surface >& surface, const SurfacePosition& pos ):
	face( face_ ),renderSurface( surface ),cursorPos( pos ){}
	
	FT_Face face;
	boost::shared_ptr< Surface > renderSurface;
	SurfacePosition cursorPos;
};

template< void Render( const SurfacePosition&, const FT_Bitmap&, const Rgba&, boost::shared_ptr< Surface >& ) >
void DrawText( TextData& data, const wString& str, const Rgba& color )
{
	const wChar* text = str.c_str();
	const int32_t text_height = convert_26_6_to_int32( data.face->size->metrics.height );
	SurfacePosition pos( data.cursorPos.x, text_height - kPadding );
	bool useKerning = bool( FT_HAS_KERNING( data.face ) ? true : false );
	FT_UInt preIndex = 0;

	while( *text != 0 )
	{
		wChar c = *text;

		if( c == '\n' )
		{
			pos.y += text_height;
			pos.x = 0;
			++text;
			continue;
		}

		FT_UInt index = FT_Get_Char_Index( data.face, c );
		FT_Error result = FT_Load_Glyph( data.face, index, FT_LOAD_DEFAULT );
		if( !result )
		{
			if( useKerning && index && preIndex )
			{
				FT_Vector delta;

				FT_Get_Kerning( data.face, preIndex, index, 0, &delta );

				pos.x += convert_26_6_to_int32(delta.x);
			}

			result = FT_Render_Glyph( data.face->glyph, FT_RENDER_MODE_NORMAL );
			FT_GlyphSlot slot = data.face->glyph;
			if( !result )
			{
				SurfacePosition tmpPos( pos.x + slot->bitmap_left, pos.y - slot->bitmap_top  );
				Render( tmpPos, slot->bitmap, color, data.renderSurface );
			}

			pos.x += convert_26_6_to_int32(data.face->glyph->advance.x);  // advance is in 26.6 format, convert to int
			preIndex = index;
		}

		++text;
	}

	data.cursorPos.x = pos.x;
	data.cursorPos.y = pos.y - text_height;
}

void FreeTypeFont::DoRenderAlpha( boost::shared_ptr< Surface >& surface, const wString& str, const Rgba& color )
{
	TextData renderInfo( m_face.get(), surface, SurfacePosition() );

	SetTheFontSize();
	DrawText< RenderBitmapAlpha >( renderInfo, str, color );
}

void FreeTypeFont::DoRenderOpaque( boost::shared_ptr< Surface >& surface, const wString& str, const Rgba& color )
{
	TextData renderInfo( m_face.get(), surface, SurfacePosition() );

	SetTheFontSize();
	DrawText< RenderBitmapOpaque >( renderInfo, str, color );
}

void FreeTypeFont::DoRenderAlpha( boost::shared_ptr< Surface >& surface, boost::uint32_t& cursorX, const wString& str, const Rgba& color )
{
	TextData renderInfo( m_face.get(), surface, SurfacePosition(cursorX,0) );

	SetTheFontSize();
	DrawText< RenderBitmapAlpha >( renderInfo, str, color );
	
	cursorX = renderInfo.cursorPos.x;
}

void FreeTypeFont::DoRenderOpaque( boost::shared_ptr< Surface >& surface, boost::uint32_t& cursorX, const wString& str, const Rgba& color )
{
	TextData renderInfo( m_face.get(), surface, SurfacePosition(cursorX,0) );
	
	SetTheFontSize();
	DrawText< RenderBitmapOpaque >( renderInfo, str, color );
	
	cursorX = renderInfo.cursorPos.x;
}

bool FreeTypeFont::DoSetSize( boost::int32_t width, boost::int32_t height )
{
	FT_Error result = 0;
	if( !( convert_26_6_to_int32( m_face->size->metrics.height ) == height && width == GetCharWidth() )  )
	{
		result = FT_Set_Pixel_Sizes( m_face.get(), width, height );
		if( !result )
		{
			SetSize( width, convert_26_6_to_int32( m_face->size->metrics.height ) );
		}

	}
	return bool( !result );
}

FreeTypeFont* FreeTypeFont::DoClone() const
{
	return new FreeTypeFont( *this );
}


void FreeTypeFont::SetTheFontSize()
{
	SetFontSize( GetCharWidth(), GetCharHeight() );
}