#include "../../Engine/Base/RuMaterialBase.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

ruRTTI_DEFINE_SUBCLASS(IRuMaterial, IRuObject)
ruCLASSGUID_DEFINE(IRuMaterial, 0x8129E034, 0xBD474b3d, 0xBC2C5D54, 0x7F8852FB)

ruRTTI_DEFINE_SUBCLASS(CRuMaterial, IRuMaterial)
ruHEAP_DEFINE_SUBCLASS(CRuMaterial, "CRuMaterial", "IRuObject")
ruCLASSGUID_DEFINE(CRuMaterial, 0xCEE4F7EB, 0x85FA4127, 0xA4751BE8, 0x5BA6ADCC)

// ************************************************************************************************************************************************************

CRuMaterial::CRuMaterial()
:	m_shaderName(NULL),
	m_shader(NULL),
	m_materialPriority(1000),
	m_isDeprecatedMaterial(FALSE)
{
}

CRuMaterial::~CRuMaterial()
{
	// Free all textures
	for(INT32 i = 0; i < m_textures.Count(); i++)
	{
		ruSAFE_RELEASE(m_textures[i].m_texture);
	}
	m_textures.Clear();

	// Free shader name string
	if(m_shaderName)
		delete[] m_shaderName;
}

IRuObject *CRuMaterial::Clone(IRuObject *clonedObject)
{
	// Create a new cloned entity if necessary
	clonedObject = clonedObject ? clonedObject : ruNEW CRuMaterial();

	// Chain cloning call to parent class
	IRuMaterial::Clone(clonedObject);

	// Shortcut reference to the cloned entity
	CRuMaterial *clonedMaterial = static_cast<CRuMaterial *>(clonedObject);

	for(INT32 i = 0; i < m_textures.Count(); ++i)
	{
		clonedMaterial->SetTexture(m_textures[i].m_textureChannel, m_textures[i].m_texture);
	}

	if(m_shaderName)
	{
		clonedMaterial->m_shaderName = ruNEW char [strlen(m_shaderName) + 1];
		strcpy(clonedMaterial->m_shaderName, m_shaderName);
	}

	clonedMaterial->m_shader = m_shader;
	clonedMaterial->m_parameterBlock = m_parameterBlock;
	clonedMaterial->m_materialPriority = m_materialPriority;

	clonedMaterial->m_isDeprecatedMaterial = m_isDeprecatedMaterial;
	clonedMaterial->m_materialType = m_materialType;
	clonedMaterial->m_lightingType = m_lightingType;

	return clonedMaterial;
}

BOOL CRuMaterial::SerializeFrom(IRuStream *stream)
{
	CRuGUID classGUID;

	// Read class GUID
	if(RuReadGUID(stream, classGUID) == FALSE)
	{
		return FALSE;
	}

	// Read signature and version
	INT32 signature;
	INT32 version = 1;
	stream->Read(&signature, sizeof(INT32));

	if(signature == 0xFFFFFFFF)
	{
		// Signature found, read version 
		stream->Read(&version, sizeof(INT32));
	}
	else
	{
		// No signature, this is an old model stream, so rewind
		stream->Seek(-4, ruSSM_Current);
	}

	switch(version)
	{
		case 1:
			{
				// Read shader name length & name (including NULL)
				INT32 shaderNameLength = 0;
				stream->Read(&shaderNameLength, sizeof(INT32));

				m_shaderName = ruNEW char [shaderNameLength];
				stream->Read(m_shaderName, shaderNameLength * sizeof(BYTE));

				// Set shader
				IRuShader *shader = NULL;
				if(m_shaderName && strlen(m_shaderName) > 0)
				{
					shader = g_ruResourceManager->GetShader(m_shaderName);
				}

				if(shader == NULL)
				{
					shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_SH9");
				}

				SetShader(shader);

				m_isDeprecatedMaterial = TRUE;
			}

			break;

		case 2:
			{
				// Read material type
				stream->Read(&m_materialType, sizeof(INT32));

				// Read lighting type
				stream->Read(&m_lightingType, sizeof(INT32));

				m_isDeprecatedMaterial = FALSE;
			}

			break;
	}

	// Read number of texture channels
	INT32 numTexChannels;
	stream->Read(&numTexChannels, sizeof(INT32));

	// Read each texture channel
	for(INT32 i = 0; i < numTexChannels; ++i)
	{
		// Read texture channel ID
		RuTextureChannel texChannel;
		stream->Read(&texChannel, sizeof(RuTextureChannel));

		// Read texture name length
		INT32 textureNameLength;
		stream->Read(&textureNameLength, sizeof(INT32));

		// Read texture name
		char textureName[4096];
		stream->Read(textureName, textureNameLength * sizeof(BYTE));

		IRuBaseTexture *texture = g_ruResourceManager->LoadTexture(textureName);
		if(texture)
		{
			this->SetTexture(texChannel, texture);
			texture->Release();
		}
	}

	// Read number of parameters
	INT32 numParameters;
	stream->Read(&numParameters, sizeof(INT32));

	// Read each parameter
	for(INT32 i = 0; i < numParameters; ++i)
	{
		// Read parameter type
		RuParameterType paramType;
		stream->Read(&paramType, sizeof(RuParameterType));

		// Read parameter length
		INT32 paramLength = 0;
		stream->Read(&paramLength, sizeof(INT32));

		switch(paramType)
		{
			case ruPARAMTYPE_LIGHT_SELF:
				{
					REAL paramValue = 0.0f;
					stream->Read(&paramValue, sizeof(REAL));
					m_parameterBlock.SetParameter(paramType, RuCastFloatToDWORD(paramValue));
				}

				break;

			case ruPARAMTYPE_GEOMETRY_2SIDED:
			case ruPARAMTYPE_GEOMETRY_FACETED:
			case ruPARAMTYPE_GEOMETRY_WIRE:
			case ruPARAMTYPE_GEOMETRY_ALPHATESTENABLE:
				{
					BOOL paramValue = FALSE;
					stream->Read(&paramValue, sizeof(BOOL));
					m_parameterBlock.SetParameter(paramType, paramValue);
				}

				break;

			case ruPARAMTYPE_GEOMETRY_ALPHATESTVALUE:
			case ruPARAMTYPE_MATERIAL_ALPHAMODE:
				{
					UINT32 paramValue = FALSE;
					stream->Read(&paramValue, sizeof(UINT32));
					m_parameterBlock.SetParameter(paramType, paramValue);
				}

				break;

			default:
				{
					// Skip parameter
					stream->Seek(paramLength, ruSSM_Current);
				}

				break;
		}
	}

	// Determine shader to use if this is the proper material type
	if(m_isDeprecatedMaterial == FALSE)
	{
		SelectShader();
	}

	return TRUE;
}

BOOL CRuMaterial::SerializeTo(IRuStream *stream)
{
	// Write class GUID
	stream->Write(GetClassGUID().Data(), GetClassGUID().Size());

	if(m_isDeprecatedMaterial)
	{
		// Write shader name length & name (including NULL)
		INT32 shaderNameLength = 1;
		const char *shaderName = "";
		if(m_shaderName)
		{
			shaderNameLength = strlen(m_shaderName) + 1;
			shaderName = m_shaderName;
		}

		stream->Write(&shaderNameLength, sizeof(INT32));
		stream->Write(shaderName, shaderNameLength * sizeof(BYTE));
	}
	else
	{
		// Write signature
		INT32 signature = 0xFFFFFFFF;
		stream->Write(&signature, sizeof(INT32));

		// Write version
		INT32 version = 2;
		stream->Write(&version, sizeof(INT32));

		// Write material type
		stream->Write(&m_materialType, sizeof(INT32));

		// Write lighting type
		stream->Write(&m_lightingType, sizeof(INT32));
	}

	// Write number of texture channels
	INT32 numTexChannels = m_textures.Count();
	stream->Write(&numTexChannels, sizeof(INT32));

	// Write each texture channel
	for(INT32 i = 0; i < numTexChannels; ++i)
	{
		// Write texture channel ID
		stream->Write(&m_textures[i].m_textureChannel, sizeof(RuTextureChannel));

		INT32 textureNameLength = 1;
		const char *textureName = "";

		if(m_textures[i].m_texture && m_textures[i].m_texture->GetTextureName())
		{
			textureNameLength = strlen(m_textures[i].m_texture->GetTextureName()) + 1;
			textureName = m_textures[i].m_texture->GetTextureName();
		}

		// Write texture name length (including NULL)
		stream->Write(&textureNameLength, sizeof(INT32));

		// Write texture name
		stream->Write(textureName, textureNameLength * sizeof(BYTE));
	}

	// Write number of parameters
	INT32 numParameters = m_parameterBlock.GetNumParameters();
	stream->Write(&numParameters, sizeof(INT32));

	// Write parameters
	for(INT32 i = 0; i < numParameters; ++i)
	{
		// Get parameter tuple
		RuParameterType paramType;
		DWORD paramValue;
		m_parameterBlock.GetParameter(i, paramType, paramValue);

		switch(paramType)
		{
			case ruPARAMTYPE_LIGHT_SELF:
				{
					// Write parameter type
					stream->Write(&paramType, sizeof(RuParameterType));

					// Write parameter length
					INT32 paramLength = sizeof(REAL);
					stream->Write(&paramLength, sizeof(INT32));

					REAL realValue = RuCastDWORDToFloat(paramValue);
					stream->Write(&realValue, sizeof(REAL));
				}

				break;

			case ruPARAMTYPE_GEOMETRY_2SIDED:
			case ruPARAMTYPE_GEOMETRY_FACETED:
			case ruPARAMTYPE_GEOMETRY_WIRE:
			case ruPARAMTYPE_GEOMETRY_ALPHATESTENABLE:
				{
					// Write parameter type
					stream->Write(&paramType, sizeof(RuParameterType));

					// Write parameter length
					INT32 paramLength = sizeof(REAL);
					stream->Write(&paramLength, sizeof(INT32));

					BOOL realValue = paramValue;
					stream->Write(&realValue, sizeof(BOOL));
				}

				break;

			case ruPARAMTYPE_GEOMETRY_ALPHATESTVALUE:
			case ruPARAMTYPE_MATERIAL_ALPHAMODE:
				{
					// Write parameter type
					stream->Write(&paramType, sizeof(RuParameterType));

					// Write parameter length
					INT32 paramLength = sizeof(REAL);
					stream->Write(&paramLength, sizeof(INT32));

					UINT32 realValue = paramValue;
					stream->Write(&realValue, sizeof(UINT32));
				}

				break;
		}
	}

	return TRUE;
}

DWORD CRuMaterial::GetDiffuse() const
{
	return 0xFFFFFFFF;
}

DWORD CRuMaterial::GetSpecular() const
{
	return 0xFFFFFFFF;
}

INT32 CRuMaterial::GetNumTextureChannels() const
{
	return m_textures.Count();
}

RuTextureChannel CRuMaterial::GetTextureChannel(INT32 index) const
{
	if(index >= 0 && index < (INT32) m_textures.Count())
		return m_textures[index].m_textureChannel;

	return ruTEXCHANNEL_INVALID;
}

BOOL CRuMaterial::SetTextureChannel(INT32 index, RuTextureChannel texChannel)
{
	if(index >= 0 && index < (INT32) m_textures.Count())
	{
		m_textures[index].m_textureChannel = texChannel;
		return TRUE;
	}

	return FALSE;
}

INT32 CRuMaterial::AddTextureChannel(RuTextureChannel texChannel)
{
	// Add new texture channel
	RuMatTexture matTexture;
	matTexture.m_textureChannel = texChannel;
	matTexture.m_texture = NULL;
	m_textures.Add(matTexture);

	return m_textures.Count() - 1;
}

BOOL CRuMaterial::DeleteTextureChannelAtIndex(INT32 index)
{
	if(index >= 0 && index < (INT32) m_textures.Count())
	{
		// Release the texture and remove the texture channel
		ruSAFE_RELEASE(m_textures[index].m_texture)
		m_textures.RemoveAt(index);

		return TRUE;
	}

	return FALSE;
}

IRuBaseTexture *CRuMaterial::GetTexture(RuTextureChannel texChannel) const
{
	for(INT32 i = 0; i < m_textures.Count(); i++)
	{
		if(m_textures[i].m_textureChannel == texChannel)
			return m_textures[i].m_texture;
	}

	return NULL;
}

void CRuMaterial::SetTexture(RuTextureChannel texChannel, IRuBaseTexture *texture)
{
	for(INT32 i = 0; i < m_textures.Count(); i++)
	{
		if(m_textures[i].m_textureChannel == texChannel)
		{
			if(texture == NULL)
			{
				// Release old texture
				ruSAFE_RELEASE(m_textures[i].m_texture);

				// Delete texture channel entry
				m_textures.RemoveAt(i);

				return;
			}
			else
			{
				// Increment texture reference count
				texture->AddRef();

				// Release old texture
				ruSAFE_RELEASE(m_textures[i].m_texture);

				// Set new texture into texture channel
				m_textures[i].m_texture = texture;

				return;
			}
		}
	}

	// Texture channel not found, so add the new channel if the specified texture is non-null
	if(texture == NULL)
		return;

	// Increment texture reference count
	texture->AddRef();

	// Add new texture channel with the specified texture
	RuMatTexture matTexture;
	matTexture.m_textureChannel = texChannel;
	matTexture.m_texture = texture;
	m_textures.Add(matTexture);
}

const char *CRuMaterial::GetShaderName() const
{
	return m_shaderName;
}

void CRuMaterial::SetShaderName(const char *shaderName)
{
	if(m_shaderName)
		delete[] m_shaderName;

	m_shaderName = ruNEW char [strlen(shaderName) + 1];
	strcpy(m_shaderName, shaderName);
}

IRuShader *CRuMaterial::GetShader() const
{
	return m_shader;
}

void CRuMaterial::SetShader(IRuShader *shader)
{
	m_shader = shader;
	m_isDeprecatedMaterial = TRUE;
}

CRuParameterBlock *CRuMaterial::GetParameterBlock()
{
	return &m_parameterBlock;
}

const CRuParameterBlock *CRuMaterial::GetParameterBlock() const
{
	return &m_parameterBlock;
}

INT32 CRuMaterial::GetMaterialPriority() const
{
	return m_materialPriority;
}

void CRuMaterial::SetMaterialPriority(INT32 priority)
{
	m_materialPriority = priority;
}

void CRuMaterial::SelectShader()
{
	// Determine whether the material has a gloss map
	BOOL hasGlossMap = GetTexture(ruTEXCHANNEL_GLOSSMAP) ? TRUE : FALSE;

	switch(m_materialType)
	{
		case ruMATERIAL_TYPE_STANDARD:
			{
				switch(m_lightingType)
				{
					case ruMATERIAL_LIGHTINGTYPE_STANDARD:
						{
							if(hasGlossMap)
							{
								m_shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_SH9_GLOSS");
							}
							else
							{
								m_shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_SH9");
							}
						}

						break;

					case ruMATERIAL_LIGHTINGTYPE_PRELIT:
						{
							m_shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_PRELIT");
						}

						break;

					case ruMATERIAL_LIGHTINGTYPE_SELFILLUMINATE:
						{
							// TODO: NOT DONE
							m_shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_FX");
						}

						break;

					default:
						{
							m_shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_FLAT");
						}

						break;
				}
			}

			break;

		case ruMATERIAL_TYPE_TRANSLUCENT:
			{
				switch(m_lightingType)
				{
					case ruMATERIAL_LIGHTINGTYPE_STANDARD:
						{
							m_shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_SH9_TRANSLUCENT");
						}

						break;

					case ruMATERIAL_LIGHTINGTYPE_PRELIT:
						{
							m_shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_PRELIT_TRANSLUCENT");
						}

						break;

					case ruMATERIAL_LIGHTINGTYPE_SELFILLUMINATE:
						{
							// TODO: NOT DONE
							m_shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_FX");
						}

						break;

					default:
						{
							m_shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_FLAT");
						}

						break;
				}
			}

			break;

		case ruMATERIAL_TYPE_FX:
			{
				// TODO: NOT DONE
				m_shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_FX");
			}

			break;

		default:
			{
				m_shader = g_ruResourceManager->GetShader("ruSHADER_STATIC_FLAT");
			}

			break;
	}
}

// ************************************************************************************************************************************************************

#pragma managed
