/*
	Trig Wars.
	Copyright (c) 2008, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//=====================================================================================================================
/// @brief		Class that stores a bitmap font and displays const char *s in this font type. Expects font image to 
///				have 16 columns and 16 rows of characters, ordered by the layout of the codepage in use. 
///				Character spacings are precomptuted in xml files and loaded when the font is created.
///				These spacings are created by a tool that was written for another project.
/// @remarks	This is adapted from previously written C#/XNA code. The adaptions make use of the resource 
///				management system of the DX framework since DX does not manage loadead resources automatically.
///				This class is a wrapper around an actual font resource, and does the 'work' of the font - i.e 
///				rendering.
//=====================================================================================================================

#include "Precompiled.h"
#include "Graphics\BitmapFont.h"
#include "Graphics\VertexFormat.h"

using namespace Game;

//=====================================================================================================================
/// @brief		Sets up the state of the renderer for drawing.
/// @param		transform - transform to use for the font
/// @pre		The font resource must be valid
/// @pre		The font manager must have a vertex format created
/// @returns	False if draw setup failed
//=====================================================================================================================
 
bool BitmapFont::DrawSetup( const Matrix4f & transform )
{
	// Debug check:
	
	ENSURE( m_resource.IsValid() , "The font resource must be valid" )
	
	// Get the resource data:
	
	BitmapFontResourceData * data = m_resource.GetResource()->GetData();
	
	// Apply the texture:
	
	data->m_texture.Apply(0);
	
	// Apply pixel and vertex shaders:
	
	data->m_settings.m_pixel_shader.Apply();
	data->m_settings.m_vertex_shader.Apply();
	
	// Set the shader transform:
	
	data->m_settings.m_u_transform.SetMatrix(transform);
	
	// Get the bitmap font manager:
	
	BitmapFontManager * mgr = (BitmapFontManager*) m_resource.GetManager();
	
	// Get the vertex format:
	
	VertexFormat * vf = mgr->GetVertexFormat();
	
	// Make sure ok:
	
	ENSURE( vf , "The font manager must have a vertex format created" );
	
	// See if valid:
	
	if ( vf->IsValid() ) 
	{
		// Valid vertex format: can draw
	
		vf->Apply(); return true;
	}
	else
	{
		// Invalid vertex format: cannot draw
	
		return false;
	}
}

//=====================================================================================================================
/// @brief		Puts the specified character into the rendering buffer.
/// @param		c - character to put into the render buffer
/// @param		position - position to draw the character at
/// @remarks	This function assumes there is space on the stack used for rendering
///				to begin with.
/// @pre		The resource used by this font must be valid
//=====================================================================================================================

void BitmapFont::PutBufferCharacter( unsigned char c , const Vec3f & position )
{
    //-----------------------------------------------------------------------------------------------------------------
    // Get what row of the font texture the character is located at. There 
    // are 16 columns and 16 rows in a font texture. First 4 bits of character code 
    // gives the column, next 4 bits gives the row.
    //-----------------------------------------------------------------------------------------------------------------
    
    int row = ( ((int)(c)) >> 4 ) & 0xf;
    
    // Get the resource data:
    
    BitmapFontResourceData * res_data = m_resource.GetResource()->GetData();
    
    // Make sure ok:
    
    ENSURE( res_data , "The resource used by this font must be valid" )
    
    // Get the character metrics for this character:
    
    BitmapFontResourceData::CharacterMetric * metrics = res_data->m_metrics;
    
    // Get the current font settings:
    
    BitmapFontSettings & settings = res_data->m_settings;
    
    // Get the stack we will put all the character data to:
    
    Stack & stack = ((BitmapFontManager*)(m_resource.GetManager()))->GetRenderBuffer();

    //-----------------------------------------------------------------------------------------------------------------
    // Now convert these to u and v texture coordinates for the top, bottom and 
    // left/right of the character's textured quad.
    //-----------------------------------------------------------------------------------------------------------------
    
    // Get left and right texture coordinats:

    float ul = metrics[c].m_u;  float ur = ul + metrics[c].m_s;
    
    // Get top and bottom texture coordinates:

    float vt = (float)( row     ) / 16.0f;
    float vb = (float)( row + 1 ) / 16.0f;

    //-----------------------------------------------------------------------------------------------------------------
    // Put the character's position, color and texure coordinates into the rendering 
    // buffer. We are rendering it's quad as two triangles.
    //-----------------------------------------------------------------------------------------------------------------

    // Fill in the buffer positions:
    
		// 1st triangle

		stack.Push( position.x );
		stack.Push( position.y );
		stack.Push( position.z );
		
		stack.Push(settings.m_color);
	    
		stack.Push(ul);
		stack.Push(vt);		
	    
		stack.Push( position.x + metrics[c&0xff].m_s * 16 * settings.m_size );
		stack.Push( position.y );
		stack.Push( position.z );
		
		stack.Push(settings.m_color);
	    
		stack.Push(ur);
		stack.Push(vt);		

		stack.Push( position.x + metrics[c&0xff].m_s * 16 * settings.m_size );
		stack.Push( position.y - settings.m_size );
		stack.Push( position.z );
		
		stack.Push(settings.m_color);
	    
		stack.Push(ur);
		stack.Push(vb);		    

		// 2nd triangle:
		
		stack.Push( position.x + metrics[c&0xff].m_s * 16 * settings.m_size );
		stack.Push( position.y - settings.m_size );
		stack.Push( position.z );
		
		stack.Push(settings.m_color);
		
		stack.Push(ur);
		stack.Push(vb);		

		stack.Push( position.x );
		stack.Push( position.y - settings.m_size );
		stack.Push( position.z );
		
		stack.Push(settings.m_color);
		
		stack.Push(ul);
		stack.Push(vb);		
		
		stack.Push( position.x );
		stack.Push( position.y );
		stack.Push( position.z );
		
		stack.Push(settings.m_color);
	    
		stack.Push(ul);
		stack.Push(vt);		
}

//=========================================================================================
/// @brief		Draws a given string at the specified location with the given transform matrix.
/// @param		text - text to draw
/// @param		position - where to draw the text
/// @param		transform - transform matrix to use
/// @pre		Given text cannot be null
/// @pre		Given position must be valid
/// @pre		The font resource must be valid
//=========================================================================================

void BitmapFont::Draw( const char * text , const Vec3f & position , const Matrix4f & transform )
{
    // Make sure this is all ok:
    
    ENSURE( text != 0					, "Given text cannot be null"		)
    ENSURE( Float::IsValid(position.x)	, "Given position must be valid"	)
    ENSURE( Float::IsValid(position.y)	, "Given position must be valid"	)
    ENSURE( IsValid()					, "The font resource must be valid"	)

    // Current position to draw the next character at

    Vec3f pos = position;

    // Number of characters put to the render buffer

    int buffer_chars = 0;
    
    // Get the length of the text:
    
    unsigned text_length = strlen(text);
    
    // If the text is zero length then don't bother:
    
    if ( text_length == 0 ) return;
    
    // Do draw setup: if that failed then abort
    
    if ( DrawSetup(transform) == false ) return;    
    
    // Get the font settings:
    
    BitmapFontSettings & settings = m_resource.GetResource()->GetData()->m_settings;
    
    // Get the character metrics for the font:
    
    BitmapFontResourceData::CharacterMetric * metrics = m_resource.GetResource()->GetData()->m_metrics;
    
    // Get the render buffer:
    
    Stack & render_buffer = ((BitmapFontManager*)(m_resource.GetManager()))->GetRenderBuffer();

    // Draw the string:

    for ( unsigned i = 0 ; i < text_length ; ++i )
    {   
        // Get the number of characters left:

        unsigned charsLeft = text_length - i;

        // See if the buffer is big enough to render the remaining chars

        if ( charsLeft <= BitmapFontManager::RENDER_BUFFER_SIZE )
        {
            //---------------------------------------------------------------------------------------------------------
            // Can render the remaining chars in one go
            //---------------------------------------------------------------------------------------------------------

            // Set the number of characters put to the buffer as 0

            buffer_chars = 0;

            //---------------------------------------------------------------------------------------------------------
            // Go through this part of the string that we can render in one go
            //---------------------------------------------------------------------------------------------------------

            for ( unsigned j = i ; j < text_length ; j++ )
            {
                // Get the character at this index:

                unsigned char c = (unsigned char) text[j];

                // If the draw character is a carriage return or newline then move onto a new line:

                if ( c == '\n' )
                {
                    // Move onto a new line:

                    pos.y -= settings.m_size + settings.m_line_spacing; pos.x = position.x; continue;
                }

                // If the draw character is a tab then move out to the next tab space:

                if ( c == '\t' )
                {
                    // Calculate x value of next tab space

                    float tab_x = (int)( ( pos.x - position.x ) / settings.m_size * 4.0f ) * ( settings.m_size * 4.0f ) + ( settings.m_size * 4.0f );

                    // Set the new position to draw at

                    pos.x = tab_x; continue;
                }

                // If the draw character is a space then ignore:

                if ( c == ' ' ){ pos.x += settings.m_size * settings.m_whitespace_width_pc; continue; }

                // Put this character in the render buffer:

                PutBufferCharacter(c,pos);

                // Move onto the next character space

                pos.x += settings.m_char_spacing + metrics[c].m_s * 16.0f * settings.m_size;

                // Added one more char to the rendering buffer

                buffer_chars++;
            }

            //---------------------------------------------------------------------------------------------------------
            // Draw the contents of the rendering buffer: 
            //---------------------------------------------------------------------------------------------------------

            // Only if there are characters to draw:

            if ( buffer_chars > 0 )
            {
				// Rewind the stack pointer:
				
				render_buffer.Rewind();
            
                // Draw the text:
                
                ((BitmapFontManager*)(m_resource.GetManager()))->GetDevice()->DrawPrimitiveUP
                (
					D3DPT_TRIANGLELIST			,
					buffer_chars * 2			,
					render_buffer.GetPointer()	,
					sizeof(BitmapFontManager::BitmapFontVertex)
                );
            }

            // Now finished: set string index to end of the string

            i = text_length;
        }
        else
        {
            //---------------------------------------------------------------------------------------------------------
            // Render buffer is not big enough to accomodate rest of the string
            //---------------------------------------------------------------------------------------------------------

            // Set the number of characters put to the buffer as 0

            buffer_chars = 0;

            // Calculate the index where we will stop putting chars to the buffer in the const char *

            int end_index = i + BitmapFontManager::RENDER_BUFFER_SIZE;

            //---------------------------------------------------------------------------------------------------------
            // Go through the portion of the string we can draw:
            //---------------------------------------------------------------------------------------------------------

            for ( int j = i ; j < end_index ; j++ )
            {
                // Get the character at this index:

                unsigned char c = (unsigned char) text[j];

                // If the draw character is a carriage return or newline then move onto a new line:

                if ( c == '\n' )
                {
                    // Move onto a new line:

                    pos.y -= settings.m_size + settings.m_line_spacing; pos.y = position.x; continue;
                }

                // If the draw character is a tab then move out to the next tab space:

                if ( c == '\t' )
                {
                    // Calculate x value of next tab space

                    float tab_x = (int)( ( pos.x - position.x ) / settings.m_size * 4.0f ) * ( settings.m_size * 4.0f ) + ( settings.m_size * 4.0f );

                    // Set the new position to draw at

                    pos.x = tab_x; continue;
                }

                // If the draw character is a space then ignore:

                if ( c == ' ' ){ pos.x += settings.m_size * settings.m_whitespace_width_pc; continue; }

                // Put this character in the render buffer:

                PutBufferCharacter(c,pos);

                // Move onto the next character space

                pos.x += settings.m_char_spacing + metrics[c].m_s * 16.0f * settings.m_size;

                // Added one more char to the rendering buffer

                buffer_chars++;
            }

            //---------------------------------------------------------------------------------------------------------
            // Draw the contents of the rendering buffer
            //---------------------------------------------------------------------------------------------------------

            // Only if there are characters to draw:

            if ( buffer_chars > 0 )
            {
				// Rewind the stack pointer:
				
				render_buffer.Rewind();            
            
                // Draw the text:
                
                ((BitmapFontManager*)(m_resource.GetManager()))->GetDevice()->DrawPrimitiveUP
                (
					D3DPT_TRIANGLELIST							,
					buffer_chars * 2							,
					render_buffer.GetPointer()					,
					sizeof(BitmapFontManager::BitmapFontVertex)
                );
            }

            // Move on in the string by the render buffer size

            i += BitmapFontManager::RENDER_BUFFER_SIZE;

        }   // end else of if there is enough buffer space left to render remaining chars
    }
}

//=====================================================================================================================
/// @brief		Draws a const char * centered horizontally over the given position with the given transform matrix.
/// @param		text - text to draw
/// @param		position - Position to draw the text at.
/// @param		transform - transform to use for drawing
/// @pre		Given text must not be null
/// @pre		Given position must be valid
/// @pre		The font resource must be valid
//=====================================================================================================================

void BitmapFont::DrawCentered( const char * text, const Vec3f & position , const Matrix4f & transform )
{
    // Make sure this is all ok:
    
    ENSURE( text != 0					, "Given text cannot be null"		)
    ENSURE( Float::IsValid(position.x)	, "Given position must be valid"	)
    ENSURE( Float::IsValid(position.y)	, "Given position must be valid"	)
    ENSURE( IsValid()					, "The font resource must be valid"	)    
    
    // Get the settings for the font:
    
    BitmapFontSettings & settings = m_resource.GetResource()->GetData()->m_settings;
    
    // Break up into lines:
    
    std::list<std::string> lines; String::Split(text,'\n',lines);

    // Current y offset for the text:

    float y_offset = 0;

    // Draw each line:

    while ( lines.size() > 0 )
    {
		// Get this line:
		
		const std::string & line = lines.front();
    
        // Get the size of the line:

        Vec2f size = GetSize( line.c_str() );

        // Now figure out what x coordinate to draw it at to center the const char * horizontally:

        float x = position.x - ( size.x * 0.5f );

        // Now just use the regular draw const char * function to draw at figured out position

        Draw( line.c_str() , Vec3f( x , position.y + y_offset , position.z ) , transform );
        
        // Decrement y offset for the next line:

        y_offset -= settings.m_line_spacing + settings.m_size;
        
        // Remove this string from the list:
        
        lines.pop_front();
    }
}

//=====================================================================================================================
/// @brief		Draws a string right aligned to the given position with the given transform matrix.
/// @param		text - string to draw
/// @param		position - position to draw in
/// @param		transform - transform to use for drawing
/// @pre		Given text must not be null
/// @pre		Given position must be valid
/// @pre		The font resource must be valid
//=====================================================================================================================

void BitmapFont::DrawRightAligned( const char * text, const Vec3f & position , const Matrix4f & transform )
{
    // Make sure this is all ok:
    
    ENSURE( text != 0					, "Given text cannot be null"		)
    ENSURE( Float::IsValid(position.x)	, "Given position must be valid"	)
    ENSURE( Float::IsValid(position.y)	, "Given position must be valid"	)
    ENSURE( IsValid()					, "The font resource must be valid"	)
    
    // Get the settings for the font:
    
    BitmapFontSettings & settings = m_resource.GetResource()->GetData()->m_settings;    

    // Break up into lines:
    
    std::list<std::string> lines; String::Split(text,'\n',lines);

    // Current y offset for the text:

    float y_offset = 0;

    // Draw each line:

    while ( lines.size() > 0 )
    {
		// Get this line:
		
		const std::string & line = lines.front();
    
        // Get the size of the string:

        Vec2f size = GetSize( line.c_str() );

        // Now figure out what x coordinate to draw it at to right align:

        float x = position.x - size.x;

        // Now just use the regular draw string function to draw at figured out position

        Draw( line.c_str() , Vec3f( x , position.y + y_offset , position.z ) , transform );
        
        // Increment y offset for the next line:

        y_offset -= settings.m_line_spacing + settings.m_size;
        
        // Done with this string:
        
        lines.pop_front();
    }
}

//=====================================================================================================================
/// @brief		Gets the dimensions for a given string with the font's sizing settings.
/// @param		text - text to get the size of
/// @returns	Total dimensions of the drawn text
/// @remarks	The size is returned in font units. Font units are not the same as 
///				screen units and may be scaled to a different size depending on the 
///				current viewing and projection matrices used.
/// @pre		Given text must not be null
/// @pre		The font resource must be valid
//=====================================================================================================================

Vec2f BitmapFont::GetSize( const char * text )
{
    // Make sure this is all ok:
    
    ENSURE( text != 0	, "Given text cannot be null"		)
    ENSURE( IsValid()	, "The font resource must be valid"	)
    
    // Get the settings for the font:
    
    BitmapFontSettings & settings = m_resource.GetResource()->GetData()->m_settings;
    
    // Get the character metrics for the font:
    
    BitmapFontResourceData::CharacterMetric * metrics = m_resource.GetResource()->GetData()->m_metrics;

    // Break up into lines:
    
    std::list<std::string> lines; String::Split(text,'\n',lines);        
    
    // Need to save the number of lines for later:
    
    unsigned num_lines = lines.size();

    // Store the maximum width of all the lines here:

    float max_w = 0;

    // Run through all the lines:

    while ( lines.size() > 0 )    
    {
		// Get the current line:
		
		const std::string & line = lines.front();
    
        // Store the maximum width of this line here:

        float line_max_w = 0;
        
        // Get the length of this line:
        
        unsigned line_len = strlen( line.c_str() );

        // Run through all the characters in this line:

        for ( unsigned j = 0 ; j < line_len ; j++ )
        {
            // Get this character:

            unsigned char c = (unsigned char) line[j];

            // If the draw character is a tab then move out to the next tab space:

            if ( c == '\t' )
            {
                // Calculate x value of next tab space

                float tab_x = (int)( ( line_max_w ) / ( settings.m_size * 4.0f ) ) * ( settings.m_size * 4.0f ) + ( settings.m_size * 4.0f );

                // Set the new max width of the line:

                line_max_w = tab_x; continue;
            }

            // If the draw character is a space then increment by a fixed size:

            if ( c == ' ' ){ line_max_w += settings.m_size * settings.m_whitespace_width_pc; continue; }

            // Increase the max width of the const char * according to char metrics and font size / spacing 

            line_max_w += metrics[c].m_s * 16.0f * settings.m_size;

            // If this is the last character then do not apply char spacing:

            if ( j < line_len - 1 ) line_max_w += settings.m_char_spacing;
        }

        // If this line is longer than the current longest then save:

        if ( line_max_w > max_w ) max_w = line_max_w;
        
        // Done with this line:
        
        lines.pop_front();
    }

    // Return the dimensions of the text:

    return Vec2f
    (
        max_w ,
        float(num_lines) * settings.m_size + Float::Clamp( float(num_lines) - 1.0f , 0.0f , 100000.0f ) * settings.m_line_spacing
    );
}
