/*
	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		Represents a Direct3D9 vertex format that can be used to draw primitives
//=====================================================================================================================

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

using namespace Game;

//=====================================================================================================================
/// @brief		Creates a default null vertex format
//=====================================================================================================================

VertexFormat::VertexFormat()
{
	m_vertex_declaration	= 0;
	m_is_valid				= false;
	m_d3d_device			= 0;
	m_vertex_size			= 0;
}

//=====================================================================================================================
/// @brief		Copy constructor: creates this vertex declaration from another one
//=====================================================================================================================

VertexFormat::VertexFormat( const VertexFormat & other )
{
	// See if the other format has a valid d3d device:
	
	if ( other.m_d3d_device )
	{
		// Good: add a reference to it
		
		m_d3d_device = other.m_d3d_device; m_d3d_device->AddRef(); 
	
		// Make sure it has a valid direct3d device:
		
		if ( other.m_vertex_declaration )
		{
			// Good:

			m_vertex_declaration	= other.m_vertex_declaration;
			m_is_valid				= other.m_is_valid;
			m_vertex_size			= other.m_vertex_size;
			
			m_vertex_declaration->AddRef();
		}
		else
		{
			// Bah.. 
		
			m_is_valid				= false;
			m_vertex_size			= 0;
			m_vertex_declaration	= 0;
		}
	}
	else
	{
		// Complete fail: make everything null
	
		m_vertex_declaration	= 0;
		m_d3d_device			= 0;
		m_vertex_size			= 0;
		m_is_valid				= false;	
	}
}

//=====================================================================================================================
/// @brief		Assignment operator: assigns the other vertex declaration to this one
/// @param		other - other vertex declaration to assign to this one
//=====================================================================================================================

VertexFormat & VertexFormat::operator = ( const VertexFormat & other )
{
	// Make sure not the same:
	
	if ( &other != this )
	{
		// See if it has a device: if so then add a reference to it
		
		if ( other.m_d3d_device ) other.m_d3d_device->AddRef();
		
		// See if it has a declaration: if so then add a reference to it
		
		if ( other.m_vertex_declaration ) other.m_vertex_declaration->AddRef();
		
		// Remove ref to old vertex declaration if there:
		
		if ( m_vertex_declaration ) m_vertex_declaration->Release();
		
		// Remove ref to old device if there:
		
		if ( m_d3d_device ) m_d3d_device->Release();
		
		// Copy across everything:
		
		m_vertex_declaration	= other.m_vertex_declaration;
		m_d3d_device			= other.m_d3d_device;
		m_vertex_size			= other.m_vertex_size;
		
		// See if valid:
		
		if ( m_vertex_declaration && m_d3d_device )
		{
			m_is_valid = true;
		}
		else
		{
			m_is_valid = false;
		}
	}

	// Return this vertex format:
	
	return * this;
}

///====================================================================================================================
/// @brief		Creates the given vertex format 
/// @param		display - directx display the format is for
/// @param		num_elements - number of elements in each vertex
/// @param		usages - usages of each vertex element
/// @param		types - types of each vertex element
/// @pre		Given display cannot be null
/// @pre		Number of elements cannot be zero
/// @pre		Usages array cannot be null
/// @pre		Types array cannot be null
///====================================================================================================================

VertexFormat::VertexFormat
( 
	Display	*							display			, 
	unsigned char						num_elements	,
	const VertexFormat::ElementUsage *	usages			,
	const VertexFormat::ElementType *	types
)
{
	// Make sure ok:
	
	ENSURE( display , "Given display cannot be null" );
	
	// Make sure ok:
	
	ENSURE( num_elements , "Number of elements cannot be zero" );
	
	// Make sure ok:
	
	ENSURE( usages , "Usages array cannot be null" );
	
	// Make sure ok:
	
	ENSURE( types , "Types array cannot be null" );			

	// Clear data:

	m_is_valid = false; m_vertex_declaration = 0;

	// If no display this is not valid:

	if ( display != 0 )
	{
		// Ok: get the device

		IDirect3DDevice9 * device = display->GetDevice();

		// See if valid and if arrays given are valid:

		if ( device != 0 && usages != 0 && types != 0 && num_elements > 0 )
		{
			// Save the device and add a reference to it:

			m_d3d_device = device; m_d3d_device->AddRef();

			// Store the current offset in the stream here:

			unsigned short offset = 0;

			// Store the number of each type used here:

			unsigned char position_count	= 0;			
			unsigned char color_count		= 0;
			unsigned char texcoord_count	= 0;
			unsigned char normal_count		= 0;
			unsigned char tangent_count		= 0;
			unsigned char binormal_count	= 0;

			// Make an array for direct3d vertex elements:

			D3DVERTEXELEMENT9 * elements = new D3DVERTEXELEMENT9[ num_elements + 1 ];

			// Fill the array:

			for ( int i = 0 ; i < num_elements ; ++i )
			{
				// Set the stream:

				elements[i].Stream = 0;

				// Set the offset in the stream:

				elements[i].Offset = offset;
				
				// Set the method for the vertex: no tesselation
				
				elements[i].Method = D3DDECLMETHOD_DEFAULT;

				// Set the usage of the vertex:

				switch ( usages[i] )
				{
					case VF_USAGE_POSITION:		
					{
						elements[i].Usage		= D3DDECLUSAGE_POSITION;	
						elements[i].UsageIndex	= position_count; 
						position_count++;
						
					}	break;
					
					case VF_USAGE_COLOR:		
					{
						elements[i].Usage		= D3DDECLUSAGE_COLOR;		
						elements[i].UsageIndex	= color_count;						
						color_count++;
						
					}	break;
					
					case VF_USAGE_TEXCOORD:		
					{
						elements[i].Usage		= D3DDECLUSAGE_TEXCOORD;
						elements[i].UsageIndex	= texcoord_count;
						texcoord_count++;
						
					}	break;
					
					case VF_USAGE_NORMAL:		
					{
						elements[i].Usage		= D3DDECLUSAGE_NORMAL;	
						elements[i].UsageIndex	= normal_count;
						normal_count++;
						
					}	break;
										
					case VF_USAGE_BINORMAL:		
					{
						elements[i].Usage		= D3DDECLUSAGE_BINORMAL;
						elements[i].UsageIndex	= binormal_count;
						binormal_count++;
						
					}	break;
					
					case VF_USAGE_TANGENT:		
					{
						elements[i].Usage		= D3DDECLUSAGE_TANGENT;	
						elements[i].UsageIndex	= tangent_count;	
						tangent_count++;
						
					}	break;
					
				}	// end switch vertex usage
				
				// Set the type of the vertex
				
				switch ( types[i] )
				{
					case VF_TYPE_FLOAT1:	elements[i].Type = D3DDECLTYPE_FLOAT1;		offset += 4;	break;
					case VF_TYPE_FLOAT2:	elements[i].Type = D3DDECLTYPE_FLOAT2;		offset += 8;	break;
					case VF_TYPE_FLOAT3:	elements[i].Type = D3DDECLTYPE_FLOAT3;		offset += 12;	break;
					case VF_TYPE_FLOAT4:	elements[i].Type = D3DDECLTYPE_FLOAT4;		offset += 16;	break;
					case VF_TYPE_COLOR:		elements[i].Type = D3DDECLTYPE_D3DCOLOR;	offset += 4;	break;
				}
			}
			
			// Set the last element in the array to the unused index:
			
			elements[num_elements].Stream		= 0xff;
			elements[num_elements].Offset		= 0;
			elements[num_elements].Type			= D3DDECLTYPE_UNUSED;
			elements[num_elements].Method		= 0;
			elements[num_elements].Usage		= 0;
			elements[num_elements].UsageIndex	= 0;
			
			// Calculate the size of this format:
			
			m_vertex_size = 0;
			
			// Do it:
			
			for ( unsigned i = 0; i < num_elements; ++i )
			{
				// See what type this element is:
			
				switch ( elements[i].Type )
				{
					case D3DDECLTYPE_FLOAT1:	m_vertex_size += 4;		break;
					case D3DDECLTYPE_FLOAT2:	m_vertex_size += 8;		break;
					case D3DDECLTYPE_FLOAT3:	m_vertex_size += 12;	break;
					case D3DDECLTYPE_FLOAT4:	m_vertex_size += 16;	break;
					case D3DDECLTYPE_D3DCOLOR:	m_vertex_size += 4;		break;
				}
			}
			
			// Now declare the declaration with directx:
			
			if ( device->CreateVertexDeclaration( elements , &m_vertex_declaration ) != D3D_OK )
			{
				// Failed: clear the vertex declaration
				
				m_vertex_declaration = 0;
			}
			else
			{
				// Success:
				
				m_is_valid = true;
			}
			
			// Done with these
			
			delete[] elements;
		}
	}
}

///====================================================================================================================
/// @brief		Destroys the vertex format
///====================================================================================================================
			
VertexFormat::~VertexFormat()
{
	// If we have a declaration then release it:
	
	if ( m_vertex_declaration != 0 )
	{
		m_vertex_declaration->Release();
	}

	// If we have a d3d device then release:

	if ( m_d3d_device != 0 ) 
	{
		m_d3d_device->Release();
	}
}
