/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
 *
 * Distributed under the terms of the ISC license; see accompanying file
 * "COPYING" for details.
 *
 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
 * See accompanying file "TRADEMARK" for details.
 *
 * To redistribute this file separately, substitute the full license texts
 * for the above references.
 */

#include "C4Include.h"
#include "lib/StdMeshUpdate.h"

#include "lib/StdMesh.h"
#include "lib/StdMeshMaterial.h"
#include "lib/StdMeshLoader.h"

StdMeshMaterialUpdate::StdMeshMaterialUpdate(StdMeshMatManager& manager):
	MaterialManager(manager)
{
}

void StdMeshMaterialUpdate::Update(StdMesh* mesh) const
{
	for(auto & SubMesh : mesh->SubMeshes)
	{
		auto mat_iter = Materials.find(SubMesh.Material);
		if(mat_iter != Materials.end())
		{
			const StdMeshMaterial* new_material = MaterialManager.GetMaterial(mat_iter->second.Name.getData());

			if(new_material)
			{
				SubMesh.Material = new_material;
			}
			else
			{
				// If no replacement material is available, then re-insert the previous
				// material into the material map. This is mainly just to keep things
				// going - next time the scenario will be started the mesh will fail
				// to load because the material cannot be found.
				MaterialManager.Materials[mat_iter->second.Name] = mat_iter->second; // TODO: could be moved
				SubMesh.Material = MaterialManager.GetMaterial(mat_iter->second.Name.getData());
			}
		}
	}
}

void StdMeshMaterialUpdate::Update(StdMeshInstance* instance) const
{
	for(unsigned int i = 0; i < instance->SubMeshInstances.size(); ++i)
	{
		StdSubMeshInstance* sub_instance = instance->SubMeshInstances[i];
		std::map<const StdMeshMaterial*, StdMeshMaterial>::const_iterator mat_iter = Materials.find(sub_instance->Material);
		if(mat_iter != Materials.end())
		{
			// Material needs to be updated
			const StdMeshMaterial* new_material = MaterialManager.GetMaterial(mat_iter->second.Name.getData());
			// If new material is not available, fall back to StdMesh (definition) material
			if(!new_material) new_material = instance->GetMesh().GetSubMesh(i).Material;
			sub_instance->SetMaterial(*new_material);
		}
	}
}

void StdMeshMaterialUpdate::Cancel() const
{
	// Reset all materials in manager
	for(const auto & Material : Materials)
		MaterialManager.Materials[Material.second.Name] = Material.second; // TODO: could be moved
}

void StdMeshMaterialUpdate::Add(const StdMeshMaterial* material)
{
	assert(Materials.find(material) == Materials.end());
	Materials[material] = *material; // TODO: could be moved
}

StdMeshUpdate::StdMeshUpdate(const StdMesh& old_mesh):
	OldMesh(&old_mesh), BoneNamesByIndex(OldMesh->GetSkeleton().GetNumBones())
{
	for (unsigned int i = 0; i < OldMesh->GetSkeleton().GetNumBones(); ++i)
		BoneNamesByIndex[i] = OldMesh->GetSkeleton().GetBone(i).Name;
}

void StdMeshUpdate::Update(StdMeshInstance* instance, const StdMesh& new_mesh) const
{
	assert(&instance->GetMesh() == OldMesh);

	// Update instance to represent new mesh
	instance->Mesh = &new_mesh;
	instance->BoneTransforms = std::vector<StdMeshMatrix>(new_mesh.GetSkeleton().GetNumBones(), StdMeshMatrix::Identity());
	instance->BoneTransformsDirty = true;

	for (auto & SubMeshInstance : instance->SubMeshInstances)
		delete SubMeshInstance;
	instance->SubMeshInstances.resize(new_mesh.GetNumSubMeshes());
	for (unsigned int i = 0; i < instance->SubMeshInstances.size(); ++i)
	{
		instance->SubMeshInstances[i] = new StdSubMeshInstance(*instance, new_mesh.GetSubMesh(i), instance->GetCompletion());

		// Submeshes are reset to their default material,
		// so the submesh order is unaltered
		instance->SubMeshInstancesOrdered = instance->SubMeshInstances;

		// TODO: We might try to restore the previous mesh material here, using global material manager, maybe iff the number of submesh instances is unchanged.
	}

	// Update child bone of attach parent. If the bone does not exist anymore
	// in the updated mesh, then detach the mesh from its parent
	if(instance->AttachParent)
	{
		if(!instance->AttachParent->SetChildBone(BoneNamesByIndex[instance->AttachParent->ChildBone]))
		{
			bool OwnChild = instance->AttachParent->OwnChild;
			instance->AttachParent->Parent->DetachMesh(instance->AttachParent->Number);
			
			// If the attachment owned the child instance then detach procedure
			// deleted the child instance. In that case we do not want to proceed
			// with the mesh update procedure.
			if(OwnChild) return;
		}
	}

	// Update parent bones of attach children. If a bone does not exist in the
	// updated mesh then detach the mesh from its parent.
	std::vector<unsigned int> Removal;
	for(StdMeshInstance::AttachedMeshIter iter = instance->AttachedMeshesBegin(); iter != instance->AttachedMeshesEnd(); ++iter)
	{
		if(!(*iter)->SetParentBone(BoneNamesByIndex[(*iter)->ParentBone]))
		{
			// Do not detach the mesh right here so we can finish iterating over
			// all attached meshes first.
			Removal.push_back((*iter)->Number);
		}
	}

	for(unsigned int i : Removal)
		instance->DetachMesh(i);

	// Update custom nodes in the animation tree. Leaf nodes which refer to an animation that
	// does not exist anymore are removed.
	for (unsigned int i = instance->AnimationStack.size(); i > 0; --i)
		if(!UpdateAnimationNode(instance, instance->AnimationStack[i-1]))
			instance->StopAnimation(instance->AnimationStack[i-1]);
}

bool StdMeshUpdate::UpdateAnimationNode(StdMeshInstance* instance, StdMeshInstance::AnimationNode* node) const
{
	const StdMesh& new_mesh = *instance->Mesh;
	switch (node->GetType())
	{
	case StdMeshInstance::AnimationNode::LeafNode:
		// Animation pointer is updated by StdMeshAnimationUpdate
		return true;
	case StdMeshInstance::AnimationNode::CustomNode:
		{
			// Update bone index by bone name
			StdCopyStrBuf bone_name = BoneNamesByIndex[node->Custom.BoneIndex];
			const StdMeshBone* bone = new_mesh.GetSkeleton().GetBoneByName(bone_name);
			if(!bone) return false;
			node->Custom.BoneIndex = bone->Index;
			return true;
		}
	case StdMeshInstance::AnimationNode::LinearInterpolationNode:
		{
			const bool left_result = UpdateAnimationNode(instance, node->GetLeftChild());
			const bool right_result = UpdateAnimationNode(instance, node->GetRightChild());

			// Remove this node completely
			if (!left_result && !right_result)
				return false;

			// Note that either of this also removes this node (and replaces by
			// the other child in the tree).
			if (!left_result)
				instance->StopAnimation(node->GetLeftChild());
			if (!right_result)
				instance->StopAnimation(node->GetRightChild());

			return true;
		}
	default:
		assert(false);
		return false;
	}
}

StdMeshAnimationUpdate::StdMeshAnimationUpdate(const StdMeshSkeletonLoader& skeleton_loader)
{
	// Store all animation names of all skeletons by pointer, we need those when
	// updating the animation state of mesh instances after the update.
	for(StdMeshSkeletonLoader::skeleton_iterator iter = skeleton_loader.skeletons_begin(); iter != skeleton_loader.skeletons_end(); ++iter)
	{
		const StdMeshSkeleton& skeleton = *iter->second;
		for (const auto & Animation : skeleton.Animations)
		{
			AnimationNames[&Animation.second] = Animation.first;
		}
	}
}

void StdMeshAnimationUpdate::Update(StdMeshInstance* instance) const
{
	// Update the animation tree. Leaf nodes which refer to an animation that
	// does not exist anymore are removed.
	for (unsigned int i = instance->AnimationStack.size(); i > 0; --i)
		if(!UpdateAnimationNode(instance, instance->AnimationStack[i-1]))
			instance->StopAnimation(instance->AnimationStack[i-1]);
}

bool StdMeshAnimationUpdate::UpdateAnimationNode(StdMeshInstance* instance, StdMeshInstance::AnimationNode* node) const
{
	const StdMesh& new_mesh = *instance->Mesh;
	switch (node->GetType())
	{
	case StdMeshInstance::AnimationNode::LeafNode:
		{
			// Find dead animation
			std::map<const StdMeshAnimation*, StdCopyStrBuf>::const_iterator iter = AnimationNames.find(node->Leaf.Animation);

			// If this assertion fires, it typically means that UpdateAnimations() was called twice for the same mesh instance
			assert(iter != AnimationNames.end());

			// Update to new animation
			node->Leaf.Animation = new_mesh.GetSkeleton().GetAnimationByName(iter->second);
			if(!node->Leaf.Animation) return false;

			// Clamp provider value
			StdMeshInstance::ValueProvider* provider = node->GetPositionProvider();
			C4Real min = Fix0;
			C4Real max = ftofix(node->GetAnimation()->Length);
			provider->Value = Clamp(provider->Value, min, max);
			return true;
		}
	case StdMeshInstance::AnimationNode::CustomNode:
		// Nothing to do here; bone index is updated in StdMeshUpdate
		return true;
	case StdMeshInstance::AnimationNode::LinearInterpolationNode:
		{
			const bool left_result = UpdateAnimationNode(instance, node->GetLeftChild());
			const bool right_result = UpdateAnimationNode(instance, node->GetRightChild());

			// Remove this node completely
			if (!left_result && !right_result)
				return false;

			// Note that either of this also removes this node (and replaces by
			// the other child in the tree).
			if (!left_result)
				instance->StopAnimation(node->GetLeftChild());
			if (!right_result)
				instance->StopAnimation(node->GetRightChild());

			return true;
		}
	default:
		assert(false);
		return false;
	}
}
