//////////////////////////////////////////////////////////////////////////////
//
// Asura_Chunk_MaterialResponse.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

/******************************************************************************/
/* Included Files                                                             */
/******************************************************************************/

#include "Core/Asura.h"

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

#include "ChunkSystem/Asura_Chunk_MaterialResponse.h"

// Asura Platform-independent
#include "ChunkSystem/Asura_Chunk_Loaders.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "ChunkSystem/Asura_SaveBlock.h"
#include "Core/Asura_MaterialResponse_System.h"

#if defined ASURA_TOOLS
#include "Core/Asura_HashTree.h"
#include "Materials/Tools_MaterialResponse_System.h"
#endif

/******************************************************************************/
/* Asura_Chunk_MaterialResponse Static Class Data                             */
/******************************************************************************/

const int Asura_Chunk_MaterialResponse::iCURRENT_VERSION = 19;

/******************************************************************************/
/* Asura_Chunk_MaterialResponse Class Functions                               */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Chunk_MaterialResponse::Process()
//
// Process a chunk containing material responses and adds them to the system
//
// Parameters:		iFileHandle - Handle to file from which to read the chunk
// Return value:	true iff successful
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_Chunk_MaterialResponse::Process( const Asura_Handle iFileHandle )
{
	if( Version > iCURRENT_VERSION || Version < 8)
	{
		#if defined ASURA_ASSERT
			const char* const szAssertText = Version < iCURRENT_VERSION
										   ? "Material response chunk is too old (version %d, should be %d.) Re-export the level."
										   : "Material response chunk is too new (version %d, should be %d.) You need a new build.";
		#endif

		Asura_Assertf( false, (szAssertText, Version, iCURRENT_VERSION) );

		return false;
	}

	Asura_Chunk_File_Stream xStream( iFileHandle );

	int iNumMaterials;
	xStream >> iNumMaterials;

	for( int i = 0; i < iNumMaterials; ++i )
	{
		Asura_Hash_ID uHashID;

		xStream >> uHashID;

		Asura_MaterialResponse* const pxNewMaterial = Asura_MaterialResponse_System::CreateNewMaterial( uHashID );

		if (!pxNewMaterial)
		{
			if (Asura_GetHashID("default")!=uHashID)
			{
#if defined(ASURA_ASSERT) && !defined(ASURA_TOOLS)
				// Did this fail because it was already in the tree? If so, that's probably OK, as separately-exported files may end up with the same material in them.
				if (!Asura_MaterialResponse_System::GetMaterialByHashID(uHashID))
#endif //#if defined(ASURA_ASSERT) && !defined(ASURA_TOOLS)
				{
					Asura_Assert( false, "Code Bug: Allocation of new material unsuccessful!" );
				}
			}

			// NB: make sure the amount to skip stays up to date with chunk modifications

			u_int uSkipBytes = sizeof( int )				// Material index
							 + sizeof( Asura_Hash_ID ) * 5	// Texture hashes
							 + sizeof( float )				// Ambient contribution
							 + sizeof( Asura_RGBA );		// Ambient colour

			uSkipBytes += sizeof( float );					// Prelighting colourise

			uSkipBytes += sizeof( Asura_Hash_ID );			// Texture animation hashes

			uSkipBytes += sizeof( u_int );					// Blend mode

			uSkipBytes += sizeof( Asura_Hash_ID );			// Environment map hashes

			uSkipBytes += sizeof( float )					// Self-illumination contribution
						+ sizeof( Asura_RGBA )				// Self-illumination colour
						+ sizeof( float );					// Self-illumination colourise

			uSkipBytes += sizeof( u_int )					// Special FX mode
						+ sizeof( Asura_RGBA )				// Water reflection colour
						+ sizeof( Asura_RGBA )				// Water refraction colour
						+ sizeof( Asura_RGBA )				// Water fog colour
						+ sizeof( float )					// Water fog strength
						+ sizeof( float ) * 6				// Water UV controls
						+ sizeof( float )					// Water ripple scale						
						+ sizeof( float )					// Water edge softness
						+ sizeof( float ) * 6;				// Water wave controls

			if( Version >= 13 )
			{
				uSkipBytes+= sizeof( float );				// Water distortion scale
			}

			uSkipBytes += sizeof( u_int );					// Surface type

			uSkipBytes += sizeof( Asura_Hash_ID )			// Reflectivity mask hashes
						+ sizeof( float );					// Reflectivity

			if( Version >= 9 )
			{
				uSkipBytes += sizeof( Asura_Hash_ID );		// Parallax map hashes
				uSkipBytes += sizeof( float );				// Parallax scale
			}
			if( Version >= 16 )
			{
				uSkipBytes += sizeof( Asura_Hash_ID ) * 2;	// Detail map hashes
				uSkipBytes += sizeof( float ) * 4;			// Detail scales
			}

			if (Version >= 18)
			{
				uSkipBytes += sizeof( u_int );				//  pxNewMaterial->m_uProjectContext;
			} 
			
			if( Version < 10 )
			{
				uSkipBytes += 9*sizeof(Asura_Hash_ID);		//8 secondary textures and a secondary UV anim removed going from 9 to 10
			}

			if( Version >= 17 )
			{
				uSkipBytes += 2 * sizeof( u_int );			// Render-type and Chunked flags
			}
			else if( Version >= 14 )
			{
				uSkipBytes += sizeof( u_int );				// Chunked flags
			}
			else
			{
				if( Version >= 11 )
				{
					uSkipBytes += sizeof( u_char );			// Decal flag (bools chunked as u_chars)
				}

				if( Version >= 12 )
				{
					uSkipBytes += 2*sizeof( u_char );		// Don't render and always casts shadows flags (bools chunked as u_chars)
				}
			}

			if( Version >= 15 )
			{
				uSkipBytes += sizeof( float ) * 2;			// Diffuse & specular graph scales
			}

			if( Version >= 19 )
			{
				uSkipBytes += sizeof( Asura_Hash_ID );		// Partial shader hash
			}

			xStream.Skip( uSkipBytes );

			continue;
		}

		xStream >> pxNewMaterial->m_iMaterialIndex;
		pxNewMaterial->m_iMaterialIndex += Asura_MaterialResponse_System::GetNumMaterialsTextureLines();

		if(Version >= 10)
		{
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::ALBEDO_TEXTURE ];
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::NORMAL_MAP ];
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::SPECULAR_MAP ];
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::TRANSPARENCY_MAP ];
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::SELFILLUMINATION_MAP ];
		}
		else
		{
			//No longer have the secondary textures , so skip every second one
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::ALBEDO_TEXTURE ];
			xStream.Skip(sizeof(u_int));
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::NORMAL_MAP ];
			xStream.Skip(sizeof(u_int));
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::SPECULAR_MAP ];
			xStream.Skip(sizeof(u_int));
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::TRANSPARENCY_MAP ];
			xStream.Skip(sizeof(u_int));
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::SELFILLUMINATION_MAP ];
			xStream.Skip(sizeof(u_int));
		}
		
		xStream >> pxNewMaterial->m_fAmbientContribution;
		xStream >> pxNewMaterial->m_xAmbientColour;

		xStream >> pxNewMaterial->m_fPrelightingColourise;

		xStream >> pxNewMaterial->m_uTextureAnimHashID;
		if(Version < 10) xStream.Skip(sizeof(Asura_Hash_ID));

		{
			// Can't stream in an enum value, so we're using a temp u_int
			u_int uTemp;
			xStream >> uTemp;
				
			pxNewMaterial->m_eBlendMode = (enum Asura_MaterialResponse::BLEND_MODE) uTemp;
		}

		xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::ENVIRONMENT_MAP ];
		if(Version < 10) xStream.Skip(sizeof(Asura_Hash_ID));

		xStream >> pxNewMaterial->m_fSelfIlluminationContribution;
		xStream >> pxNewMaterial->m_xSelfIlluminationColour;
		xStream >> pxNewMaterial->m_fSelfIlluminationColourise;

		// Special FX properties

		{
			u_int uTemp;
			xStream >> uTemp;
			pxNewMaterial->m_eSpecialFXMode = (enum Asura_MaterialResponse::SPECIALFX_MODE) uTemp;
		}

		xStream >> pxNewMaterial->m_xWaterReflectionColour;
		xStream >> pxNewMaterial->m_xWaterRefractionColour;
		xStream >> pxNewMaterial->m_xWaterFogColour;
		xStream >> pxNewMaterial->m_fWaterFogStrength;

		for(int j=0;j<6;++j)
		{
			xStream >> pxNewMaterial->m_afWaterUVControl[j];		
		}

		xStream >> pxNewMaterial->m_fWaterRippleScale;
		
		if( Version >= 13 )
		{		
			xStream >> pxNewMaterial->m_fWaterDistortionScale;
		}
		xStream >> pxNewMaterial->m_fWaterEdgeSoftness;

		for(int j=0;j<6;++j)
		{
			xStream >> pxNewMaterial->m_afWaterWaveControl[j];		
		}

#if defined(ASURA_MATERIAL_RESPONSE_SURFACES_USE_HASH_IDS)
		xStream >> pxNewMaterial->m_uSurfaceTypeHash;
#else
		xStream >> pxNewMaterial->m_uSurfaceType;
#endif	// defined(ASURA_MATERIAL_RESPONSE_SURFACES_USE_HASH_IDS)

		xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::REFLECTIVITY_MASK ];
		if(Version<10) xStream.Skip(sizeof(Asura_Hash_ID));

		xStream >> pxNewMaterial->m_fReflectivity;

		if( Version >= 9 )
		{
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::PARALLAX_MAP ];
			if(Version < 10) xStream.Skip(sizeof(Asura_Hash_ID));

			xStream >> pxNewMaterial->m_fParallaxScale;
		}
		if( Version >= 16 )
		{
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::DETAIL_MAP ];
			xStream >> pxNewMaterial->m_auTextureHashes[ Asura_MaterialResponse::DETAIL_NORMAL_MAP ];

			xStream >> pxNewMaterial->m_fAlbedoDetailMapUVScale;
			xStream >> pxNewMaterial->m_fAlbedoDetailMapValueScale;
			xStream >> pxNewMaterial->m_fNormalDetailMapUVScale;
			xStream >> pxNewMaterial->m_fNormalDetailMapValueScale;
		}
		if (Version >= 18)
		{
			xStream >> pxNewMaterial->m_uProjectContext;
		} else {
			pxNewMaterial->m_uProjectContext = 0;

		}

		if( Version >= 17 )
		{
			Asura_ReadFromStream_Bitfield_Wrapper<u_int>	xBitfield(pxNewMaterial->m_uChunkedFlags);
			u_int											uRenderType;

			xStream >> xBitfield;
			xStream >> uRenderType;

			pxNewMaterial->m_eRenderType	= static_cast<Asura_MaterialResponse::RENDER_TYPE>(uRenderType);
		}
		else if( Version >= 14 )
		{
			union
			{
				u_int		m_uChunkedFlags;

				struct
				{
					u_int	m_bDontRender			: 1;
					u_int	m_bAlwaysCastShadows	: 1;
					u_int	m_bDecal				: 1;
				};
			} xOldChunkedFlags;

			// Switched to using a bitfield for the flags
			Asura_ReadFromStream_Bitfield_Wrapper< u_int >
				xBitfield( xOldChunkedFlags.m_uChunkedFlags );

			xStream >> xBitfield;

			pxNewMaterial->m_bDecal	= xOldChunkedFlags.m_bDecal;
			if(xOldChunkedFlags.m_bDontRender)
			{
				if(xOldChunkedFlags.m_bAlwaysCastShadows)
				{
					pxNewMaterial->m_eRenderType	= Asura_MaterialResponse::RENDER_TYPE_DONT_RENDER_BUT_CAST_SHADOW;
				}
				else
				{
					pxNewMaterial->m_eRenderType	= Asura_MaterialResponse::RENDER_TYPE_DEFAULT;
				}
			}
			else
			{
				pxNewMaterial->m_eRenderType	= Asura_MaterialResponse::RENDER_TYPE_DEFAULT;
			}
		}
		else
		{
			// bools are chunked as u_chars
			u_char ucTemp;

			if( Version >= 11 )
			{
				xStream >> ucTemp;
				pxNewMaterial->m_bDecal = ucTemp;
			}
			else
			{
				// Before the decal flag was added, transparent blend modes just meant decal-type transparency anyway
				pxNewMaterial->m_bDecal = (pxNewMaterial->GetBlendMode() != Asura_MaterialResponse::BLEND_MODE_OPAQUE);
			}

			if( Version >= 12 )
			{
				u_char	ucDontRender;
				u_char	ucAlwaysCastShadows;

				xStream >> ucDontRender;
				xStream >> ucAlwaysCastShadows;

				if(ucDontRender)
				{
					if(ucAlwaysCastShadows)
					{
						pxNewMaterial->m_eRenderType	= Asura_MaterialResponse::RENDER_TYPE_DONT_RENDER_BUT_CAST_SHADOW;
					}
					else
					{
						pxNewMaterial->m_eRenderType	= Asura_MaterialResponse::RENDER_TYPE_DEFAULT;
					}
				}
				else
				{
					pxNewMaterial->m_eRenderType	= Asura_MaterialResponse::RENDER_TYPE_DEFAULT;
				}
			}
		}

		if( Version >= 15 )
		{
			xStream >> pxNewMaterial->m_fDiffuseGraphScale;
			xStream >> pxNewMaterial->m_fSpecularGraphScale;
		}

		if( Version >= 19 )
		{
			xStream >> pxNewMaterial->m_uPartialShaderHash;
		}

		Asura_MaterialResponse_System::InitialiseMaterial( uHashID, *pxNewMaterial );
	}

	return true;
}

/******************************************************************************/
/* Asura_Chunk_MaterialResponse Static Class Functions                        */
/******************************************************************************/

#if defined ASURA_TOOLS

void Asura_Chunk_MaterialResponse::Create( Asura_SaveBlock& xSaveBlock, Asura_HashTree* const pxMaterialsToExport, Asura_HashTree* const pxSlaveMaterialsToExport )
{
	Asura_Chunk_SaveBlock_Stream xStream( &xSaveBlock );

	xStream.WriteChunk_Start( ASURA_CHUNK_MATERIAL_RESPONSE, iCURRENT_VERSION, 0 );

	int iNumberOfMaterialsToExport = pxMaterialsToExport->GetNumberOfNodes();
	int iNumberOfSlavesToExport = pxSlaveMaterialsToExport->GetNumberOfNodes();
	
	xStream << iNumberOfMaterialsToExport + iNumberOfSlavesToExport;

	for( int i = 0; i < iNumberOfMaterialsToExport + iNumberOfSlavesToExport; ++i )
	{
		Asura_Hash_ID uCurrHash;
		if( i < iNumberOfMaterialsToExport )
		{
			uCurrHash = pxMaterialsToExport->FindHashIDByIndex(i);
		}
		else
		{
			uCurrHash = pxSlaveMaterialsToExport->FindHashIDByIndex( i - iNumberOfMaterialsToExport );
		}

		const Tools_MaterialResponse* const pxCurrMaterial = Tools_MaterialResponse_System::GetMaterialByHashID( uCurrHash );

		xStream << uCurrHash;

		const Tools_MaterialResponse* const pxParentMaterial = Tools_MaterialResponse_System::GetMaterialByHashID( pxCurrMaterial->GetParentHash() );
		if( pxCurrMaterial->GetParentHash() != ASURA_HASH_ID_UNSET && !pxParentMaterial )
		{
			Asura_Assertf( false, ("Failed to find parent 0x%X for material %s.", pxCurrMaterial->GetParentHash(), pxCurrMaterial->GetName().GetString()) );
		}

		// Output parent index if we're a slave
		if( pxParentMaterial )
		{
			int iParentIndex;
			if( pxMaterialsToExport->FindNodeIndexByHash( pxCurrMaterial->GetParentHash(), false, iParentIndex ) )
			{
				xStream << iParentIndex;
			}
			else
			{
				Asura_Assertf( false, ("Parent %s for material %s isn't in the export list.", pxParentMaterial->GetName().GetString(), pxCurrMaterial->GetName().GetString() ) );
				xStream << i;
			}
		}
		else
		{
			xStream << i;
		}

		for ( u_int u=0; u<Asura_MaterialResponse::ENVIRONMENT_MAP; ++u)
		{
			xStream << pxCurrMaterial->m_auTextureHashIDs[u];
		}
		
		if( pxParentMaterial )
		{
			xStream << pxParentMaterial->m_fAmbientContribution / 100.0f;
			xStream << pxParentMaterial->m_xAmbientColour;

			xStream << pxParentMaterial->m_fColouriseAmbient / 100.0f;
		}
		else
		{
			xStream << pxCurrMaterial->m_fAmbientContribution / 100.0f;
			xStream << pxCurrMaterial->m_xAmbientColour;

			xStream << pxCurrMaterial->m_fColouriseAmbient / 100.0f;
		}

		xStream << pxCurrMaterial->m_uTextureAnimHashID;


		// stream out enum value as a u_int
		xStream << (u_int)(pxParentMaterial ? pxParentMaterial->m_eBlendMode : pxCurrMaterial->m_eBlendMode);

		xStream << pxCurrMaterial->m_auTextureHashIDs[Asura_MaterialResponse::ENVIRONMENT_MAP];

		if( pxParentMaterial )
		{
			xStream << pxParentMaterial->m_fSelfIlluminationContribution / 100.0f;
			xStream << pxParentMaterial->m_xSelfIlluminationColour;
			xStream << pxParentMaterial->m_fColouriseSelfIllumination / 100.0f;
		} else {
			xStream << pxCurrMaterial->m_fSelfIlluminationContribution / 100.0f;
			xStream << pxCurrMaterial->m_xSelfIlluminationColour;
			xStream << pxCurrMaterial->m_fColouriseSelfIllumination / 100.0f;
		}

		// Special FX properties
		xStream << (u_int)pxCurrMaterial->m_eSpecialFXMode;			

		xStream << pxCurrMaterial->m_xWaterReflectionColour;
		xStream << pxCurrMaterial->m_xWaterRefractionColour;
		xStream << pxCurrMaterial->m_xWaterFogColour;
		xStream << pxCurrMaterial->m_fWaterFogStrength;

		for(int i=0;i<6;i++)
		{
			xStream << pxCurrMaterial->m_afWaterUVControl[i];		
		}

		xStream << pxCurrMaterial->m_fWaterRippleScale;
		xStream << pxCurrMaterial->m_fWaterDistortionScale;
		xStream << pxCurrMaterial->m_fWaterEdgeSoftness;	

		for(int i=0;i<6;i++)
		{
			xStream << pxCurrMaterial->m_afWaterWaveControl[i];		
		}

#if defined(ASURA_MATERIAL_RESPONSE_SURFACES_USE_HASH_IDS)
		xStream << pxCurrMaterial->m_uSurfaceTypeHash;
#else
		xStream << pxCurrMaterial->m_uSurfaceType;
#endif	// defined(ASURA_MATERIAL_RESPONSE_SURFACES_USE_HASH_IDS)

		xStream << pxCurrMaterial->m_auTextureHashIDs[Asura_MaterialResponse::REFLECTIVITY_MASK];

		if( pxParentMaterial )
		{
			xStream << pxParentMaterial->m_fReflectivity / 100.0f;
		} else {
			xStream << pxCurrMaterial->m_fReflectivity / 100.0f;

		}

		xStream << pxCurrMaterial->m_auTextureHashIDs[Asura_MaterialResponse::PARALLAX_MAP];

		xStream << pxCurrMaterial->m_fParallaxScale / 100.0f;

		xStream << pxCurrMaterial->m_auTextureHashIDs[Asura_MaterialResponse::DETAIL_MAP];
		xStream << pxCurrMaterial->m_auTextureHashIDs[Asura_MaterialResponse::DETAIL_NORMAL_MAP];
		xStream << pxCurrMaterial->m_fAlbedoDetailMapUVScale;
		xStream << pxCurrMaterial->m_fAlbedoDetailMapValueScale;
		xStream << pxCurrMaterial->m_fNormalDetailMapUVScale;
		xStream << pxCurrMaterial->m_fNormalDetailMapValueScale;

		xStream << pxCurrMaterial->m_uProjectContext;

		{
			const u_int& uFlags = pxParentMaterial
				? pxParentMaterial->m_uChunkedFlags
				: pxCurrMaterial->m_uChunkedFlags;

			const bool bIsDecal = pxParentMaterial
				? pxParentMaterial->GetIsDecal()
				: pxCurrMaterial->GetIsDecal();

			const bool bIsZPrePass = pxParentMaterial
				? pxParentMaterial->GetIsZPrePass()
				: pxCurrMaterial->GetIsZPrePass();

			const bool bIgnoreSpecularPrelighting = pxParentMaterial
				? pxParentMaterial->GetIgnoreSpecularPrelighting()
				: pxCurrMaterial->GetIgnoreSpecularPrelighting();

			Asura_WriteToStream_Bitfield_Wrapper< u_int > xBitfield( uFlags );

			xBitfield.Add( bIsDecal, 1 );
			xBitfield.Add( bIsZPrePass, 1 );
			xBitfield.Add( bIgnoreSpecularPrelighting, 1 );

			xStream << xBitfield;
		}

		xStream << static_cast<u_int>((pxParentMaterial == NULL) ? pxCurrMaterial->m_eRenderType : pxParentMaterial->m_eRenderType);

		// The graph scales are the maximum discrete values
		{
			const Asura_Graph< float >& xDiffuseGraph = pxParentMaterial
													  ? pxParentMaterial->m_xDiffuseGraph
													  : pxCurrMaterial->m_xDiffuseGraph;

			const Asura_Graph< float >& xSpecularGraph = pxParentMaterial
													   ? pxParentMaterial->m_xSpecularGraph
													   : pxCurrMaterial->m_xSpecularGraph;

			xStream << xDiffuseGraph.GetMaxDiscreteValue() / 100.0f;
			xStream << xSpecularGraph.GetMaxDiscreteValue() / 100.0f;
		}

		const Asura_Hash_ID uPartialShaderHash = pxCurrMaterial->CalculatePartialShaderHash();
		xStream << uPartialShaderHash;
	}

	xStream.WriteChunk_End();
}

#endif // defined ASURA_TOOLS

#endif // defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
