/** \file Skeleton.cpp
 * \brief The implementation of the 'Skeleton' class.
 */

#include "Skeleton.h"

#include "XmlTools.h"

#include <xercesc/dom/DOM.hpp>
#include <xercesc/dom/DOMDocument.hpp>
#include <xercesc/dom/DOMDocumentType.hpp>
#include <xercesc/dom/DOMElement.hpp>
#include <xercesc/dom/DOMImplementation.hpp>
#include <xercesc/dom/DOMImplementationLS.hpp>
#include <xercesc/dom/DOMNodeIterator.hpp>
#include <xercesc/dom/DOMNodeList.hpp>
#include <xercesc/dom/DOMText.hpp>
XERCES_CPP_NAMESPACE_USE

namespace OSkA
{
	Skeleton::Skeleton()
	{
		bones.resize(0);
		animations.resize(0);
		tempBoneTransforms.resize(0);
	}

	Skeleton::~Skeleton()
	{
		clear();
	}

	void Skeleton::clear()
	{
		clearTempData();

		for(int i=0; i<bones.size(); i++)
			delete bones[i];
		bones.clear();

		for(int i=0; i<animations.size(); i++)
			delete animations[i];
		animations.clear();
	}

	void Skeleton::clearTempData()
	{
		for(int i=0; i<tempBoneTransforms.size(); i++)
			delete tempBoneTransforms[i];
		tempBoneTransforms.clear();
	}

	Skeleton::Skeleton(char* filename)
	{
		for(int i=0; i<bones.size(); i++)
			delete bones[i];
		bones.resize(0);

		for(int i=0; i<animations.size(); i++)
			delete animations[i];
		animations.resize(0);

		loadFile(filename);
	}

	bool Skeleton::loadFile(char* filename)
	{
		try
		{
			XMLPlatformUtils::Initialize(); // Initialize Xerces infrastructure
		}
		catch(XMLException& e)
		{
			return false;
		}

		xercesc::XercesDOMParser *parser=new XercesDOMParser;

		parser->setValidationScheme(XercesDOMParser::Val_Never);
		parser->setDoNamespaces(false);
		parser->setDoSchema(false);
		parser->setLoadExternalDTD(false);

		xercesc::DOMDocument* xmlDoc;
		try
		{
			parser->parse(filename);
			xmlDoc=parser->getDocument();
			if(!xmlDoc)// Do we have no document? Will happen when file does not exist.
				return false;
		}
		catch(xercesc::XMLException& e)
		{
			return false;
		}

		clear();

		DOMNodeList* superNodes;
		DOMNodeList* nodes;
		DOMNodeList* nodes2;
		DOMNodeList* nodes3;

		bones.resize(0);
		animations.resize(0);

		superNodes=getNodeList(xmlDoc, "bone");
		for(int sn=0; sn<superNodes->getLength(); sn++)
		{
			float rotAngle=0;
			float *pos, *rotAxis, *scale;
			pos=new float[3];
			rotAxis=new float[3];
			scale=new float[3];
			pos[0]=pos[1]=pos[2]=0;
			rotAxis[0]=rotAxis[1]=rotAxis[2]=1;
			scale[0]=scale[1]=scale[2]=1;

			DOMElement* currentSn=dynamic_cast< xercesc::DOMElement* >(superNodes->item(sn));

			int index=getIntAttribute(superNodes->item(sn), "id");
			char* name=getCharAttribute(superNodes->item(sn), "name");

			nodes=getNodeList(currentSn, "position");
			if(nodes->getLength()> 0)
				pos=getXYZAttributes(nodes->item(0));

			nodes=getNodeList(currentSn, "scale");
			if(nodes->getLength()> 0)
			{
				char* factor=getCharAttribute(nodes->item(0), "factor");
				if(std::string(factor).compare("")==0)
					scale=getXYZAttributes(nodes->item(0));
				else
					scale[0]=scale[1]=scale[2]=atof(factor);
			}

			nodes=getNodeList(currentSn, "axis");
			if(nodes->getLength()> 0)
				rotAxis=getXYZAttributes(nodes->item(0));

			nodes=getNodeList(currentSn, "rotation");
			if(nodes->getLength()> 0)
				rotAngle=getFloatAttribute(nodes->item(0), "angle");

			Matrix* trans=new Matrix();
			trans->SetTransform(pos, rotAngle, rotAxis, scale);
			addBone(index, name, trans);
		}

		superNodes=getNodeList(xmlDoc, "boneparent");
		for(int sn=0; sn<superNodes->getLength(); sn++)
		{
			char* bone=getCharAttribute(superNodes->item(sn), "bone");
			char* parent=getCharAttribute(superNodes->item(sn), "parent");
			setBoneParent(bone, parent);
		}

		superNodes=getNodeList(xmlDoc, "animation");
		for(int sn=0; sn<superNodes->getLength(); sn++)
		{
			DOMElement* currentSn=dynamic_cast< xercesc::DOMElement* >(superNodes->item(sn));

			char* name=getCharAttribute(superNodes->item(sn), "name");
			float length=getFloatAttribute(superNodes->item(sn), "length");
			addAnimation(name, length);

			Animation* anim=getAnimation(name);

			nodes=getNodeList(currentSn, "track");
			for(int n=0; n<nodes->getLength(); n++)
			{
				DOMElement* currentN=dynamic_cast< xercesc::DOMElement* >(nodes->item(n));

				int boneId=getBoneId(getCharAttribute(nodes->item(n), "bone"));
				anim->addTrack(boneId);

				Track* track=anim->getTrack(boneId);

				nodes2=getNodeList(currentN, "keyframe");
				for(int n2=0; n2<nodes2->getLength(); n2++)
				{
					float time=getFloatAttribute(nodes2->item(n2), "time");

					float rotAngle=0;
					float *pos, *rotAxis, *scale;
					pos=new float[3];
					rotAxis=new float[3];
					scale=new float[3];
					pos[0]=pos[1]=pos[2]=0;
					rotAxis[0]=rotAxis[1]=rotAxis[2]=1;
					scale[0]=scale[1]=scale[2]=1;

					DOMElement* currentN2=dynamic_cast< xercesc::DOMElement* >(nodes2->item(n2));
					nodes3=getNodeList(currentN2, "translate");
					if(nodes3->getLength()> 0)
						pos=getXYZAttributes(nodes3->item(0));

					nodes3=getNodeList(currentN2, "scale");
					if(nodes3->getLength()> 0)
					{
						char* factor=getCharAttribute(nodes3->item(0), "factor");
						if(std::string(factor).compare("")==0)
							scale=getXYZAttributes(nodes3->item(0));
						else
							scale[0]=scale[1]=scale[2]=atof(factor);
						delete factor;
					}

					nodes3=getNodeList(currentN2, "axis");
					if(nodes3->getLength()> 0)
						rotAxis=getXYZAttributes(nodes3->item(0));

					nodes3=getNodeList(currentN2, "rotate");
					if(nodes3->getLength()> 0)
						rotAngle=getFloatAttribute(nodes3->item(0), "angle");

					Matrix* trans=new Matrix();
					trans->SetTransform(pos, rotAngle, rotAxis, scale);
					track->addKeyframe(time, trans);
				}
			}
		}

		tempBoneTransforms.resize(bones.size());
		for(int i=0; i<bones.size(); i++)tempBoneTransforms[i]=NULL;

		delete parser;
		XMLPlatformUtils::Terminate();

		return true;
	}

	Matrix* Skeleton::getFullBoneTransform(int bone)
	{
		if(tempBoneTransforms[bone]==NULL)
		{
			Matrix* ret=new Matrix();
			Matrix m1, m2;

			m2=*bones[bone]->getTransform();
			if(!bones[bone]->isRoot())
				m1=*getFullBoneTransform(bones[bone]->getParentId());
			*ret=m1 * m2;

			tempBoneTransforms[bone]=ret;
		}

		return tempBoneTransforms[bone];
	}

	Matrix* Skeleton::getFullAnimTransform(int animation, int bone, float time)
	{
		Matrix *ret=new Matrix();
		Matrix m1, m2, m3, m4;

		m2=*getFullBoneTransform(bone);
		m3=*getAnimation(animation)->getTrack(bone)->getInterpolTransform(time);
		m4=*getFullBoneTransform(bone)->GetInverted();
		if(!bones[bone]->isRoot())
			m1=*getFullAnimTransform(animation, bones[bone]->getParentId(), time);
		*ret=m1 * m2 * m3 * m4;
		return ret;
	}

	void Skeleton::addBone(int id, char* name, Matrix* transform)
	{
		if(id >=getBonesCount())
			bones.resize(id+1);
		for(int i=0; i<getBonesCount(); i++)
			if(!bones[i])
				bones[i]=new Bone();

		bones[id]->setName(name);
		bones[id]->setTransform(transform);
	}

	int Skeleton::getBoneId(char* name)
	{
		for(int i=0; i<getBonesCount(); i++)
		{
			if(std::string(bones[i]->getName()).compare(name)==0)
				return i;
		}
		return 0;
	}

	void Skeleton::setBoneParent(char* name, char* parent)
	{
		bones[getBoneId(name)]->setParentId(getBoneId(parent));
	}

	Bone* Skeleton::getBone(char* name)
	{
		return bones[getBoneId(name)];
	}

	Bone* Skeleton::getBone(int id)
	{
		return bones[id];
	}

	int Skeleton::getBonesCount()
	{
		return bones.size();
	}

	void Skeleton::addAnimation(char* name, float length)
	{
		animations.push_back(new Animation(name, length));
	}

	Animation* Skeleton::getAnimation(char* name)
	{
		return animations[getAnimationId(name)];
	}

	Animation* Skeleton::getAnimation(int id)
	{
		return animations[id];
	}

	int Skeleton::getAnimationsCount()
	{
		return animations.size();
	}

	int Skeleton::getAnimationId(char* name)
	{
		for(int i=0; i<getAnimationsCount(); i++)
		{
			if(std::string(animations[i]->getName()).compare(name)==0)
				return i;
		}
		return 0;
	}
}