#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;
}