/*
	Lan Wars.
	Copyright (c) 2009, 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		Represents a 3D sprite that is animated. 3D is meant loosely because different orientations are 
///				stored as different sprites. Diffferent sequences are supported also.
//=====================================================================================================================

#include "Precompiled.h"
#include "Graphics/SpriteAnimation.h"
#include "Graphics/Sprite.h"
#include "Graphics/SpriteRenderer.h"
#include "Resources/TextureManager.h"
#include "Game/GameCore.h"
#include <TinyXml.h>

using namespace Game;

//=====================================================================================================================
/// @brief		Constructor: creates the sprite animation from the settings stored in the given xml file
/// @param		xml_file - name of the xml file to load the animation from
/// @pre		The given file name cannot be null
/// @pre		GameCore must exist
/// @pre		GameCore must have a texture manager
//=====================================================================================================================

SpriteAnimation::SpriteAnimation( const char * xml_file )
{
	// Make sure ok
	
	ENSURE( xml_file != 0									, "The given file name cannot be null"		)
	ENSURE( GameCore::GetSingleton()						, "GameCore must exist"						)
	ENSURE( GameCore::GetSingleton()->GetTextureManager()	, "GameCore must have a texture manager"	)
	
	// Get the texture manager from the game
	
	TextureManager & tex_mgr = * ( GameCore::GetSingleton()->GetTextureManager() );
	
	// Assume invalid at first: reset all of these also
	
	m_is_valid			= false;
	m_frame				= 0; 
	m_frame_progress	= 0;
	m_frame_time		= 1;
	m_sequence			= 0;
	m_direction			= DIR_SOUTH;	
	m_additive_blend	= false;
	m_color				= Vec4f::One();
	m_no_flip			= false;
	
	// Try and open the xml file:
	
	TiXmlDocument xml_doc;
	
	// Try:
	
	try
	{
		// Try and open:
		
		xml_doc.LoadFile(xml_file);
		
		// Try and get the root element:
		
		TiXmlElement * root = xml_doc.RootElement();
		
		// Only if there:
		
		if ( root )
		{
			// Run through all the sub elements: these are animation sequences
			
			TiXmlElement * sequence_elem = root->FirstChildElement();
			
			// Continue:
			
			while ( sequence_elem )
			{
				// Make sure this has a name:
				
				if ( sequence_elem->Value() != 0 )
				{
					// Make this sequence:
					
					m_sequences.push_back( Sequence() );
					
					// Get a reference to this
					
					Sequence & sequence = m_sequences[ m_sequences.size() - 1 ];
					
					// Save the size for this sequence:
					
					sequence.m_size = GetSequenceSize(sequence_elem);
					
					// Get the frames for each direction:
					
					TiXmlElement * south_elem		= sequence_elem->FirstChildElement("South");
					TiXmlElement * south_east_elem	= sequence_elem->FirstChildElement("SouthEast");
					TiXmlElement * east_elem		= sequence_elem->FirstChildElement("East");
					TiXmlElement * north_east_elem	= sequence_elem->FirstChildElement("NorthEast");
					TiXmlElement * north_elem		= sequence_elem->FirstChildElement("North");
					
					// Make sure they are all there:
					
					if 
					(
						south_elem			&&
						south_east_elem		&&
						east_elem			&&
						north_east_elem		&&
						north_elem
					)
					{
						// Get all of the frame names
					
						std::vector<std::string> south_frame_names = GetDirectionFrames(south_elem);
						std::vector<std::string> south_east_frame_names = GetDirectionFrames(south_east_elem);
						std::vector<std::string> east_frame_names = GetDirectionFrames(east_elem);
						std::vector<std::string> north_east_frame_names = GetDirectionFrames(north_east_elem);
						std::vector<std::string> north_frame_names = GetDirectionFrames(north_elem);
						
						// Make sure there are frames for each one:
						
						if 
						( 
							south_frame_names.size()		> 0	&&
							south_east_frame_names.size()	> 0	&&
							east_frame_names.size()			> 0	&&
							north_east_frame_names.size()	> 0	&&
							north_frame_names.size()		> 0
						)
						{
							// Ok: need to load all of the frames for each of the directions
							
							for ( unsigned i = 0 ; i < south_frame_names.size() ; ++i )
							{							
								sequence.m_frames[unsigned(DIR_SOUTH)].push_back
								( 
									Sprite( tex_mgr.GetTexture(south_frame_names[i]) , Vec3f::Zero() , sequence.m_size )
								);
							}
							
							for ( unsigned i = 0 ; i < south_frame_names.size() ; ++i )
							{							
								sequence.m_frames[unsigned(DIR_SOUTH_EAST)].push_back
								( 
									Sprite( tex_mgr.GetTexture(south_east_frame_names[i]) , Vec3f::Zero() , sequence.m_size )
								);
							}
							
							for ( unsigned i = 0 ; i < south_frame_names.size() ; ++i )
							{							
								sequence.m_frames[unsigned(DIR_EAST)].push_back
								( 
									Sprite( tex_mgr.GetTexture(east_frame_names[i]) , Vec3f::Zero() , sequence.m_size )
								);
							}
							
							for ( unsigned i = 0 ; i < south_frame_names.size() ; ++i )
							{							
								sequence.m_frames[unsigned(DIR_NORTH_EAST)].push_back
								( 
									Sprite( tex_mgr.GetTexture(north_east_frame_names[i]) , Vec3f::Zero() , sequence.m_size )
								);
							}
							
							for ( unsigned i = 0 ; i < south_frame_names.size() ; ++i )
							{							
								sequence.m_frames[unsigned(DIR_NORTH)].push_back
								( 
									Sprite( tex_mgr.GetTexture(north_frame_names[i]) , Vec3f::Zero() , sequence.m_size )
								);
							}										
						}
					}
				}
			
				// Next sequence:
				
				sequence_elem = sequence_elem->NextSiblingElement();
			}
			
			// Now make sure all the animation frames are valid:
			
			m_is_valid = true;
			
			// Do it:
			
			for ( unsigned i = 0 ; i < m_sequences.size() ; ++i )
			{
				for ( unsigned j = 0 ; j < 5 ; ++j )
				{
					for ( unsigned k = 0 ; k < m_sequences[i].m_frames[j].size() ; ++k )
					{
						m_is_valid = m_is_valid && m_sequences[i].m_frames[j][k].IsValid();
					}
				}
			}
		}
	}
	catch ( ... )
	{
		// Unable to open the document. Do nothing.		
	}
}

//=====================================================================================================================
/// @brief		Copy constructor. Creates from another sprite animation
/// @param		other - other sprite animation to create from
//=====================================================================================================================

SpriteAnimation::SpriteAnimation( const SpriteAnimation & other )
{
	m_sequences			= other.m_sequences;
	m_direction			= other.m_direction;
	m_frame_time		= other.m_frame_time;
	m_frame_progress	= other.m_frame_progress;
	m_sequence			= other.m_sequence;
	m_frame				= other.m_frame;
	m_is_valid			= other.m_is_valid;
	m_additive_blend	= other.m_additive_blend;
	m_color				= other.m_color;
	m_no_flip			= other.m_no_flip;
}

//=====================================================================================================================
/// @brief		Assignment operator. Assigns the other sequence to this sequence.
/// @param		other - other sprite animation to create from
/// @returns	This animation
//=====================================================================================================================

SpriteAnimation & SpriteAnimation::operator = ( const SpriteAnimation & other )
{
	m_sequences			= other.m_sequences;
	m_direction			= other.m_direction;
	m_frame_time		= other.m_frame_time;
	m_frame_progress	= other.m_frame_progress;
	m_sequence			= other.m_sequence;
	m_frame				= other.m_frame;
	m_is_valid			= other.m_is_valid;
	m_additive_blend	= other.m_additive_blend;
	m_color				= other.m_color;
	m_no_flip			= other.m_no_flip;
	
	return * this;
}

//=====================================================================================================================
/// @brief		Gets the names of all the frames of animation stored underneath a direction identifier in the xml
/// @param		direction_element - xml element in the xml file for this direction.
/// @pre		Given element cannot be null
//=====================================================================================================================

std::vector<std::string> SpriteAnimation::GetDirectionFrames( TiXmlElement * direction_element )
{
	// Make sure ok
	
	ENSURE( direction_element != 0 , "Given element cannot be null" )

	// Store all the frames here:	
	
	std::vector<std::string> frames;
	
	// Run through all the child elements:
	
	TiXmlElement * frame_elem = direction_element->FirstChildElement();
	
	// Continue until we go through all:
	
	while ( frame_elem )
	{
		// Get the name of this frame:
		
		const char * frame_name = frame_elem->GetText();
		
		// Make sure there:
		
		if ( frame_name )
		{
			// Into the list of frame names:
			
			frames.push_back(frame_name);
		}
	
		// Next frame
		
		frame_elem = frame_elem->NextSiblingElement();
	}
	
	// Return the frames
	
	return frames;
}

//=====================================================================================================================
/// @brief		Gets the size for a given sequence from the xml file:
/// @param		sequence_element - element representing the animation sequence in the xml
/// @pre		Sequence element cannot be null
/// @remarks	Defaults size to 64x64 if the size cannot be loaded right or if the size is invalid
//=====================================================================================================================

Vec2f SpriteAnimation::GetSequenceSize( TiXmlElement * sequence_element )
{
	// Save the size here:
	
	Vec2f size = Vec2f(64,64);

	// Make sure ok:
	
	ENSURE( sequence_element != 0 , "Sequence element cannot be null" )
	
	// Try and find the size element:
	
	TiXmlElement * size_element = sequence_element->FirstChildElement("Size");
	
	// See if there:
	
	if ( size_element )
	{
		// Find the x and y elements
		
		TiXmlElement * x_element = size_element->FirstChildElement("X");
		TiXmlElement * y_element = size_element->FirstChildElement("Y");
		
		// See if there:
		
		if ( x_element && y_element && x_element->GetText() && y_element->GetText() )
		{
			// Ok: convert the size to a float
			
			size.x = String::ToFloat( x_element->GetText() , 64 );
			size.y = String::ToFloat( y_element->GetText() , 64 );
		}
	}
	
	// Return the size
	
	return size;
}

//=====================================================================================================================
/// @brief		Tells if the animation is at the end of the current sequence:
/// @returns	True if the animation is at the end of the current sequence.
//=====================================================================================================================

bool SpriteAnimation::IsSequenceFinished() const
{
	// Make sure the current sequence is in range:
	
	if ( m_sequence < m_sequences.size() )
	{
		// Get our current direction:
		
		unsigned dir = unsigned(m_direction);
		
		// Index of the direction in the array of directions for this sequence:
		
		unsigned sequence_dir_index = 0;
		
		// Set the position in the array of directions for this sequence:
		
		switch ( dir )
		{
			case DIR_SOUTH:			sequence_dir_index = 0;		break;
			case DIR_SOUTH_EAST:	sequence_dir_index = 1;		break;
			case DIR_EAST:			sequence_dir_index = 2;		break;
			case DIR_NORTH_EAST:	sequence_dir_index = 3;		break;
			case DIR_NORTH:			sequence_dir_index = 4;		break;
			case DIR_NORTH_WEST:	sequence_dir_index = 3;		break;
			case DIR_WEST:			sequence_dir_index = 2;		break;
			case DIR_SOUTH_WEST:	sequence_dir_index = 1;		break;
		}
		
		// Get how many frames there are for this direction
		
		unsigned frames = m_sequences[m_sequence].m_frames[sequence_dir_index].size();
		
		// See what the story is:
		
		if ( frames == 0 )
		{
			// Finished:
		
			return true;
		}						
		else if ( m_frame > frames )
		{
			// Finished:
			
			return true;
		}
		else if ( m_frame >= frames - 1 )
		{
			// See if frame time is up:
			
			if ( m_frame_progress >= 1 )
			{
				// Finished:
				
				return true;
			}
			else
			{
				// Not finished:
				
				return false;
			}
		}
		else
		{
			// Not finished:
			
			return false;
		}
	}
	else
	{
		// Not a valid sequence: finished
		
		return true;
	}
}

//=====================================================================================================================
/// @brief		Sets the current frame number we are on in the current animation sequence
/// @param		frame - frame to set to
/// @remarks	This will reset the progress through the current frame also
//=====================================================================================================================

void SpriteAnimation::SetFrame( unsigned frame )
{
	// Only if there is sequences:
	
	if ( m_sequences.size() > 0 )
	{
		// Get our sequence number and make sure it is in range:
		
		unsigned sequence_num = m_sequence;
		
		// Make sure in range
		
		if ( sequence_num >= m_sequences.size() ) sequence_num = m_sequences.size() - 1;
		
		// Now get the number of frames for the sequence:
		
		unsigned sequence_frames = m_sequences[sequence_num].m_frames[0].size();
		
		// If there are no frames then just set to zero: otherwise wrap
		
		if ( sequence_frames == 0 )
		{
			m_frame = 0;
		}
		else
		{
			m_frame = frame % sequence_frames;
		}
	}
	else
	{
		// Reset frame number to 0: no sequences
		
		m_frame = 0;
	}
	
	// Reset the frame progress
	
	m_frame_progress = 0;
}

//=====================================================================================================================
/// @brief		Sets the progress for the current frame
/// @param		progress - current amount of progress for the current frame
//=====================================================================================================================

void SpriteAnimation::SetFrameProgress( float progress )
{
	// Clamp to 0-1
	
	progress = Float::Clamp(progress,0,1);
	
	// Set:
	
	m_frame_progress = progress;
}

//=====================================================================================================================
/// @brief		Sets the amount of time each frame lasts for
/// @param		frame_time - amount of time each frame lasts for
//=====================================================================================================================

void SpriteAnimation::SetFrameTime( float frame_time )
{
	// If less than this then clamp:
	
	if ( frame_time < 0.000001f ) frame_time = 0.000001f;
	
	// Set:
	
	m_frame_time = frame_time;
}

//=====================================================================================================================
/// @brief		Sets the current animation sequence
/// @param		sequence - sequence number to switch to
/// @param		reset_frame_timings - if true then we reset the timings of the frame. If false then we leave them alone.
//=====================================================================================================================

void SpriteAnimation::SetSequence( unsigned sequence , bool reset_frame_timings )
{
	// See if we have any sequences:
	
	if ( m_sequences.size() == 0 )
	{
		// Have no sequences: just set everything to zero
		
		m_sequence = 0;
	}
	else
	{
		// Have sequences: wrap sequence number
		
		m_sequence = sequence % m_sequences.size();
	}
	
	// See if we are to reset frame and timings:
	
	if ( reset_frame_timings )
	{
		// Reset frame and timing
		
		SetFrame(0);
		SetFrameProgress(0);
	}
	else
	{
		// Set the frame to current frame: will put into range if out of range
		
		SetFrame(m_frame);
	}
}

//=====================================================================================================================
/// @brief		Sets the current direction of the animation to display
//=====================================================================================================================

void SpriteAnimation::SetDirection( Direction direction )
{
	// Make sure the direction is in range:
	
	direction = (Direction)( unsigned(direction) & 7 );
	
	// Save:
	
	m_direction = direction;
}

//=====================================================================================================================
/// @brief		Sets the current direction we are using for the animated sprite
/// @param		cam_pos - position of the camera
/// @param		obj_pos - position of the object
/// @param		obj_rot - rotation of the object in radians about the y axis
/// @todo		Finish this function
//=====================================================================================================================

void SpriteAnimation::SetDirection( Vec3f cam_pos , Vec3f obj_pos , float obj_rot )
{
	// Convert both positions to 2D:
	
	Vec2f p1 = Vec2f(cam_pos.x,cam_pos.z);
	Vec2f p2 = Vec2f(obj_pos.x,obj_pos.z);

	// Now adjust angle depending on camera position relative to the sprite
	
	float angle = ( p1 - p2 ).AngleBetween( - Vec2f::UnitY() );
	
	// Add the object rotation:
	
	angle -= obj_rot;
	
	// Convert to degrees:
	
	angle = Angle::ToDegrees(angle);
	
	// Clamp from 0-360:
	
	angle = Float::Mod(angle,float(360));
	
	// Divide by the number of degrees in a direction
	
	angle /= 45.0f;
	
	// Round:
	
	angle = Float::Round(angle,0);
	
	// To integer:
	
	int dir = int(angle);
	
	// Make sure in range:
	
	dir = dir & 7;
	
	// Now save:
	
	m_direction = (Direction)(dir);
}

//=====================================================================================================================
/// @brief		Animates the sprite
/// @param		time_delta - amount of time elapsed
/// @param		stop_at_end - if true then we do not wrap the animation, we pause at the end
//=====================================================================================================================

void SpriteAnimation::Animate( float time_delta , bool stop_at_end )
{
	// Make sure the time delta is at least 0
	
	if ( time_delta < 0 ) time_delta = 0;
	
	// See if we have any sequences:
	
	if ( m_sequences.size() > 0 )
	{
		// Get the number of the sequence we are on:
		
		unsigned sequence_num = m_sequence;
		
		// Wrap:
		
		sequence_num = sequence_num % m_sequences.size();
		
		// Get our direction index:
		
		unsigned sequence_dir_index = 0;
		
		// Set the position in the array of directions for this sequence:
		
		switch ( m_direction )
		{
			case DIR_SOUTH:			sequence_dir_index = 0;		break;
			case DIR_SOUTH_EAST:	sequence_dir_index = 1;		break;
			case DIR_EAST:			sequence_dir_index = 2;		break;
			case DIR_NORTH_EAST:	sequence_dir_index = 3;		break;
			case DIR_NORTH:			sequence_dir_index = 4;		break;
			case DIR_NORTH_WEST:	sequence_dir_index = 3;		break;
			case DIR_WEST:			sequence_dir_index = 2;		break;
			case DIR_SOUTH_WEST:	sequence_dir_index = 1;		break;
		}
		
		// Get the number of frames in this sequence:
		
		unsigned sequence_frames = m_sequences[sequence_num].m_frames[sequence_dir_index].size();
		
		// See if there are any frames:
		
		if ( sequence_frames > 0 )
		{
			// Get the frame time: make sure it is greater than this
			
			float frame_time = m_frame_time; if ( frame_time < 0.000001f ) frame_time = 0.000001f;
		
			// Increase the frame progress:
			
			m_frame_progress += time_delta / frame_time;
			
			// See if we are over the end:
			
			if ( m_frame_progress >= 1.0f )
			{
				// Time to change frame: increment the frame
				
				m_frame++;
				
				// See if we are at the end:
				
				if ( m_frame >= sequence_frames )
				{
					// See if we are to stop at the end
					
					if ( stop_at_end )
					{
						// Clamp frame to the end
						
						m_frame = sequence_frames - 1;
						
						// Clamp progress to 1
						
						m_frame_progress = 1;
					}
					else
					{
						// Reset frame progress but keep leftover time
						
						m_frame_progress = Float::Mod(m_frame_progress,1);
						
						// Back to starting frame
						
						m_frame = 0;
					}
				}
				else
				{				
					// Reset frame progress but keep leftover time
					
					m_frame_progress = Float::Mod(m_frame_progress,1);
				}
			}
		}
		else
		{
			// No sequence frames
			
			m_frame				= 0;
			m_frame_progress	= 0;
		}
	}
}

//=====================================================================================================================
/// @brief		Draws the sprite animation
/// @param		cam - camera to draw from
/// @param		renderer - sprite renderer to use
/// @pre		Given camera must be valid
/// @pre		Given sprite renderer must be valid
//=====================================================================================================================

#pragma warning(disable:4100)

void SpriteAnimation::Draw( Camera3D * cam , SpriteRenderer * renderer )
{
	// Make sure ok
	
	ENSURE( cam != 0		, "Given camera must be valid"			)
	ENSURE( renderer != 0	, "Given sprite renderer must be valid"	)

	// Only if everything is valid:	
	
	if ( m_is_valid )
	{
		// Get our current sequence number: make sure it is wrapped
		
		unsigned sequence_num = m_sequence;
		
		// This should never fail
		
		ENSURE( m_sequences.size() > 0 , "Sprite marked as valid when there are no sequences" )
		
		// Wrap the sequence number:
		
		sequence_num = sequence_num % m_sequences.size();
		
		// Get our direction index:
		
		unsigned sequence_dir_index = 0;
		
		// Set the position in the array of directions for this sequence:
		
		switch ( m_direction )
		{
			case DIR_SOUTH:			sequence_dir_index = 0;		break;
			case DIR_SOUTH_EAST:	sequence_dir_index = 1;		break;
			case DIR_EAST:			sequence_dir_index = 2;		break;
			case DIR_NORTH_EAST:	sequence_dir_index = 3;		break;
			case DIR_NORTH:			sequence_dir_index = 4;		break;
			case DIR_NORTH_WEST:	sequence_dir_index = 3;		break;
			case DIR_WEST:			sequence_dir_index = 2;		break;
			case DIR_SOUTH_WEST:	sequence_dir_index = 1;		break;
		}
		
		// Get the number of frames in this sequence:
		
		unsigned sequence_frames = m_sequences[sequence_num].m_frames[sequence_dir_index].size();
		
		// This should always be true
		
		ENSURE( sequence_frames > 0 , "No frames for the animation sequence and still valid" )
		
		// Wrap the frame number:
		
		unsigned frame_num = m_frame % sequence_frames;
		
		// Get the actual sprite
		
		Sprite & sprite = m_sequences[sequence_num].m_frames[sequence_dir_index][frame_num];
		
		// Ok: set the position of our sprite:
		
		sprite.SetPosition(m_position);
		
		// See whether we are to flip or not:
		
		if ( m_no_flip )
		{
			sprite.SetHorizontallyFlipped(false);
		}
		else
		{	
			if ( unsigned(m_direction) > 4 )
			{
				sprite.SetHorizontallyFlipped(true);
			}
			else
			{
				sprite.SetHorizontallyFlipped(false);
			}
		}
		
		// See if additively blended or not:
		
		if ( m_additive_blend )
		{
			sprite.SetAdditivelyBlended(true);
		}
		else
		{
			sprite.SetAdditivelyBlended(false);
		}
		
		// Set sprite color:
		
		sprite.SetColor(m_color);
		
		// Draw the sprite:
		
		renderer->AddSprite(sprite);
	}
}

#pragma warning(default:4100)
