//////////////////////////////////////////////////////////////////////////////
//
// Tools_MaterialResponse_System.h
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

#if defined ASURA_USE_PRAGMA_ONCE
    #pragma once
#endif

#if !defined __TOOLS_MATERIALRESPONSE_SYSTEM_H__
#define __TOOLS_MATERIALRESPONSE_SYSTEM_H__

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

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

#include "Core/Asura_Graph.h"
#include "Core/Asura_HashTree.h"
#include "Core/Asura_MaterialResponse_System.h"
#include "Core/Asura_String.h"
#include "Rendering/Asura_Colour.h"

/******************************************************************************/
/* Constants                                                                  */
/******************************************************************************/

const float fTOOLS_MATERIAL_RESPONSE_GRAPH_MAX = 200.0f;

#define szMATERIALS_DIRECTORY "Materials\\"
#define szMATERIALS_EXTENSION ".AsuraMaterial"

/******************************************************************************/
/* Classes                                                                    */
/******************************************************************************/

class Asura_Chunk_Stream;
class Asura_TreeDialog_Tree;
template< typename T > class Asura_TemplateHashTree;

////////////////////////////////////////////////////////////////////////////////
//
// Tools_MaterialResponse
//
// Holds data for an individual material setup. Only needed in Tools builds;
// retail builds will store it in the more minimal Asura_MaterialResponse
//
////////////////////////////////////////////////////////////////////////////////

class Tools_MaterialResponse
{
	// KJFL - 22 December 2006 - Way too many friends here: needs refactoring
	friend class Asura_MaterialResponse;
	friend class Asura_MaterialResponse_System;
	friend class Asura_Chunk_MaterialResponse;
	friend class Tools_Chunk_MaterialResponse;
	friend class Tools_MaterialProperties_Overlay;
	friend class Tools_SmoothSkin_Processor;
	friend class Tools_Texturing_MaterialResponse;

	friend void Asura_PC_MaterialResponse_UpdateTexture();
	friend void Asura_PC_MaterialResponse_InvalidTexture();
	template< class TPixelType> friend void Asura_PC_MaterialResponse_UpdateTexture_Fill( const float fTypeRange, const u_int uOffsetA, const u_int uOffsetB, const u_int uOffsetG, const u_int uOffsetR);

	public:

		// Types

		union PropertyCopyFlags
		{
			u_int m_uAllFlags;

			struct
			{
				u_int m_uEverything               : 1;
				u_int m_uResponses                : 1;
				u_int m_uAmbientResponse          : 1;
				u_int m_uDiffuseResponse          : 1;
				u_int m_uSpecularResponse         : 1;
				u_int m_uTransparencyResponse     : 1;
				u_int m_uSelfIlluminationResponse : 1;
				u_int m_uReflectivityResponse     : 1;
				u_int m_uTextures                 : 1;
				u_int m_uAlbedoMaps               : 1;
				u_int m_uNormalMaps               : 1;
				u_int m_uSpecularMaps             : 1;
				u_int m_uTransparencyMaps         : 1;
				u_int m_uSelfIlluminationMaps     : 1;
				u_int m_uReflectionTextures       : 1;
				u_int m_uTextureAnims             : 1;
				u_int m_uSpecialFX		          : 1;
				u_int m_uSurfaceType	          : 1;

				u_int m_uUnused                   : 14;
			};

		};

		//Old texture slot enumation. RAR 13/06/2007
		enum OLD_TEXTURE_SLOT_ID
		{
			OLD_ALBEDO_TEXTURE_1 = 0,
			OLD_ALBEDO_TEXTURE_2 = 1,

			OLD_NORMAL_MAP_1 = 2,
			OLD_NORMAL_MAP_2 = 3,
		
			OLD_SPECULAR_MAP_1 = 4,
			OLD_SPECULAR_MAP_2 = 5,

			OLD_TRANSPARENCY_MAP_1 = 6,
			OLD_TRANSPARENCY_MAP_2 = 7,

			OLD_SELFILLUMINATION_MAP_1 = 8,
			OLD_SELFILLUMINATION_MAP_2 = 9,

			OLD_ENVIRONMENT_MAP_1 = 10,
			OLD_ENVIRONMENT_MAP_2 = 11,

			OLD_REFLECTIVITY_MASK_1 = 12,
			OLD_REFLECTIVITY_MASK_2 = 13,

			OLD_PARALLAX_MAP_1 = 14,
			OLD_PARALLAX_MAP_2 = 15,


			OLD_NUMBER_OF_TEXTURE_SLOTS
		};

		// Bitmasks for the m_uChunkedFlags field, used by Tools dialogs
		enum
		{
			BITMASK_DECAL                       = 0x01,
			BITMASK_ZPREPASS                    = 0x02,
			BITMASK_IGNORE_SPECULAR_PRELIGHTING = 0x04,
		};

		// Constructors / destructor

		Tools_MaterialResponse();
		Tools_MaterialResponse( const Tools_MaterialResponse& xOther );
		~Tools_MaterialResponse();

		// Operators

		const Tools_MaterialResponse& operator =( const Tools_MaterialResponse& xOther );

		// Functions

		const Asura_String& GetName() const { return m_xName; }
		const Asura_Hash_ID GetHash() const { return GetName().GetHash(); }

		void SetName( const Asura_String& xMaterialName ) { m_xName = xMaterialName; }
	
		bool IsLoaded( void ) const { return m_bIsLoaded; }
		void LoadData( void );
		void LoadDataIfRequired( void );
		void MarkAsUnloaded( void ) { m_bIsLoaded = false; }

		void SetParentHash( const Asura_Hash_ID uParentHash ) { m_uParentHash = uParentHash; }
		Asura_Hash_ID GetParentHash() const { return m_uParentHash; }

		float GetAmbientContribution() const { return m_fAmbientContribution; }
		float GetColouriseAmbient() const { return m_fColouriseAmbient; }
		const Asura_RGBA& GetAmbientColour() const { return m_xAmbientColour; }

		float GetColouriseDiffuse() const { return m_fColouriseDiffuse; }
		const Asura_RGBA& GetDiffuseColour() const { return m_xDiffuseColour; }

		float GetMetalicity() const { return m_fMetalicity; }
		float GetColouriseSpecular() const { return m_fColouriseSpecular; }
		const Asura_RGBA& GetSpecularColour() const { return m_xSpecularColour; }

		int GetTexture( u_int uTextureSlot ) const;
		Asura_Hash_ID GetTextureHash( u_int uTextureSlot ) const;
		const char* GetTextureFilename( u_int uTextureSlot ) const;
		void SetTexture( u_int uTextureSlot, const int iTextureIndex );
		void ClearTexture( u_int uTextureSlot );

		Asura_Hash_ID GetTextureAnim() const;
		void SetTextureAnim(Asura_Hash_ID uAnimID);

		Asura_MaterialResponse::BLEND_MODE GetBlendMode( void ) const	{ return(m_eBlendMode); }
		Asura_MaterialResponse::RENDER_TYPE GetRenderType( void ) const	{ return(m_eRenderType); }

		void CopyPropertiesFrom( const Tools_MaterialResponse& xOther, PropertyCopyFlags xCopyFlags );
		void WriteTextureLineToStream( Asura_Chunk_Stream& xStream, const bool bScale = false ) const;

		bool HasSlaves() const;

		void ApplySpecularSharpness( const float fSpecularSharpness, const bool bOptimize );

		void SetModified( const bool bModified ) { m_bModified = bModified; }

		// Allow direct access to the pointer for project context controls
		bool* GetModifiedPtr() { return &m_bModified; }

		#ifdef ASURA_MAIN
		void SetNeedsSave(bool bNeedsSave );
		bool CanSaveFile(bool bShowWarning);
		#endif

		enum Asura_MaterialResponse::SPECIALFX_MODE GetSpecialFXMode( void ) const { return m_eSpecialFXMode; }

		//Gets the second material to use when converting from one material per poly to two per poly
		Asura_Hash_ID GetSecondMaterialID() const {return m_uSecondMaterialID;}

		bool GetIsDecal() const { return((m_uChunkedFlags & BITMASK_DECAL) != 0); }
		bool GetIsZPrePass() const { return((m_uChunkedFlags & BITMASK_ZPREPASS) != 0); }
		bool GetIgnoreSpecularPrelighting() const { return((m_uChunkedFlags & BITMASK_IGNORE_SPECULAR_PRELIGHTING) != 0); }

		// Allocation of texture rows in tools
		uint32		GetResponseRowIndex(void) {return m_uResponseRowIndex;}
		bool		AllocateResponseRowIndex(void);

		// static methods
		static const char* const* GetBlendModeTextStrings( void );
		static u_int GetNumberOfBlendModes( void ) { return Asura_MaterialResponse::NUMBER_OF_BLEND_MODES; }

		static const char* const* GetSpecialFXModeTextStrings( void );
		static u_int GetNumberOfSpecialFXModes( void ) { return Asura_MaterialResponse::NUMBER_OF_SPECIALFX_MODES; }

		u_int GetProjectContext() const {return m_uProjectContext;}
		void  SetProjectContext(u_int uProjectContext) {m_uProjectContext = uProjectContext;}

		// Allow direct access to the pointer for project context controls
		u_int* GetProjectContextPtr() { return &m_uProjectContext; }
		
		const Asura_Graph< float >& GetDiffuseGraph() const { return m_xDiffuseGraph; }
		const Asura_Graph< float >& GetSpecularGraph() const { return m_xSpecularGraph; }
		const Asura_Graph< float >& GetTransparencyGraph() const { return m_xTransparencyGraph; }

		// Transparent strips must be rendered after the deferred lights
		bool IsTransparent() const { return GetBlendMode() != Asura_MaterialResponse::BLEND_MODE_OPAQUE && !GetIsDecal(); }

		Asura_Hash_ID CalculatePartialShaderHash() const;

	protected:

		// Functions
		void CopyTextures( const bool bPerformCopy, const Tools_MaterialResponse& xFrom,
						   const Asura_MaterialResponse::TEXTURE_SLOT_ID eSlot );

		// Data

		// Name includes subdir of materials folder, excludes extension. e.g. Environments\\MyLevel\\MyMaterial
		Asura_String						m_xName;
		Asura_Hash_ID						m_uParentHash;

		float								m_fAmbientContribution;
		float								m_fColouriseAmbient;
		Asura_RGBA							m_xAmbientColour;

		Asura_Graph< float >				m_xDiffuseGraph;
		float								m_fColouriseDiffuse;
		Asura_RGBA							m_xDiffuseColour;

		float								m_fMetalicity;
		Asura_Graph< float >				m_xSpecularGraph;
		float								m_fColouriseSpecular;
		Asura_RGBA							m_xSpecularColour;

		Asura_Graph< float >				m_xTransparencyGraph;

		float								m_fSelfIlluminationContribution;
		float								m_fColouriseSelfIllumination;
		Asura_RGBA							m_xSelfIlluminationColour;

		float								m_fReflectivity;

		float								m_fParallaxScale;

		float								m_fAlbedoDetailMapUVScale;
		float								m_fAlbedoDetailMapValueScale;
		float								m_fNormalDetailMapUVScale;
		float								m_fNormalDetailMapValueScale;


		Asura_Hash_ID						m_auTextureHashIDs[Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS];
		Asura_String						m_axTextureFilenames[Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS];

		//Temporary storage for old materials that had two textures of each type. RAR 13/06/2007
		Asura_Hash_ID*						m_puOldTextureHashIDs;
		Asura_String*						m_pxOldTextureFilenames;
		Asura_Hash_ID*						m_puOldTextureAnimHashIDs;


		Asura_Hash_ID						m_uTextureAnimHashID;

		Asura_MaterialResponse::BLEND_MODE	m_eBlendMode;
		Asura_MaterialResponse::RENDER_TYPE	m_eRenderType;
	
		// Keep these as bools, needed by Tools dialog constructors
		bool								m_bModified;	// Set to true if need to recalc the material texture
		bool								m_bNeedsSave;	// Stays true if the material needs saving
		bool								m_bIsLoaded;

		// These flags are material properties, serialised in Tools_Chunk_MaterialResponse
		u_int								m_uChunkedFlags;


		// Can be used for whatever the project wants, no assumption is made by asura over what this means
		// other than 0 will be passed for the second ID when there is no second material
		u_int								m_uProjectContext;


		// New specialFX members 
		Asura_MaterialResponse::SPECIALFX_MODE m_eSpecialFXMode;

		// SpecialFX mode: Water
		Asura_RGBA							m_xWaterReflectionColour;
		Asura_RGBA							m_xWaterRefractionColour;
		Asura_RGBA							m_xWaterFogColour;
		float								m_fWaterFogStrength;
		float								m_afWaterUVControl[6];			// 2 layers, U/V scroll speed and scale for each
		float								m_fWaterRippleScale;
		float								m_fWaterDistortionScale;
		float								m_fWaterEdgeSoftness;
		float								m_afWaterWaveControl[6];		// Amplitude, Frequency, Speed along the X and Z axes

#if defined(ASURA_MATERIAL_RESPONSE_SURFACES_USE_HASH_IDS)
		Asura_Hash_ID						m_uSurfaceTypeHash;				// Allows surfaces to be specified by designer.
#else
		u_int								m_uSurfaceType;					// Takes values from a project defined enumeration. Used for specifying wood/stone/metal etc for playing footstep sounds , or similar
#endif	// defined(ASURA_MATERIAL_RESPONSE_SURFACES_USE_HASH_IDS)

		//Secondary material to use when converting old mesh data to having two material per poly.
		Asura_Hash_ID						m_uSecondMaterialID;

		uint32								m_uResponseRowIndex;			// Texture rows now allocated dynamically in tools as materials are used		

};

////////////////////////////////////////////////////////////////////////////////
//
// Tools_MaterialResponse_System
//
// Manager class for the material response system
//
////////////////////////////////////////////////////////////////////////////////


// Make sure Texture Usage matches texture slots
ASURA_STATIC_ASSERT( Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS == 10 ); 


class Tools_MaterialResponse_System
{
	public:

		// Ensure this matches order of Asura_MaterialResponse::TEXTURE_SLOT_ID
		enum TextureUsage
		{
			TU_ALBEDO            = 0x001,
			TU_NORMAL            = 0x002,
			TU_SPECULAR          = 0x004,
			TU_TRANSPARENCY      = 0x008,
			TU_SELF_ILLUMINATION = 0x010,
			TU_ENVIRONMENT       = 0x020,
			TU_REFLECTIVITY      = 0x040,
			TU_PARALLAX          = 0x080,
			TU_DETAIL_MAP		 = 0x100,
			TU_DETAIL_NORMAL_MAP = 0x200,
	

			TU_SINGLE_CHANNEL   = TU_SPECULAR
								| TU_TRANSPARENCY
								| TU_SELF_ILLUMINATION
								| TU_REFLECTIVITY
								| TU_PARALLAX,

			TU_COLOUR_CHANNELS	= TU_ALBEDO
								| TU_ENVIRONMENT
								| TU_DETAIL_MAP,
			TU_NORMAL_CHANNELS	= TU_NORMAL
								| TU_DETAIL_NORMAL_MAP,
		};

		static void Initialise();
		static void Reset();
		static void Shutdown();

		static void LoadMaterials();
		static void LogMaterials( void );

		static void MakeMaterialNameFromPath( const Asura_String& xPath, Asura_String& xResult );
		static void MakePathFromMaterialName( const Asura_String& xName, Asura_String& xResult );

		static int CompareMaterials( const Asura_Hash_ID uMaterialHash1, const Asura_Hash_ID uMaterialHash2 );
		static int CompareMaterials( const Tools_MaterialResponse& xMaterial1, const Tools_MaterialResponse& xMaterial2 );

		static Tools_MaterialResponse* GetMaterialByHashID( const Asura_Hash_ID uNameHashID, int* const piMaterialIndex = NULL );
		static Tools_MaterialResponse* GetMaterialByIndex( const u_int uIndex );
		static Tools_MaterialResponse* const GetMaterialByIndex_WithoutLoading( const u_int uIndex );

		static const char* GetMaterialNameByIndex( const u_int uIndex );
		static void GetMaterialNames( int& iNumMaterials, const char**& pszMaterialNames );
		static u_int GetNumMaterials();

		static u_int GetTextureUsage( const Asura_Hash_ID uTextureNameHash );

		typedef bool MaterialTreeFilterByMaterialFn( const Tools_MaterialResponse& xMaterial, const void* const pUserData );
		static void CreateMaterialNameTree( Asura_TreeDialog_Tree& xTree, MaterialTreeFilterByMaterialFn* const pfnFilter = NULL, const void* const pUserData = NULL );

		//variation of CreateMaterialNameTree where the filter function just receives a hash ID , which prevents
		//the need for all the materials being loaded
		typedef bool MaterialTreeFilterByHashFn( Asura_Hash_ID uMaterialHash, const void* const pUserData );
		static void CreateMaterialNameTree( Asura_TreeDialog_Tree& xTree, MaterialTreeFilterByHashFn* const pfnFilter, const void* const pUserData);
		
		// return a pointer to a new blank material - or the existing material already using this hash
		static Tools_MaterialResponse* AddMaterial( const Asura_Hash_ID uHashID )
		{
			return s_xMaterialTree.AddOrFindNode( uHashID );
		}

		// return a pointer to a new material copy constructed from the source material
		static Tools_MaterialResponse* AddDuplicateMaterial( const Asura_Hash_ID uHashID, const Tools_MaterialResponse& xSource )
		{
			return s_xMaterialTree.AddNode( uHashID, xSource );
		}

		// "rename" the material using the new hash
		static void ReplaceMaterialHash( Asura_Hash_ID uOldHash, Asura_Hash_ID uNewHash )
		{
			s_xMaterialTree.ReplaceHash( uOldHash, uNewHash );
		}


		static uint32 GetNextResponseRowIndex()
		{
			uint32 uRet = s_uNextResponseRowIndex;
			s_uNextResponseRowIndex++;
			return uRet;
		};

		//Adds to the list of known material names
		static void AddToMaterialNameTree(const char* szName);
		//Gets a name from hash ID if available. The matching material may not exist.
		//if bCheckAllKnownMaterialNames is false ,then only existing materials will be checked
		static const char* GetMaterialNameByHashID(Asura_Hash_ID uHash, bool bCheckAllKnownMaterialNames = true);

		//Gets a string to use for missing materials in asserts
		//Uses a static buffer , so the string is only valid until the next call
		static const char* GetMissingMaterialDescription(Asura_Hash_ID uHash);

		//Access to entries in the material name tree
		static int GetMaterialNameTreeSize(){return s_xMaterialNameTree.GetNumberOfNodes();}
		static const char* GetMaterialNameTreeEntryByIndex(int i){return s_xMaterialNameTree.GetDataByIndex(i)->GetString();}

		static const char* GetTextureSlotDescription(u_int u) { Asura_Assert( u < Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS, "" ); return s_aszTextureSlotDescription[ u ]; }

		//makes sure that all the materials in loaded.
		//can be called before something that is going to end up loading them all anyway , to give some progress on it.
		static void EnsureAllMaterialsAreLoaded();

	protected:
		static void CreateMaterialNameTreeInternal(Asura_TreeDialog_Tree& xTree, MaterialTreeFilterByMaterialFn* const pfnFilterByMaterial , MaterialTreeFilterByHashFn* const pfnFilterByHash, const void* const pUserData);

		static Asura_TemplateHashTree< Tools_MaterialResponse > s_xMaterialTree;
		//Tree contains list of material names that have been loaded from asura files.
		//It won't necessarily contain all material names , but is instead a backup place to lookup
		//names from hashes of materials that no longer exist. RAR 21/09/2007
		static Asura_TemplateHashTree< Asura_String > s_xMaterialNameTree;
		static uint32 s_uNextResponseRowIndex;
		static const char* s_aszTextureSlotDescription[ Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS ];

		static bool s_bHasLoadedAllMaterials;
};


////////////////////////////////////////////////////////////////////////////////

//Simple structure storing the material hash IDs for a polygon. Used by a few of the mesh processing classes
//Doesn't really belong here, but I haven't decided on a good home for this yet. RAR 20/06/2007
class Tools_PolygonMaterialResponseIDs
{
public :
	Tools_PolygonMaterialResponseIDs(Asura_Hash_ID uMat1,Asura_Hash_ID uMat2)
	{
		m_auMaterialResponseHashID[0] = uMat1;
		m_auMaterialResponseHashID[1] = uMat2;
	}

	bool operator == (const Tools_PolygonMaterialResponseIDs& xOther) const
	{
		return m_auMaterialResponseHashID[0] == xOther.m_auMaterialResponseHashID[0] 
		&& m_auMaterialResponseHashID[1] == xOther.m_auMaterialResponseHashID[1];
	}

	bool operator != (const Tools_PolygonMaterialResponseIDs& xOther) const
	{
		return !(*this == xOther);
	}

	Asura_Hash_ID GetMaterial(int iIndex) const {return m_auMaterialResponseHashID[iIndex];}
	void SetMaterial(int iIndex, Asura_Hash_ID uMaterialID) {m_auMaterialResponseHashID[iIndex] = uMaterialID;}

private :

	Asura_Hash_ID m_auMaterialResponseHashID[2];
};


#endif // defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

#endif // !defined __TOOLS_MATERIALRESPONSE_SYSTEM_H__
