// Copyright (C) 2017 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 <Nazara/Graphics/SkeletalModel.hpp>
#include <Nazara/Graphics/AbstractRenderQueue.hpp>
#include <Nazara/Graphics/Config.hpp>
#include <Nazara/Graphics/SkinningManager.hpp>
#include <Nazara/Utility/MeshData.hpp>
#include <Nazara/Utility/Sequence.hpp>
#include <Nazara/Utility/SkeletalMesh.hpp>
#include <memory>
#include <Nazara/Graphics/Debug.hpp>

namespace Nz
{
	/*!
	* \ingroup graphics
	* \class Nz::SkeletalModel
	* \brief Graphics class that represents a model with a skeleton
	*/

	/*!
	* \brief Checks whether the parameters for the skeletal mesh are correct
	* \return true If parameters are valid
	*/

	bool SkeletalModelParameters::IsValid() const
	{
		if (!ModelParameters::IsValid())
			return false;

		if (loadAnimation && !animation.IsValid())
			return false;

		return true;
	}

	/*!
	* \brief Constructs a SkeletalModel object by default
	*/

	SkeletalModel::SkeletalModel() :
	m_currentSequence(nullptr),
	m_animationEnabled(true)
	{
	}

	/*!
	* \brief Adds the skeletal mesh to the rendering queue
	*
	* \param renderQueue Queue to be added
	* \param instanceData Data for the instance
	*/

	void SkeletalModel::AddToRenderQueue(AbstractRenderQueue* renderQueue, const InstanceData& instanceData, const Recti& scissorRect) const
	{
		if (!m_mesh)
			return;

		unsigned int submeshCount = m_mesh->GetSubMeshCount();
		for (unsigned int i = 0; i < submeshCount; ++i)
		{
			const SkeletalMesh* mesh = static_cast<const SkeletalMesh*>(m_mesh->GetSubMesh(i));
			const Material* material = GetMaterial(mesh->GetMaterialIndex());

			MeshData meshData;
			meshData.indexBuffer = mesh->GetIndexBuffer();
			meshData.primitiveMode = mesh->GetPrimitiveMode();
			meshData.vertexBuffer = SkinningManager::GetBuffer(mesh, &m_skeleton);

			renderQueue->AddMesh(instanceData.renderOrder, material, meshData, m_skeleton.GetAABB(), instanceData.transformMatrix, scissorRect);
		}
	}

	/*!
	* \brief Updates the animation of the mesh
	*
	* \param elapsedTime Delta time between two frames
	*
	* \remark Produces a NazaraError with NAZARA_GRAPHICS_SAFE defined if there is no animation
	*/

	void SkeletalModel::AdvanceAnimation(float elapsedTime)
	{
		#if NAZARA_GRAPHICS_SAFE
		if (!m_animation)
		{
			NazaraError("Model has no animation");
			return;
		}
		#endif

		m_interpolation += m_currentSequence->frameRate * elapsedTime;
		while (m_interpolation > 1.f)
		{
			m_interpolation -= 1.f;

			unsigned lastFrame = m_currentSequence->firstFrame + m_currentSequence->frameCount - 1;
			if (m_nextFrame + 1 > lastFrame)
			{
				if (m_animation->IsLoopPointInterpolationEnabled())
				{
					m_currentFrame = m_nextFrame;
					m_nextFrame = m_currentSequence->firstFrame;
				}
				else
				{
					m_currentFrame = m_currentSequence->firstFrame;
					m_nextFrame = m_currentFrame + 1;
				}
			}
			else
			{
				m_currentFrame = m_nextFrame;
				m_nextFrame++;
			}
		}

		m_animation->AnimateSkeleton(&m_skeleton, m_currentFrame, m_nextFrame, m_interpolation);

		InvalidateBoundingVolume();
	}

	/*!
	* \brief Clones this skeletal model
	* \return Pointer to newly allocated SkeletalModel
	*/

	SkeletalModel* SkeletalModel::Clone() const
	{
		return new SkeletalModel(*this);
	}

	/*!
	* \brief Creates a default skeletal model
	* \return Pointer to newly allocated SkeletalModel
	*/

	SkeletalModel* SkeletalModel::Create() const
	{
		return new SkeletalModel;
	}

	/*!
	* \brief Enables the animation of the model
	*
	* \param animation Should the model be animated
	*/

	void SkeletalModel::EnableAnimation(bool animation)
	{
		m_animationEnabled = animation;
	}

	/*!
	* \brief Gets the animation of the model
	* \return Pointer to the animation
	*/

	Animation* SkeletalModel::GetAnimation() const
	{
		return m_animation;
	}

	/*!
	* \brief Gets the skeleton of the model
	* \return Pointer to the skeleton
	*/

	Skeleton* SkeletalModel::GetSkeleton()
	{
		InvalidateBoundingVolume();

		return &m_skeleton;
	}

	/*!
	* \brief Gets the skeleton of the model
	* \return Constant pointer to the skeleton
	*/

	const Skeleton* SkeletalModel::GetSkeleton() const
	{
		return &m_skeleton;
	}

	/*!
	* \brief Checks whether the skeleton has an animation
	* \return true If it is the case
	*
	* \see IsAnimated, IsAnimationEnabled
	*/

	bool SkeletalModel::HasAnimation() const
	{
		return m_animation != nullptr;
	}

	/*!
	* \brief Checks whether the skeleton is animated
	* \return true
	*
	* \see HasAnimation, IsAnimationEnabled
	*/

	bool SkeletalModel::IsAnimated() const
	{
		return true;
	}

	/*!
	* \brief Checks whether the skeleton is currently animated
	* \return true If it is the case
	*
	* \see HasAnimation, IsAnimated
	*/

	bool SkeletalModel::IsAnimationEnabled() const
	{
		return m_animationEnabled;
	}

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

	bool SkeletalModel::LoadFromFile(const String& filePath, const SkeletalModelParameters& params)
	{
		return SkeletalModelLoader::LoadFromFile(this, filePath, params);
	}

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

	bool SkeletalModel::LoadFromMemory(const void* data, std::size_t size, const SkeletalModelParameters& params)
	{
		return SkeletalModelLoader::LoadFromMemory(this, data, size, params);
	}

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

	bool SkeletalModel::LoadFromStream(Stream& stream, const SkeletalModelParameters& params)
	{
		return SkeletalModelLoader::LoadFromStream(this, stream, params);
	}

	/*!
	* \brief Sets the animation for the model
	* \return true If successful
	*
	* \param animation Animation for the model
	*
	* \remark Produces a NazaraError with NAZARA_GRAPHICS_SAFE if there is no mesh
	* \remark Produces a NazaraError with NAZARA_GRAPHICS_SAFE if animation is invalid
	*/

	bool SkeletalModel::SetAnimation(Animation* animation)
	{
		#if NAZARA_GRAPHICS_SAFE
		if (!m_mesh)
		{
			NazaraError("Model has no mesh");
			return false;
		}

		if (animation)
		{
			if (!animation->IsValid())
			{
				NazaraError("Invalid animation");
				return false;
			}

			if (animation->GetType() != m_mesh->GetAnimationType())
			{
				NazaraError("Animation type must match mesh animation type");
				return false;
			}

			if (animation->GetJointCount() != m_mesh->GetJointCount())
			{
				NazaraError("Animation joint count must match mesh joint count");
				return false;
			}
		}
		#endif

		m_animation = animation;
		if (m_animation)
		{
			m_currentFrame = 0;
			m_interpolation = 0.f;

			SetSequence(0);
		}

		return true;
	}

	/*!
	* \brief Sets the mesh for the model
	*
	* \param mesh Mesh for the model
	*
	* \remark Produces a NazaraError with NAZARA_GRAPHICS_SAFE if there is no mesh or if invalid
	*/

	void SkeletalModel::SetMesh(Mesh* mesh)
	{
		#if NAZARA_GRAPHICS_SAFE
		if (mesh && mesh->GetAnimationType() != AnimationType_Skeletal)
		{
			NazaraError("Mesh animation type must be skeletal");
			return;
		}
		#endif

		Model::SetMesh(mesh);

		if (m_mesh)
		{
			if (m_animation && m_animation->GetJointCount() != m_mesh->GetJointCount())
			{
				NazaraWarning("Animation joint count is not matching new mesh joint count, disabling animation...");
				SetAnimation(nullptr);
			}

			m_skeleton = *m_mesh->GetSkeleton(); // Copy of skeleton template
		}
	}

	/*!
	* \brief Sets the sequence for the model
	* \return true If successful
	*
	* \param sequenceName Name for the sequence animation
	*
	* \remark Produces a NazaraError with NAZARA_GRAPHICS_SAFE if there is no animation
	* \remark Produces a NazaraError with NAZARA_GRAPHICS_SAFE if sequence name does not exist for the current animation
	*/

	bool SkeletalModel::SetSequence(const String& sequenceName)
	{
		///TODO: Make this error "safe" with the new system of error handling (No-log)
		#if NAZARA_GRAPHICS_SAFE
		if (!m_animation)
		{
			NazaraError("Model has no animation");
			return false;
		}
		#endif

		const Sequence* currentSequence = m_animation->GetSequence(sequenceName);
		#if NAZARA_GRAPHICS_SAFE
		if (!currentSequence)
		{
			NazaraError("Sequence not found");
			return false;
		}
		#endif

		m_currentSequence = currentSequence;
		m_nextFrame = m_currentSequence->firstFrame;

		return true;
	}

	/*!
	* \brief Sets the sequence for the model
	*
	* \param sequenceIndex Index for the sequence animation
	*
	* \remark Produces a NazaraError with NAZARA_GRAPHICS_SAFE if there is no animation
	* \remark Produces a NazaraError with NAZARA_GRAPHICS_SAFE if sequence name does not exist for the current animation
	*/

	void SkeletalModel::SetSequence(unsigned int sequenceIndex)
	{
		#if NAZARA_GRAPHICS_SAFE
		if (!m_animation)
		{
			NazaraError("Model has no animation");
			return;
		}
		#endif

		const Sequence* currentSequence = m_animation->GetSequence(sequenceIndex);
		#if NAZARA_GRAPHICS_SAFE
		if (!currentSequence)
		{
			NazaraError("Sequence not found");
			return;
		}
		#endif

		m_currentSequence = currentSequence;
		m_nextFrame = m_currentSequence->firstFrame;
	}

	/*
	* \brief Makes the bounding volume of this text
	*/

	void SkeletalModel::MakeBoundingVolume() const
	{
		m_boundingVolume.Set(m_skeleton.GetAABB());
	}

	/*!
	* \brief Updates the model
	*/

	void SkeletalModel::Update()
	{
		///TODO
		/*if (m_animationEnabled && m_animation)
			AdvanceAnimation(m_scene->GetUpdateTime());*/
	}

	SkeletalModelLoader::LoaderList SkeletalModel::s_loaders;
}
