#include "Managers/AnimationManager.h"
#include "Animators/IAnimator.h"
#include "Records/Mesh.h"
#include "Util/QStack.h"
#include "Util/Flag.h"
#include "Game/Game.h"

#include <vector>

// initialize any static variables needed for singleton
IMP_SINGLETON(AnimationManager);

using namespace std;

AnimationManager::AnimationManager()
{
	// nothing for c'tor to do
}

AnimationManager::~AnimationManager()
{
	// nothing for d'tor to do
}

int AnimationManager::initiate()
{
	return 1;	// return success
}

int AnimationManager::update()
{
	AnimatorIterator animatorIt = m_animators.begin();
	NodeListMapIterator nodeListIt = m_animationNodeLists.begin();

	// loop through all node lists, collapsing, and then processing with appropriate animator object
	while(nodeListIt != m_animationNodeLists.end())
	{
		AnimationNodeList * nodeList = nodeListIt->second;
		IAnimator *animator = animatorIt->second;

		collapseNodes( nodeList );

		animator->processNodes( nodeList );

		++nodeListIt;
		++animatorIt;
	}

	return 1;	// return success
}

int AnimationManager::shutdown()
{
	// destroy all animation-nodes/animation-node-lists
	for(NodeListMapIterator i = m_animationNodeLists.begin(); i != m_animationNodeLists.end(); ++i)
	{
		AnimationNodeList * nodes = i->second;

		if(nodes)
		{
			AnimationNodeIterator j = nodes->begin();

			while(j != nodes->end())
			{
				AnimationNode *node = *j;
			
				++j;

				destroyAnimationNode(node);
			}
			
			nodes->clear();

			delete nodes;
		}
	}

	m_animationNodeLists.clear();

	// destroy all animation sets
	for(AnimationSetIterator i = m_animationSets.begin(); i != m_animationSets.end(); ++i)
	{
		AnimationSet *animationSet = i->second;

		AnimatorIterator animatorIt = m_animators.find( animationSet->animatorHash );

		if(animatorIt != m_animators.end())
		{
			IAnimator *animator = animatorIt->second;

			animator->destroyAnimationSet( animationSet );
		}
	}

	m_animationSets.clear();

	// delete animator objects
	for(AnimatorIterator i = m_animators.begin(); i != m_animators.end(); ++i)
	{
		IAnimator *animator = i->second;

		if(animator)
			delete animator;
	}

	m_animators.clear();

	return 1;
}

AnimationSet *AnimationManager::createAnimationSet(string fileName, hash32 animatorHash, void *data)
{
	AnimationSet *animationSet = NULL;
	AnimationSetIterator it;
	bool setExists;
	pair<AnimationSetIterator,bool> result;

	// attempt inserting blank animation set entry
	result = m_animationSets.insert( pair<string,AnimationSet*>(fileName,(AnimationSet*)NULL) );
	it = result.first;
	setExists = !result.second;

	// if entry could not be inserted because set already exists,
	// set our return result to the entry's animation set and increase its reference count
	if(setExists)
	{
		animationSet = it->second;
		++animationSet->refCount;
	}

	// else, create a new animation set
	else
	{
		// look up animator
		AnimatorIterator animatorIt = m_animators.find( animatorHash );

		if(animatorIt != m_animators.end())
		{
			IAnimator *animator = animatorIt->second;

			animationSet = animator->createAnimationSet(fileName,data);

			if(animationSet != NULL)
			{
				animationSet->animatorHash = animatorHash;
				animationSet->refCount = 1;
				animationSet->parentListPos = it;
				it->second = animationSet;
			}
		}
	}

	return animationSet;
}

void AnimationManager::destroyAnimationSet(AnimationSet *set)
{
	if(!set)
		return;

	// decrement reference count
	--set->refCount;

	// if no one is using it any longer, get rid of it
	if(!set->refCount)
	{
		// look up animator
		AnimatorIterator animatorIt = m_animators.find( set->animatorHash );

		if(animatorIt != m_animators.end())
		{
			IAnimator *animator = animatorIt->second;

			animator->destroyAnimationSet( set );

			m_animationSets.erase( set->parentListPos );
		}
	}
}

AnimationNode *AnimationManager::createAnimationNode(AnimationNode *parent, Mesh *mesh, int flags, float weight)
{
	if(!CF(flags,ANF_HEADER) && !mesh->animationHeader)
		return NULL;

	Mesh::Shared *shared = mesh->shared;
	AnimationSet *set = shared->animationSet;
	AnimationNode *node = new AnimationNode;
	AnimationNode::HeaderInfo *headerInfo = NULL;
	bool is_header = CF(flags,ANF_HEADER);
	hash32 animatorHash = set->animatorHash;

	// if this is a header, add to animation set node list, and set up headerInfo structure
	if(is_header)
	{
		AnimationNodeList * nodes = m_animationNodeLists[ animatorHash ];

		nodes->push_back(node);

		node->parentList = nodes;
		node->parentListPos = --nodes->end();

		headerInfo = new AnimationNode::HeaderInfo();
		headerInfo->mesh = mesh;

		mesh->animationHeader = node;
	}

	// otherwise, add node to given parent or mesh's existing header node if given parent is null
	else
	{
		if(parent == NULL)
			parent = mesh->animationHeader;

		AnimationNodeList & nodes = parent->children;

		parent->children.push_back(node);

		node->parentList = &nodes;
		node->parentListPos = --nodes.end();
	}

	node->weight = weight;
	node->flags = flags;
	node->progress = 0.0f;
	node->rate = 0.0f;
	node->headerInfo = headerInfo;

	return node;
}

void AnimationManager::destroyAnimationNode(AnimationNode *node)
{
	if(!node)
		return;

	AnimationNode::HeaderInfo *headerInfo = node->headerInfo;
	AnimationNodeList *list = node->parentList;

	if(headerInfo)
		delete headerInfo;
	
	// erase from parent list
	list->erase(node->parentListPos);

	// recursively destroy child nodes, and also deallocate given node
	_destroyAnimationNode(node);
}

void AnimationManager::collapseNodes(AnimationNodeList *nodes)
{
	Game *game = Game::getGlobal();
	float elapsed = game->getElapsed(); // grab elapsed amount of time since last game update
	
	// collapse each node's tree
	for(AnimationNodeIterator i = nodes->begin(); i != nodes->end(); ++i)
	{
		AnimationNode *header = *i;

		collapseTree(header,elapsed);
	}
}

void AnimationManager::collapseTree(AnimationNode *header, float elapsed)
{
	AnimationNode::HeaderInfo *headerInfo = header->headerInfo;
	QStack<AnimationNode> treeExpansion;
	QStack<AnimationNode> & animationStack = headerInfo->animationStack;

	if(header->weight == 0.0f)
		return;

	// header's weight is expected to be already in the range [0,1], and is the only
	// node at its level, therefore, it's "collapsed weight" is equal to it's weight
	header->collapsedWeight = header->weight;

	// here we traverse the tree via a stack, so we push the header node first
	treeExpansion.push(header);

	while(!treeExpansion.empty())
	{
		AnimationNode *node = treeExpansion.pop(); // grab the next node off the stack
		AnimationNodeList & children = node->children;

		int & flags = node->flags;

		float weight = node->weight;
		float collapsedWeight = node->collapsedWeight;
		float childWeightTotal = 0.0f;
		float multiplier;
		float & progress = node->progress;

		bool is_header = CF(flags,ANF_HEADER);
		bool is_looping = CF(flags,ANF_LOOPING);
		bool is_animation = CF(flags,ANF_ANIMATION);
		bool is_dead = CF(flags,ANF_DEAD);

		progress += node->rate*elapsed; // update node progress

		// adjust progress as necessary taking into account the looping flag
		if(progress >= 1.0f)
		{
			if(is_looping)
			{
				while(progress >= 1.0f)
					progress -= 1.0f;
			}

			else
			{
				progress = 1.0f;
				flags |= ANF_DEAD;
			}
		}

		// if this node is an animation, push it onto the final stack
		if(is_animation)
			animationStack.push(node);

		else
		{
			// Go through all children, finding the sum of their weights
			for(AnimationNodeIterator j = children.begin(); j != children.end(); ++j)
			{
				AnimationNode *child = *j;

				childWeightTotal += child->weight;
			}

			if(childWeightTotal > 0.0f)
			{
				// collapsed weight of child = child->weight / childWeightTotal * collapsedWeightOfParent, so
				// calculate part that is same for all children first
				multiplier = collapsedWeight / childWeightTotal;

				// loop through all children, calculating collapsed weight, and pushing them onto the stack
				for(AnimationNodeIterator j = children.begin(); j != children.end(); ++j)
				{
					AnimationNode *child = *j;

					child->collapsedWeight = child->weight * multiplier;
					
					treeExpansion.push(child);
				}
			}
		}
		
	}
}

// recursively deallocates children nodes and their parents
void AnimationManager::_destroyAnimationNode(AnimationNode *node)
{
	if(!node)
		return;

	AnimationNodeList & children = node->children;

	for(AnimationNodeIterator i = children.begin(); i != children.end(); ++i)
		_destroyAnimationNode(*i);
	
	children.clear();

	delete node;
}

