//
// Tools_Cutscene_RecordAnimation.cpp
//
// Created by Richard Rice - 26/01/2009
// 
// Copyright (C) 2009 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"
#include "Cutscene/Tools_Cutscene_RecordAnimation.h"

#include "Animation/Asura_Hierarchy.h"
#include "ChunkSystem/Asura_Chunk_FileInfo.h"
#include "ChunkSystem/Asura_Chunk_Hierarchy.h"
#include "ChunkSystem/Asura_Saveblock.h"
#include "Cutscene/Asura_Cutscene.h"
#include "Cutscene/Asura_Cutscene_Actor.h"

#include "Cutscene/Tools_Mode_Cutscene.h"
#include "Misc/Tools_Modal_Overlay.h"
#include "Misc/Tools_Timed_Messages.h"
#include "PC/FileSystem/Tools_FileSystem.h"



Asura_Cutscene* Tools_Cutscene_RecordAnimation::s_pxCutscene = 0;
Asura_Cutscene_Actor_Hierarchy* Tools_Cutscene_RecordAnimation::s_pxActor = 0;
Asura_Hierarchy_Skin* Tools_Cutscene_RecordAnimation::s_pxSkin = 0;

Asura_Hierarchy_Anim* Tools_Cutscene_RecordAnimation::s_pxRecordedAnim = 0;
float Tools_Cutscene_RecordAnimation::s_fFPS = 30.0f;
int Tools_Cutscene_RecordAnimation::s_iNumFrames = 0;
int Tools_Cutscene_RecordAnimation::s_iNextFrame = 0;
bool Tools_Cutscene_RecordAnimation::s_bDoneFirstUpdate = false;

int Tools_Cutscene_RecordAnimation::s_iNumberOfBones = 0;
Asura_Quat* Tools_Cutscene_RecordAnimation::s_pxBoneQuat = 0;
Asura_Vector_3* Tools_Cutscene_RecordAnimation::s_pxBonePos = 0;
Asura_String Tools_Cutscene_RecordAnimation::s_xFilename;

Asura_Quat Tools_Cutscene_RecordAnimation::s_xActorStartQuat;
Asura_Vector_3 Tools_Cutscene_RecordAnimation::s_xActorStartPos;


void Tools_Cutscene_RecordAnimation::StartRecording(Asura_Cutscene* pxCutscene, Asura_Cutscene_Actor* pxActor)
{
	Asura_Assert(pxCutscene, "");
	Asura_Assert(pxActor, "");

	if(IsRecording())
	{
		AbortRecording();
	}

	//Expecting this to be done in cutscene mode , and in particular to be editing the cutscene that is being recorded
	if(pxCutscene != Tools_Mode_Cutscene::GetCurrentCutscene())
	{
		ShowMessageOverlay("Need to be editing the cutscene that the animation is being recored from");
		return;
	}


	//Need to have an end time
	if(!pxCutscene->CutsceneEndTime)
	{
		ShowMessageOverlay("Cutscene must have an end time set , for the animation to be recorded");
		return;
	}

	//The actor needs to be a hierarchy actor , and have a skin
	if(!pxActor->IsHierarchyActor())
	{
		ShowMessageOverlay("Can only record animations for hierarchy actors");
		return;
	}

	
	Asura_Cutscene_Actor_Hierarchy* pxHierActor = (Asura_Cutscene_Actor_Hierarchy*) pxActor;

	Asura_Hierarchy_Skin* pxSkin = 0;
	const char* szSkinName = pxHierActor->GetSkinName();
	if(szSkinName && *szSkinName)
	{
		pxSkin = Asura_Animation_System::GetSkin(szSkinName);

		if(!pxSkin)
		{
			pxCutscene->LoadFiles();
			pxSkin = Asura_Animation_System::GetSkin(szSkinName);
		}
	}

	if(!pxSkin)
	{
		ShowMessageOverlay("The actor doesn't have a skin , or the skin couldn't be found.");
		return;
	}

	//Get a filename to save to
	static Asura_String s_xDefaultPath = "Characters";

	Tools_FileSystem_DialogInfo xInfo;
	xInfo.DefaultExtension = "asr";
	xInfo.DefaultFilename = "";
	xInfo.DefaultPath = s_xDefaultPath.GetString();
	xInfo.FileFilter = "ASR\0*.asr\0";
	xInfo.WindowTitle = "Record animation as ...";

	s_xFilename = Tools_FileSystem::GetSaveFilename(xInfo);
	if(s_xFilename.IsBlank()) return;
	s_xFilename.SplitFilename(&s_xDefaultPath,0,0);

	if(Tools_FileSystem::IsReadOnly(s_xFilename.GetString()))
	{
		ShowMessageOverlay("%s is read-only",s_xFilename.GetString());
		return;
	}

	//get the frame rate to use
	GetNumber_Overlay xNumOverlay("Record at what FPS?", s_fFPS,1.0f, 99.0f, "%.0f");
	if(!xNumOverlay.DoModal()) return;
	s_fFPS = xNumOverlay.GetNumber();
	

	s_pxCutscene = pxCutscene;
	s_pxActor = pxHierActor;
	s_pxSkin = pxSkin;
	s_iNumFrames = -1;//Calculated after cutscene has started , to ensure end time is correct
	s_iNextFrame = 0;
	s_bDoneFirstUpdate = false;
	InitialiseAnimation();

	


	s_iNumberOfBones = s_pxRecordedAnim->GetNumberOfBones();
	s_pxBoneQuat = new Asura_Quat[s_iNumberOfBones];
	s_pxBonePos = new Asura_Vector_3[s_iNumberOfBones];

	Tools_Mode_Cutscene::s_pxThis->AdvanceCutsceneToTime(pxCutscene->m_fCutsceneStartTime);


}

void Tools_Cutscene_RecordAnimation::EndRecording()
{
	Asura_Assert(IsRecording(), "");
	if(!IsRecording()) return;
	
	//Just in case we are short one frame from rounding errors , add it now.
	//Not sure if this will happen in practice
	if(s_iNextFrame == s_iNumFrames-1)
	{
		AddFrame();
	}
	
	if(s_iNumFrames != s_iNextFrame)
	{
		Asura_Assert(s_iNumFrames == s_iNextFrame,"Not recorded the expected number of frames");
		AbortRecording();
		return;
	}

	//0.036 degrees angle tolerance is approximately the same as the quat tolerance used by the max exporters (5e-8)
	s_pxRecordedAnim->RemoveUnneededKeyFrames(0.001f, 0.036f, NULL, NULL);
		
	Asura_SaveBlock xSaveBlock;
	Asura_Chunk_FileInfo::Create(&xSaveBlock,0,AsuraFile_Anim);
	Asura_Chunk_Hierarchy_Anim::Create(s_pxRecordedAnim, &xSaveBlock);
	xSaveBlock.AddAsuraTerminator();
	
	if(xSaveBlock.SaveFile(s_xFilename.GetString(), true))
	{
		Tools_Timed_Message_System::AddMessage(0x00ff00,"Saved %s successfully.",s_xFilename.GetString());
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to save %s",s_xFilename.GetString());
	}


	
	AbortRecording();
}

void Tools_Cutscene_RecordAnimation::AbortRecording()
{
	s_pxCutscene = 0;
	s_pxActor = 0;
	s_pxSkin = 0;
	delete s_pxRecordedAnim;
	s_pxRecordedAnim = 0;
	s_iNumFrames = 0;
	s_iNextFrame = 0;

	delete [] s_pxBoneQuat;
	s_pxBoneQuat = 0;
	delete [] s_pxBonePos;
	s_pxBonePos = 0;
	s_iNumberOfBones = 0;

	s_xFilename.MakeNull();
}

void Tools_Cutscene_RecordAnimation::InitialiseAnimation()
{
	s_pxRecordedAnim = new Asura_Hierarchy_Anim;
	
	s_pxRecordedAnim->Initialise(s_pxSkin->NumberOfBones, 1.0f, true);

	s_xFilename.SplitFilename(0, &s_pxRecordedAnim->Name, 0);
	s_pxRecordedAnim->m_uHashID = s_pxRecordedAnim->Name.GetHash();

	//copy parent bones from skin
	s_pxRecordedAnim->m_piParentBones = new int[s_pxSkin->NumberOfBones];
	for(int i=0; i<s_pxSkin->NumberOfBones; i++)
	{
		s_pxRecordedAnim->m_piParentBones[i] = s_pxSkin->ParentBone[i];
	}

	//copy bone names
	if(s_pxSkin->SectionData)
	{
		s_pxRecordedAnim->m_pxBoneNames = new Asura_String[s_pxSkin->NumberOfBones];
		for(int i=0; i<s_pxSkin->NumberOfBones; i++)
		{
			s_pxRecordedAnim->m_pxBoneNames[i] = s_pxSkin->SectionData[i].SectionName;
		}
	}

	//Copy 'skin export trans' to help with importing into max
	AddSkinExportTransToAnim(s_pxSkin, s_pxRecordedAnim);

}

void Tools_Cutscene_RecordAnimation::AllocateFrames()
{
	float fCutsceneDuration = Asura_Maths::Max(s_pxCutscene->CutsceneEndTime-s_pxCutscene->m_fCutsceneStartTime, 0.0f);
	s_iNumFrames = Asura_Maths::FloatToInt_Ceil(fCutsceneDuration * s_fFPS) + 1;
	
	float fDuration = s_iNumFrames/s_fFPS;
	if(!fDuration) fDuration = 1.0f;

	s_pxRecordedAnim->Duration = fDuration;

	//allocate maximum number of frames for all hierarchy sections
	for(int i=0; i<s_pxRecordedAnim->GetBoneArraySize(); i++)
	{
		s_pxRecordedAnim->BoneAnims[i].Initialise(s_iNumFrames, false);
	}
}


void Tools_Cutscene_RecordAnimation::AddSkinExportTransToAnim(Asura_Hierarchy_Skin* pxSkin, Asura_Hierarchy_Anim* pxAnim)
{
	if(!pxSkin->m_pxInitialRestPose) return;

	//calculate the original and current model relative quats.
	Asura_Quat* pxInitialSkinQuats = new Asura_Quat[pxSkin->NumberOfBones];
	Asura_Quat* pxCurrentSkinQuats = new Asura_Quat[pxSkin->NumberOfBones];
	for(int i=0;i<pxSkin->NumberOfBones;i++)
	{
		if(i)
		{
			int iParent = pxSkin->ParentBone[i];
			pxCurrentSkinQuats[i] = pxCurrentSkinQuats[iParent] * pxSkin->BoneRestPose[i].Quat;
			pxInitialSkinQuats[i] = pxInitialSkinQuats[iParent] * pxSkin->m_pxInitialRestPose[i].Quat;
		}
		else
		{
			pxCurrentSkinQuats[i] = pxSkin->BoneRestPose[i].Quat;
			pxInitialSkinQuats[i] = pxSkin->m_pxInitialRestPose[i].Quat;
		}
	}

	//fill in tranforms from new to old quats into the anim
	pxAnim->m_pxSkinExportTransform = new Asura_Quat[pxAnim->NumberOfBones];
	for(int i=0;i<pxAnim->NumberOfBones;i++)
	{
		if(i<pxSkin->NumberOfBones)
		{
			Asura_Quat xCurrentInv = pxCurrentSkinQuats[i];
			xCurrentInv.w = -xCurrentInv.w;

			pxAnim->m_pxSkinExportTransform[i] = xCurrentInv * pxInitialSkinQuats[i];
			pxAnim->m_pxSkinExportTransform[i].Normalise();
		}
		else
		{
			pxAnim->m_pxSkinExportTransform[i] = Asura_Maths::IdentityQuat;
		}
	}

	delete [] pxInitialSkinQuats;
	delete [] pxCurrentSkinQuats;
}


float Tools_Cutscene_RecordAnimation::GetDesiredCutsceneTimer()
{
	float fTimer = s_pxCutscene->m_fCutsceneStartTime;
	if(s_bDoneFirstUpdate)
	{
		fTimer += s_iNextFrame/s_fFPS;
	}
	else
	{
		s_bDoneFirstUpdate = true;
	}
	return fTimer;
}

void Tools_Cutscene_RecordAnimation::AddFrame()
{
	if(!s_pxRecordedAnim) return;
	if(!s_bDoneFirstUpdate) return;

	if(s_iNumFrames == -1)
	{
		//Should now be safe to initialise the rest of the animation
		AllocateFrames();
	}
	if(s_iNextFrame >= s_iNumFrames)
	{
		//Added all the frames needed
		EndRecording();
		return;
	}

	//work out the index number of the current frame
	int iFrame = s_iNextFrame++;
	float fTime;
	if(iFrame == s_iNumFrames - 1)
	{
		fTime = 1.0f;
	}
	else
	{
		fTime = (float)iFrame / (float)(s_iNumFrames-1);
	}

	//get the world positions of all the bones
	Asura_Animation* pxAnim = s_pxActor->GetAnimation();
	Asura_Assert(pxAnim, "");
	Asura_Assert(pxAnim->GetSkin() == s_pxSkin, "");

	for(int i=0;i<s_iNumberOfBones;i++)
	{
		Asura_Vector_3 xPos;
		Asura_Matrix_3x3 xMat;

		pxAnim->GetBoneLocation(i, &xPos, &xMat, true);

		s_pxBoneQuat[i].FromMatrix(xMat);
		s_pxBonePos[i] = xPos;
	}
	//get the actor position for the compass movement
	Asura_Vector_3 xActorPos = pxAnim->GetPosition();
	Asura_Quat xActorQuat = pxAnim->GetOrientQuat();

	if(iFrame == 0)
	{
		s_xActorStartPos = xActorPos;
		s_xActorStartQuat = xActorQuat;
	}

	Asura_Quat xRelQuat;
	Asura_Vector_3 xRelPos;

	//fill in the relative positions for each bone
	for(int i=0; i<s_iNumberOfBones; i++)
	{
		const Asura_Quat& xThisQuat = s_pxBoneQuat[i];
		const Asura_Vector_3& xThisPos = s_pxBonePos[i];

		int iParent = s_pxSkin->ParentBone[i];
		const Asura_Quat* pxParentQuat = 0;
		const Asura_Vector_3* pxParentPos = 0;
		if(i && iParent>= 0)
		{
			pxParentQuat = &s_pxBoneQuat[iParent];
			pxParentPos = &s_pxBonePos[iParent];

		}
		else
		{
			pxParentQuat = &xActorQuat;
			pxParentPos = &xActorPos;
		}

		GetRelativeQuatAndPos(*pxParentQuat, *pxParentPos, xThisQuat, xThisPos, xRelQuat, xRelPos);

		s_pxRecordedAnim->AddBoneKeyFrame(xRelPos, xRelQuat, fTime, iFrame, i);

	}

	{
		//Add compass movement relative to the start position
		GetRelativeQuatAndPos(s_xActorStartQuat, s_xActorStartPos, xActorQuat, xActorPos, xRelQuat, xRelPos);
		s_pxRecordedAnim->AddBoneKeyFrame(xRelPos, xRelQuat, fTime, iFrame, s_pxRecordedAnim->GetMovementBoneIndex());
	}

}

void Tools_Cutscene_RecordAnimation::GetRelativeQuatAndPos(const Asura_Quat& xParentQuat, const Asura_Vector_3& xParentPos, const Asura_Quat& xThisQuat, const Asura_Vector_3& xThisPos, Asura_Quat& xRelQuat, Asura_Vector_3& xRelPos)
{
	Asura_Quat xInverseQuat = xParentQuat;
	xInverseQuat.w = -xInverseQuat.w;

	xRelQuat = xInverseQuat*xThisQuat;
	xRelQuat.Normalise();
	xRelPos = xInverseQuat.RotateVector(xThisPos-xParentPos) ;
}