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

#include "AnimationCooker.h"
#include "Skeleton.h"
#include "Matrix.h"

#include "math.h"
#include <glew.h>
#include <GL/glut.h>

namespace OSkA
{
	AnimationCooker::AnimationCooker()
	{
		this->timeScale=0;
		this->timeOffset=1;
		this->repeatBeforeBegin=false;
		this->repeatAfterEnd=false;
		this->bonesCount=0;
		this->animationTexture=0;
	}

	AnimationCooker::~AnimationCooker()
	{
		glDeleteTextures(1, &animationTexture);
		delete[] boneTransforms;
		delete[] lengths;
	}

	void AnimationCooker::cook(Skeleton* skeleton, unsigned int timeSamples, bool storeInTexture)
	{
		if( timeSamples >= 2 )
			this->timeSamples = timeSamples;
		else
			this->timeSamples = 256;

		if( storeInTexture )
		{
			int width = 3 * skeleton->getBonesCount() * skeleton->getAnimationsCount();
			boneTransforms=new float[getTimeSamples()*width*4];
			for(int i=0; i<getTimeSamples()*width*4; i++) boneTransforms[i] = 0;

			this->bonesCount=skeleton->getBonesCount();

			lengths = new float[skeleton->getAnimationsCount()];
			numAnimations = skeleton->getAnimationsCount();
			for(int animation=0; animation<skeleton->getAnimationsCount(); animation++)
			{
				lengths[animation] = skeleton->getAnimation(animation)->getLength();
				for(int h=0; h<getTimeSamples(); h++)
				{
					float time= lengths[animation] *(float)h/(float)(getTimeSamples()-1);
					for(int bone=0; bone<bonesCount; bone++)
					{
						int pos= bone*12 + animation*skeleton->getBonesCount()*3*4 + width*4*h;
						Matrix* trans=skeleton->getFullAnimTransform(animation, bone, time);
						trans->Transpose();
						memcpy(&boneTransforms[pos], trans->mCell, 12*sizeof(float));
					}
				}
			}

			glGenTextures(1, &animationTexture);
			glBindTexture(GL_TEXTURE_RECTANGLE_ARB, animationTexture);
			glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA_FLOAT32_ATI, width, timeSamples, 0, GL_RGBA, GL_FLOAT, boneTransforms); 
			glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			delete[] boneTransforms;
		}
		else
		{
			int width = 16 * skeleton->getBonesCount() * skeleton->getAnimationsCount();
			boneTransforms=new float[getTimeSamples()*width];
			for(int i=0; i<getTimeSamples()*width; i++) boneTransforms[i] = 0;

			this->bonesCount=skeleton->getBonesCount();

			lengths = new float[skeleton->getAnimationsCount()];
			numAnimations = skeleton->getAnimationsCount();

			for(int animation=0; animation<skeleton->getAnimationsCount(); animation++)
			{
				lengths[animation] = skeleton->getAnimation(animation)->getLength();
				for(int h=0; h<getTimeSamples(); h++)
				{
					float time= lengths[animation] *(float)h/(float)(getTimeSamples()-1);
					for(int bone=0; bone<bonesCount; bone++)
					{
						int pos= bone*16 + animation*skeleton->getBonesCount()*16 + width*h;
						Matrix* trans=skeleton->getFullAnimTransform(animation, bone, time);
						memcpy(&boneTransforms[pos], trans->mCell, 16*sizeof(float));
					}
				}
			}
		}
	}

	float AnimationCooker::getLength(int animationIndex)
	{
		return lengths[animationIndex];
	}

	int AnimationCooker::getAnimationsCount()
	{
		return numAnimations;
	}

	float AnimationCooker::getTimeInAnimation(float time, int animationIndex)
	{
		float newTime=(time - timeOffset)/ timeScale;
		float count = newTime / lengths[animationIndex];

		if( count < 0 )
			if(repeatBeforeBegin)
				newTime = newTime - ((int)count)*lengths[animationIndex] + lengths[animationIndex];
			else
				newTime = 0;
		else if( count > 1 )
			if(repeatAfterEnd)
				newTime = newTime - ((int)count)*lengths[animationIndex];
			else
				newTime = lengths[animationIndex];

		return newTime;
	}

	float* AnimationCooker::getBoneTransform(float time, int animationIndex)
	{
		if(bonesCount==0)return NULL;
		if(boneTransforms==NULL) return NULL;

		float t=getTimeInAnimation(time, animationIndex);

		float length = lengths[animationIndex];
		int width = 16 * getBonesCount() * getAnimationsCount();
		int offset = animationIndex * getBonesCount() * 16;

		float ts = this->getTimeSamples();

		int i=t /(length / (ts-1.0));
		if(i==ts-1)i--;

		float* ret=new float[bonesCount*16];
		for(int j=0; j<bonesCount*16; j++)
		{
			ret[j]=
				boneTransforms[i*width+offset+j]		*((i+1)* length / ts - t)+
				boneTransforms[(i+1)*width+offset+j]	*(t - i * length / ts);
		}

		return ret;
	}

	unsigned int AnimationCooker::getTimeSamples()
	{
		return timeSamples;
	}

	unsigned int AnimationCooker::getAnimationTextureId()
	{
		return animationTexture;
	}

	int AnimationCooker::getBonesCount()
	{
		return bonesCount;
	}

	void AnimationCooker::setTimeScale(float timeScale)
	{
		this->timeScale=timeScale;
	}

	void AnimationCooker::setTimeOffset(float timeOffset)
	{
		this->timeOffset=timeOffset;
	}

	void AnimationCooker::setRepeatBeforeBegin(bool repeatBeforeBegin)
	{
		this->repeatBeforeBegin=repeatBeforeBegin;
	}

	void AnimationCooker::setRepeatAfterEnd(bool repeatAfterEnd)
	{
		this->repeatAfterEnd=repeatAfterEnd;
	}

	float AnimationCooker::getTimeScale()
	{
		return timeScale;
	}

	float AnimationCooker::getTimeOffset()
	{
		return timeOffset;
	}

	bool AnimationCooker::getRepeatBeforeBegin()
	{
		return repeatBeforeBegin;
	}

	bool AnimationCooker::getRepeatAfterEnd()
	{
		return repeatAfterEnd;
	}
}