// Copyright (C) 2015 Jérôme Leclercq
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp

#include <memory>
#include <Nazara/Graphics/Debug.hpp>

namespace Nz
{
	/*!
	* \brief Constructs a Material object by default
	*/

	inline Material::Material()
	{
		Reset();
	}

	/*!
	* \brief Constructs a Material object by assignation
	*
	* \param material Material to copy into this
	*/

	inline Material::Material(const Material& material) :
	RefCounted(),
	Resource(material)
	{
		Copy(material);
	}

	/*!
	* \brief Destructs the object and calls OnMaterialRelease
	*
	* \see OnMaterialRelease
	*/

	inline Material::~Material()
	{
		OnMaterialRelease(this);
	}

	/*!
	* \brief Enables a renderer parameter
	*
	* \param renderParameter Parameter for the rendering
	* \param enable Should the parameter be enabled
	*
	* \remark Produces a NazaraAssert if enumeration is invalid
	*/

	inline void Material::Enable(RendererParameter renderParameter, bool enable)
	{
		NazaraAssert(renderParameter <= RendererParameter_Max, "Renderer parameter out of enum");

		m_states.parameters[renderParameter] = enable;
	}

	/*!
	* \brief Enables the alpha test
	*
	* \param alphaTest Should the parameter be enabled
	*
	* \remark Invalidates the shaders
	*/

	inline void Material::EnableAlphaTest(bool alphaTest)
	{
		m_alphaTestEnabled = alphaTest;

		InvalidateShaders();
	}

	/*!
	* \brief Enables the depth sorting
	*
	* \param depthSorting Should the parameter be enabled
	*/

	inline void Material::EnableDepthSorting(bool depthSorting)
	{
		// Has no influence on shaders
		m_depthSortingEnabled = depthSorting;
	}

	/*!
	* \brief Enables the lighting
	*
	* \param lighting Should the parameter be enabled
	*
	* \remark Invalidates the shaders
	*/

	inline void Material::EnableLighting(bool lighting)
	{
		m_lightingEnabled = lighting;

		InvalidateShaders();
	}

	/*!
	* \brief Enables the shadow casting
	*
	* \param castShadows Should shadow casting be enabled
	*/

	inline void Material::EnableShadowCasting(bool castShadows)
	{
		// Has no influence on shaders
		m_shadowCastingEnabled = castShadows;
	}

	/*!
	* \brief Enables the shadow on receiving object
	*
	* \param receiveShadow Should receiving object have shadows  enabled
	*
	* \remark Invalidates the shaders
	*/

	inline void Material::EnableShadowReceive(bool receiveShadows)
	{
		m_shadowReceiveEnabled = receiveShadows;

		InvalidateShaders();
	}

	/*!
	* \brief Enables the transformation
	*
	* \param transform Should the parameter be enabled
	*
	* \remark Invalidates the shaders
	*/

	inline void Material::EnableTransform(bool transform)
	{
		m_transformEnabled = transform;

		InvalidateShaders();
	}

	/*!
	* \brief Gets the alpha map
	* \return Constant reference to the current texture
	*/

	inline const TextureRef& Material::GetAlphaMap() const
	{
		return m_alphaMap;
	}

	/*!
	* \brief Gets the alpha threshold
	* \return The threshold value for the alpha
	*/

	inline float Material::GetAlphaThreshold() const
	{
		return m_alphaThreshold;
	}

	/*!
	* \brief Gets the ambient color
	* \return Ambient color
	*/

	inline Color Material::GetAmbientColor() const
	{
		return m_ambientColor;
	}

	/*!
	* \brief Gets the function to compare depth
	* \return Function comparing the depth of two materials
	*/

	inline RendererComparison Material::GetDepthFunc() const
	{
		return m_states.depthFunc;
	}

	/*!
	* \brief Gets the depth material
	* \return Constant reference to the depth material
	*/

	inline const MaterialRef& Material::GetDepthMaterial() const
	{
		return m_depthMaterial;
	}

	/*!
	* \brief Gets the diffuse color
	* \return Diffuse color
	*/

	inline Color Material::GetDiffuseColor() const
	{
		return m_diffuseColor;
	}

	/*!
	* \brief Gets the diffuse sampler
	* \return Reference to the current texture sampler for the diffuse
	*/

	inline TextureSampler& Material::GetDiffuseSampler()
	{
		return m_diffuseSampler;
	}

	/*!
	* \brief Gets the diffuse sampler
	* \return Constant reference to the current texture sampler for the diffuse
	*/

	inline const TextureSampler& Material::GetDiffuseSampler() const
	{
		return m_diffuseSampler;
	}

	/*!
	* \brief Gets the diffuse map
	* \return Constant reference to the texture
	*/

	const TextureRef& Material::GetDiffuseMap() const
	{
		return m_diffuseMap;
	}

	/*!
	* \brief Gets the dst in blend
	* \return Function for dst blending
	*/

	inline BlendFunc Material::GetDstBlend() const
	{
		return m_states.dstBlend;
	}

	/*!
	* \brief Gets the emissive map
	* \return Constant reference to the texture
	*/

	inline const TextureRef& Material::GetEmissiveMap() const
	{
		return m_emissiveMap;
	}

	/*!
	* \brief Gets the face culling
	* \return Current face culling side
	*/

	inline FaceSide Material::GetFaceCulling() const
	{
		return m_states.faceCulling;
	}

	/*!
	* \brief Gets the face filling
	* \return Current face filling
	*/

	inline FaceFilling Material::GetFaceFilling() const
	{
		return m_states.faceFilling;
	}

	/*!
	* \brief Gets the height map
	* \return Constant reference to the texture
	*/

	inline const TextureRef& Material::GetHeightMap() const
	{
		return m_heightMap;
	}

	/*!
	* \brief Gets the normal map
	* \return Constant reference to the texture
	*/

	inline const TextureRef& Material::GetNormalMap() const
	{
		return m_normalMap;
	}

	/*!
	* \brief Gets the render states
	* \return Constant reference to the render states
	*/

	inline const RenderStates& Material::GetRenderStates() const
	{
		return m_states;
	}

	/*!
	* \brief Gets the shader of this material
	* \return Constant pointer to the ubershader used
	*/

	inline const UberShader* Material::GetShader() const
	{
		return m_uberShader;
	}

	/*!
	* \brief Gets the shader instance based on the flag
	* \return Constant pointer to the ubershader instance
	*
	* \param flags Flag of the shader
	*/

	inline const UberShaderInstance* Material::GetShaderInstance(UInt32 flags) const
	{
		const ShaderInstance& instance = m_shaders[flags];
		if (!instance.uberInstance)
			GenerateShader(flags);

		return instance.uberInstance;
	}

	/*!
	* \brief Gets the shininess
	* \return Current shininess
	*/

	inline float Material::GetShininess() const
	{
		return m_shininess;
	}

	/*!
	* \brief Gets the specular color
	* \return Specular color
	*/

	inline Color Material::GetSpecularColor() const
	{
		return m_specularColor;
	}

	/*!
	* \brief Gets the specular map
	* \return Constant reference to the texture
	*/

	inline const TextureRef& Material::GetSpecularMap() const
	{
		return m_specularMap;
	}

	/*!
	* \brief Gets the specular sampler
	* \return Reference to the current texture sampler for the specular
	*/

	inline TextureSampler& Material::GetSpecularSampler()
	{
		return m_specularSampler;
	}

	/*!
	* \brief Gets the specular sampler
	* \return Constant reference to the current texture sampler for the specular
	*/

	inline const TextureSampler& Material::GetSpecularSampler() const
	{
		return m_specularSampler;
	}

	/*!
	* \brief Gets the src in blend
	* \return Function for src blending
	*/

	inline BlendFunc Material::GetSrcBlend() const
	{
		return m_states.srcBlend;
	}

	/*!
	* \brief Checks whether this material has an alpha map
	* \return true If it is the case
	*/

	inline bool Material::HasAlphaMap() const
	{
		return m_alphaMap.IsValid();
	}

	/*!
	* \brief Checks whether this material has a depth material
	* \return true If it is the case
	*/

	inline bool Material::HasDepthMaterial() const
	{
		return m_depthMaterial.IsValid();
	}

	/*!
	* \brief Checks whether this material has a diffuse map
	* \return true If it is the case
	*/

	inline bool Material::HasDiffuseMap() const
	{
		return m_diffuseMap.IsValid();
	}

	/*!
	* \brief Checks whether this material has a emissive map
	* \return true If it is the case
	*/

	inline bool Material::HasEmissiveMap() const
	{
		return m_emissiveMap.IsValid();
	}

	/*!
	* \brief Checks whether this material has a height map
	* \return true If it is the case
	*/

	inline bool Material::HasHeightMap() const
	{
		return m_heightMap.IsValid();
	}

	/*!
	* \brief Checks whether this material has a normal map
	* \return true If it is the case
	*/

	inline bool Material::HasNormalMap() const
	{
		return m_normalMap.IsValid();
	}

	/*!
	* \brief Checks whether this material has a specular map
	* \return true If it is the case
	*/

	inline bool Material::HasSpecularMap() const
	{
		return m_specularMap.IsValid();
	}

	/*!
	* \brief Checks whether this material has alpha test enabled
	* \return true If it is the case
	*/

	inline bool Material::IsAlphaTestEnabled() const
	{
		return m_alphaTestEnabled;
	}

	/*!
	* \brief Checks whether this material has depth sorting enabled
	* \return true If it is the case
	*/

	inline bool Material::IsDepthSortingEnabled() const
	{
		return m_depthSortingEnabled;
	}

	/*!
	* \brief Checks whether this material has the render parameter enabled
	* \return true If it is the case
	*
	* \param parameter Parameter for the rendering
	*
	* \remark Produces a NazaraAssert if enumeration is invalid
	*/

	inline bool Material::IsEnabled(RendererParameter parameter) const
	{
		NazaraAssert(parameter <= RendererParameter_Max, "Renderer parameter out of enum");

		return m_states.parameters[parameter];
	}

	/*!
	* \brief Checks whether this material has lightning enabled
	* \return true If it is the case
	*/

	inline bool Material::IsLightingEnabled() const
	{
		return m_lightingEnabled;
	}

	/*!
	* \brief Checks whether this material cast shadow
	* \return true If it is the case
	*/

	inline bool Material::IsShadowCastingEnabled() const
	{
		return m_shadowCastingEnabled;
	}

	/*!
	* \brief Checks whether this material receive shadow
	* \return true If it is the case
	*/

	inline bool Material::IsShadowReceiveEnabled() const
	{
		return m_shadowReceiveEnabled;
	}

	/*!
	* \brief Checks whether this material has transformation enabled
	* \return true If it is the case
	*/

	inline bool Material::IsTransformEnabled() const
	{
		return m_transformEnabled;
	}

	/*!
	* \brief Loads the material from file
	* \return true if loading is successful
	*
	* \param filePath Path to the file
	* \param params Parameters for the material
	*/

	inline bool Material::LoadFromFile(const String& filePath, const MaterialParams& params)
	{
		return MaterialLoader::LoadFromFile(this, filePath, params);
	}

	/*!
	* \brief Loads the material from memory
	* \return true if loading is successful
	*
	* \param data Raw memory
	* \param size Size of the memory
	* \param params Parameters for the material
	*/

	inline bool Material::LoadFromMemory(const void* data, std::size_t size, const MaterialParams& params)
	{
		return MaterialLoader::LoadFromMemory(this, data, size, params);
	}

	/*!
	* \brief Loads the material from stream
	* \return true if loading is successful
	*
	* \param stream Stream to the material
	* \param params Parameters for the material
	*/

	inline bool Material::LoadFromStream(Stream& stream, const MaterialParams& params)
	{
		return MaterialLoader::LoadFromStream(this, stream, params);
	}

	/*!
	* \brief Sets the alpha map by name
	* \return true If successful
	*
	* \param textureName Named texture
	*/

	inline bool Material::SetAlphaMap(const String& textureName)
	{
		TextureRef texture = TextureLibrary::Query(textureName);
		if (!texture)
		{
			texture = TextureManager::Get(textureName);
			if (!texture)
			{
				NazaraError("Failed to get alpha map \"" + textureName + "\"");
				return false;
			}
		}

		SetAlphaMap(std::move(texture));
		return true;
	}

	/*!
	* \brief Sets the alpha map with a reference to a texture
	* \return true If successful
	*
	* \param alphaMap Texture
	*
	* \remark Invalidates the shaders
	*/

	inline void Material::SetAlphaMap(TextureRef alphaMap)
	{
		m_alphaMap = std::move(alphaMap);

		InvalidateShaders();
	}

	/*!
	* \brief Sets the alpha threshold
	*
	* \param alphaThreshold Threshold for the alpha
	*/

	inline void Material::SetAlphaThreshold(float alphaThreshold)
	{
		m_alphaThreshold = alphaThreshold;
	}

	/*!
	* \brief Sets the color for ambient
	*
	* \param ambient Color for ambient
	*/

	inline void Material::SetAmbientColor(const Color& ambient)
	{
		m_ambientColor = ambient;
	}

	/*!
	* \brief Sets the depth functor
	*
	* \param depthFunc
	*/

	inline void Material::SetDepthFunc(RendererComparison depthFunc)
	{
		m_states.depthFunc = depthFunc;
	}

	/*!
	* \brief Sets the depth material
	* \return true If successful
	*
	* \param depthMaterial Material for depth
	*/

	inline void Material::SetDepthMaterial(MaterialRef depthMaterial)
	{
		m_depthMaterial = std::move(depthMaterial);
	}

	/*!
	* \brief Sets the color for diffuse
	*
	* \param diffuse Color for diffuse
	*/

	inline void Material::SetDiffuseColor(const Color& diffuse)
	{
		m_diffuseColor = diffuse;
	}

	/*!
	* \brief Sets the diffuse map by name
	* \return true If successful
	*
	* \param textureName Named texture
	*/

	inline bool Material::SetDiffuseMap(const String& textureName)
	{
		TextureRef texture = TextureLibrary::Query(textureName);
		if (!texture)
		{
			texture = TextureManager::Get(textureName);
			if (!texture)
			{
				NazaraError("Failed to get diffuse map \"" + textureName + "\"");
				return false;
			}
		}

		SetDiffuseMap(std::move(texture));
		return true;
	}

	/*!
	* \brief Sets the diffuse map with a reference to a texture
	* \return true If successful
	*
	* \param diffuseMap Texture
	*
	* \remark Invalidates the shaders
	*/

	inline void Material::SetDiffuseMap(TextureRef diffuseMap)
	{
		m_diffuseMap = std::move(diffuseMap);

		InvalidateShaders();
	}

	/*!
	* \brief Sets the diffuse sampler
	*
	* \param sampler Diffuse sample
	*/

	inline void Material::SetDiffuseSampler(const TextureSampler& sampler)
	{
		m_diffuseSampler = sampler;
	}

	/*!
	* \brief Sets the dst in blend
	*
	* \param func Function for dst blending
	*/

	inline void Material::SetDstBlend(BlendFunc func)
	{
		m_states.dstBlend = func;
	}

	/*!
	* \brief Sets the emissive map by name
	* \return true If successful
	*
	* \param textureName Named texture
	*/

	inline bool Material::SetEmissiveMap(const String& textureName)
	{
		TextureRef texture = TextureLibrary::Query(textureName);
		if (!texture)
		{
			texture = TextureManager::Get(textureName);
			if (!texture)
			{
				NazaraError("Failed to get emissive map \"" + textureName + "\"");
				return false;
			}
		}

		SetEmissiveMap(std::move(texture));
		return true;
	}

	/*!
	* \brief Sets the emissive map with a reference to a texture
	* \return true If successful
	*
	* \param emissiveMap Texture
	*
	* \remark Invalidates the shaders
	*/

	inline void Material::SetEmissiveMap(TextureRef emissiveMap)
	{
		m_emissiveMap = std::move(emissiveMap);

		InvalidateShaders();
	}

	/*!
	* \brief Sets the face culling
	*
	* \param faceSide Face to cull
	*/

	inline void Material::SetFaceCulling(FaceSide faceSide)
	{
		m_states.faceCulling = faceSide;
	}

	/*!
	* \brief Sets the face filling
	*
	* \param filling Face to fill
	*/

	inline void Material::SetFaceFilling(FaceFilling filling)
	{
		m_states.faceFilling = filling;
	}

	/*!
	* \brief Sets the height map by name
	* \return true If successful
	*
	* \param textureName Named texture
	*/

	inline bool Material::SetHeightMap(const String& textureName)
	{
		TextureRef texture = TextureLibrary::Query(textureName);
		if (!texture)
		{
			texture = TextureManager::Get(textureName);
			if (!texture)
			{
				NazaraError("Failed to get height map \"" + textureName + "\"");
				return false;
			}
		}

		SetHeightMap(std::move(texture));
		return true;
	}

	/*!
	* \brief Sets the height map with a reference to a texture
	* \return true If successful
	*
	* \param heightMap Texture
	*
	* \remark Invalidates the shaders
	*/

	inline void Material::SetHeightMap(TextureRef heightMap)
	{
		m_heightMap = std::move(heightMap);

		InvalidateShaders();
	}

	/*!
	* \brief Sets the normal map by name
	* \return true If successful
	*
	* \param textureName Named texture
	*/

	inline bool Material::SetNormalMap(const String& textureName)
	{
		TextureRef texture = TextureLibrary::Query(textureName);
		if (!texture)
		{
			texture = TextureManager::Get(textureName);
			if (!texture)
			{
				NazaraError("Failed to get normal map \"" + textureName + "\"");
				return false;
			}
		}

		SetNormalMap(std::move(texture));
		return true;
	}

	/*!
	* \brief Sets the normal map with a reference to a texture
	* \return true If successful
	*
	* \param normalMap Texture
	*
	* \remark Invalidates the shaders
	*/

	inline void Material::SetNormalMap(TextureRef normalMap)
	{
		m_normalMap = std::move(normalMap);

		InvalidateShaders();
	}

	/*!
	* \brief Sets the render states
	*
	* \param states States for the rendering
	*/

	inline void Material::SetRenderStates(const RenderStates& states)
	{
		m_states = states;
	}

	/*!
	* \brief Sets the shader with a constant reference to a ubershader
	*
	* \param uberShader Uber shader to apply
	*
	* \remark Invalidates the shaders
	*/

	inline void Material::SetShader(UberShaderConstRef uberShader)
	{
		m_uberShader = std::move(uberShader);

		InvalidateShaders();
	}

	/*!
	* \brief Sets the shader by name
	* \return true If successful
	*
	* \param uberShaderName Named shader
	*/

	inline bool Material::SetShader(const String& uberShaderName)
	{
		UberShaderConstRef uberShader = UberShaderLibrary::Get(uberShaderName);
		if (!uberShader)
			return false;

		SetShader(std::move(uberShader));
		return true;
	}

	/*!
	* \brief Sets the shininess of the material
	*
	* \param shininess Value of the shininess
	*/

	inline void Material::SetShininess(float shininess)
	{
		m_shininess = shininess;
	}

	/*!
	* \brief Sets the color for specular
	*
	* \param specular Color
	*/

	inline void Material::SetSpecularColor(const Color& specular)
	{
		m_specularColor = specular;
	}

	/*!
	* \brief Sets the specular map by name
	* \return true If successful
	*
	* \param textureName Named texture
	*/

	inline bool Material::SetSpecularMap(const String& textureName)
	{
		TextureRef texture = TextureLibrary::Query(textureName);
		if (!texture)
		{
			texture = TextureManager::Get(textureName);
			if (!texture)
			{
				NazaraError("Failed to get specular map \"" + textureName + "\"");
				return false;
			}
		}

		SetSpecularMap(std::move(texture));
		return true;
	}

	/*!
	* \brief Sets the specular map with a reference to a texture
	* \return true If successful
	*
	* \param specularMap Texture
	*
	* \remark Invalidates the shaders
	*/

	inline void Material::SetSpecularMap(TextureRef specularMap)
	{
		m_specularMap = std::move(specularMap);

		InvalidateShaders();
	}

	/*!
	* \brief Sets the specular sampler
	*
	* \param sampler Specular sample
	*/

	inline void Material::SetSpecularSampler(const TextureSampler& sampler)
	{
		m_specularSampler = sampler;
	}

	/*!
	* \brief Sets the src in blend
	*
	* \param func Function for src blending
	*/

	inline void Material::SetSrcBlend(BlendFunc func)
	{
		m_states.srcBlend = func;
	}

	/*!
	* \brief Sets the current material with the content of the other one
	* \return A reference to this
	*
	* \param material The other Material
	*/

	inline Material& Material::operator=(const Material& material)
	{
		Resource::operator=(material);

		Copy(material);
		return *this;
	}

	/*!
	* \brief Gets the default material
	* \return Reference to the default material
	*/

	inline MaterialRef Material::GetDefault()
	{
		return s_defaultMaterial;
	}

	/*!
	* \brief Invalidates the shaders
	*/

	inline void Material::InvalidateShaders()
	{
		for (ShaderInstance& instance : m_shaders)
			instance.uberInstance = nullptr;
	}

	/*!
	* \brief Creates a new material from the arguments
	* \return A reference to the newly created material
	*
	* \param args Arguments for the material
	*/

	template<typename... Args>
	MaterialRef Material::New(Args&&... args)
	{
		std::unique_ptr<Material> object(new Material(std::forward<Args>(args)...));
		object->SetPersistent(false);

		return object.release();
	}
}

#include <Nazara/Graphics/DebugOff.hpp>
