#include "precompiled.h"

#include "StdSkeletons.h"

#include "CommonConvert.h"

#include "FUtils/FUXmlParser.h"

namespace
{
    struct SkeletonMap : public std::map<std::string, const Skeleton*>
    {
        ~SkeletonMap()
        {
            for (iterator it = begin(); it != end(); ++it)
                delete it->second;
        }
    };

    SkeletonMap g_StandardSkeletons;
    SkeletonMap g_MappedSkeletons;

    struct Bone
    {
        std::string parent;
        std::string name;
        int targetId;
        int realTargetId;
    };
}

struct Skeleton_impl
{
    std::string title;
    std::vector<Bone> bones;
    const Skeleton* target;
};

Skeleton::Skeleton() : m(new Skeleton_impl) { }
Skeleton::~Skeleton() { }

const Skeleton* Skeleton::FindSkeleton(const std::string& name)
{
    return g_MappedSkeletons[name];
}

int Skeleton::GetBoneID(const std::string& name) const
{
    for (size_t i = 0; i < m->bones.size(); ++i)
        if (m->bones[i].name == name)
            return m->bones[i].targetId;
    return -1;
}

int Skeleton::GetRealBoneID(const std::string& name) const
{
    for (size_t i = 0; i < m->bones.size(); ++i)
        if (m->bones[i].name == name)
            return m->bones[i].realTargetId;
    return -1;
}

int Skeleton::GetBoneCount() const
{
    return (int)m->target->m->bones.size();
}

namespace
{
    bool AlreadyUsedTargetBone(const std::vector<Bone>& bones, int targetId)
    {
        for (size_t i = 0; i < bones.size(); ++i)
            if (bones[i].targetId == targetId)
                return true;
        return false;
    }

    // Recursive helper function used by LoadSkeletonData
    void LoadSkeletonBones(xmlNode* parent, std::vector<Bone>& bones, const Skeleton* targetSkeleton, const std::string& targetName)
    {
        xmlNodeList boneNodes;
        FUXmlParser::FindChildrenByType(parent, "bone", boneNodes);
        for (xmlNodeList::iterator boneNode = boneNodes.begin(); boneNode != boneNodes.end(); ++boneNode)
        {
            std::string name = FUXmlParser::ReadNodeProperty(*boneNode, "name");

            Bone b;
            b.name = name;
            
            std::string newTargetName = targetName;

            if (targetSkeleton)
            {
                xmlNode* targetNode = FUXmlParser::FindChildByType(*boneNode, "target");
                if (targetNode)
                    newTargetName = FUXmlParser::ReadNodeContentFull(targetNode);
                // else fall back to the parent node's target

                b.targetId = targetSkeleton->GetBoneID(newTargetName);
                REQUIRE(b.targetId != -1, "skeleton bone target matches some standard_skeleton bone name");

                if (AlreadyUsedTargetBone(bones, b.targetId))
                    b.realTargetId = -1;
                else
                    b.realTargetId = b.targetId;
            }
            else
            {
                // No target - this is a standard skeleton

                b.targetId = (int)bones.size();
                b.realTargetId = b.targetId;
            }

            bones.push_back(b);

            LoadSkeletonBones(*boneNode, bones, targetSkeleton, newTargetName);
        }
    }

    void LoadSkeletonData(xmlNode* root)
    {
        xmlNodeList skeletonNodes;
        FUXmlParser::FindChildrenByType(root, "standard_skeleton", skeletonNodes);
        FUXmlParser::FindChildrenByType(root, "skeleton", skeletonNodes);
        for (xmlNodeList::iterator skeletonNode = skeletonNodes.begin();
            skeletonNode != skeletonNodes.end(); ++skeletonNode)
        {
            std::auto_ptr<Skeleton> skeleton (new Skeleton());

            std::string title = FUXmlParser::ReadNodeProperty(*skeletonNode, "title");
            
            skeleton->m->title = title;

            if (IsEquivalent((*skeletonNode)->name, "standard_skeleton"))
            {
                skeleton->m->target = NULL;

                LoadSkeletonBones(*skeletonNode, skeleton->m->bones, NULL, "");

                std::string id = FUXmlParser::ReadNodeProperty(*skeletonNode, "id");
                REQUIRE(! id.empty(), "standard_skeleton has id");

                g_StandardSkeletons[id] = skeleton.release();
            }
            else
            {
                // Non-standard skeletons need to choose a standard skeleton
                // as their target to be mapped onto

                std::string target = FUXmlParser::ReadNodeProperty(*skeletonNode, "target");
                const Skeleton* targetSkeleton = g_StandardSkeletons[target];
                REQUIRE(targetSkeleton != NULL, "skeleton target matches some standard_skeleton id");

                skeleton->m->target = targetSkeleton;

                LoadSkeletonBones(*skeletonNode, skeleton->m->bones, targetSkeleton, "");

                // Currently the only supported identifier is a precise name match,
                // so just look for that
                xmlNode* identifier = FUXmlParser::FindChildByType(*skeletonNode, "identifier");
                REQUIRE(identifier != NULL, "skeleton has <identifier>");
                xmlNode* identRoot = FUXmlParser::FindChildByType(identifier, "root");
                REQUIRE(identRoot != NULL, "skeleton identifier has <root>");
                std::string identRootName = FUXmlParser::ReadNodeContentFull(identRoot);

                g_MappedSkeletons[identRootName] = skeleton.release();
            }
        }
    }
}

void errorHandler(void* ctx, const char* msg, ...);

void Skeleton::LoadSkeletonDataFromXml(const char* xmlData, size_t xmlLength, std::string& xmlErrors)
{
    xmlDoc* doc = NULL;
    try
    {
        xmlSetGenericErrorFunc(&xmlErrors, &errorHandler);
        doc = xmlParseMemory(xmlData, xmlLength);
        if (doc)
        {
            xmlNode* root = xmlDocGetRootElement(doc);
            LoadSkeletonData(root);
            xmlFreeDoc(doc);
            doc = NULL;
        }
        xmlCleanupParser();
        xmlSetGenericErrorFunc(NULL, NULL);
    }
    catch (const ColladaException&)
    {
        if (doc)
            xmlFreeDoc(doc);
        xmlCleanupParser();
        xmlSetGenericErrorFunc(NULL, NULL);
        throw;
    }

    if (! xmlErrors.empty())
        throw ColladaException("XML parsing failed");
}