///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CmdExportCaf.cpp
// Definition of class CCmdExportCaf,
// a proxy that implements the CryExportCaf command in Maya
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "StdAfx.h"
#include "ChunkFileWriter.h"
#include "crc32.h"
//#include "MayaCryUtils.h"
#include "MayaCryKeyUtil.h"

#include "cmdexportcaf.h"
#include "AdjustRotLog.h"

// constructs a syntax object
MSyntax CCmdExportCaf::newSyntax()
{
	return CCmdExportCryFile::newSyntax ();
}

// parses the common arguments passed to the command (file name)
MStatus CCmdExportCaf::parseArgs (const MArgList& args)
{
	MStatus status = CCmdExportCryFile::parseArgs (args);
	if (!status)
		return status;

	// add whatever code to add parsing capabilities here

	return MStatus::kSuccess;
}


CCmdExportCaf::CCmdExportCaf(void)
{
}

CCmdExportCaf::~CCmdExportCaf(void)
{
}

// does the actual job of executing the command
MStatus CCmdExportCaf::doIt( const MArgList& args )
{
	MStatus status = parseArgs (args);
	if (status.error())
		return status;

	Log ("Exporting to CAF \"%s\"", m_strFileName.asChar());

	MDagPath            dagNode;
	MObject             component, depNode;
	MFnDagNode          fnNode;
	MSelectionList list;
	if (m_lstSelection.length())
		list = m_lstSelection;
	else
	{
		status = MGlobal::getActiveSelectionList(list);

		if (!status)
		{
			Log ("*ERROR* couldn't get the current active selection list: %s", status.errorString().asChar());
			status.perror ("Cannot get the current active selection list");
			return status;
		}
	}

	if (!status)
	{
		Log ("*ERROR* couldn't get the current active selection list, error %d", status);
		return status;
	}

	try
	{
		LOG_AUTO_INTEND();
		prepareObjectsToExport (list);

		dumpExportedBones ();

		m_pFile = new CChunkFileWriterRefTgt (m_strFileName.asChar(), FileType_Anim, AnimFileVersion);

		exportTiming();

		progressStart ("Exporting animation tracks...", m_setExportedJoints.size() + m_setDummiesToExport.size()+2);
		for (MDagPathSet::const_iterator itJoint = m_setExportedJoints.begin(); itJoint != m_setExportedJoints.end(); ++itJoint)
		{
			progressIncrement ();
			progressThrowOnCancel();
			exportSingleBone (*itJoint);
		}

		for (MDagPathSet::const_iterator itDummy = m_setDummiesToExport.begin(); itDummy != m_setDummiesToExport.end(); ++itDummy)
		{
			progressIncrement ();
			progressThrowOnCancel();
			exportSingleBone(*itDummy);
		}

		progressIncrement ();
		exportNameEntities();
	}
	catch (CChunkFileWriter::Error& e)
	{
		Log ("*ERROR* writing file: %s", e.c_str());
		status = MS::kFailure;
	}
	catch (MStatus& e)
	{
		Log ("*ERROR* status error %s", e.errorString().asChar());
		status = e;
	}
	progressEnd();

	m_pFile = NULL; // close the file
	std::cout.flush();
	std::cerr.flush();
	return status;
}


//////////////////////////////////////////////////////////////////////////
// prepares the array of shapes for export (fills the arrays in)
void CCmdExportCaf::prepareObjectsToExport(const MSelectionList& listSelection)
{
	MDagPath pathDagNode;
	MFnDagNode fnNode;

	for ( MItSelectionList listIter( listSelection ); !listIter.isDone(); listIter.next() )
	{
		listIter.getDagPath( pathDagNode );
		if (!fnNode.setObject( pathDagNode ))
		{
			Log ("*WARNING* Skipping selection item %s because it is not a dag path node", pathDagNode.fullPathName ().asChar ());
			continue;
		}

		if (pathDagNode.node().hasFn (MFn::kJoint))
		{	// for now, it will be the root of the exported skeleton hierarchy
			prepareBoneHierarchyToExport(pathDagNode);
		}
		else
		if (!prepareNonBoneToExport (pathDagNode))
			Log ("*WARNING* skipping selection item %s of type %s", pathDagNode.fullPathName ().asChar (), pathDagNode.node().apiTypeStr ());
	}

	// delete
}


//////////////////////////////////////////////////////////////////////////
// Determines the bones that may influence the given object (shape)
// Returns true if at least one bone was found
bool CCmdExportCaf::prepareNonBoneToExport (const MDagPath& pathDagNode)
{
	MDagPath pathShape = pathDagNode;
	if (!pathShape.extendToShape ())
	{
		// this is not a shape - if we don't ignore dummies, then we'll export it
		if (!m_bIgnoreDummies)
		{
			Log0("Exporting pure transform %s as dummy", pathDagNode.partialPathName().asChar());
			m_setDummiesToExport.insert (pathShape);
			return true;
		}
		else
			return false;
	}

	// the mesh may have a skin cluster
	// find the influencing bones for that skin cluster
	MDagPathArray arrBones;
	findShapeInfluences (pathShape.node(), arrBones);
	if (arrBones.length () > 0)
	{
		for (unsigned nBone = 0; nBone < arrBones.length (); ++nBone)
			prepareBoneHierarchyToExport (arrBones[nBone]);

		return true;
	}
	else
	{
		if (pathShape.node().hasFn(MFn::kCamera) || pathShape.node().hasFn(MFn::kLocator) || pathShape.node().hasFn(MFn::kLight))
		{
			MDagPath pathTransform = reduceToTransform (pathShape);
			if (!pathTransform.node().isNull() && !m_bIgnoreDummies)
			{
				Log0("Exporting helper %s as a dummy", pathShape.partialPathName().asChar());
				m_setDummiesToExport.insert (pathTransform);
				return true;
			}
		}
		Log ("*WARNING* skipping mesh (%s) \"%s\": no influencing bones found", pathShape.node().apiTypeStr (), pathShape.partialPathName ().asChar ());
		return false;
	}
}

//////////////////////////////////////////////////////////////////////////
// 1. Prepares for export into controller chunks the bone and all its children.
// 2. Collects the bone names in the name entity array.
// 3. Ignores bones that have already been exported
void CCmdExportCaf::prepareBoneHierarchyToExport (const MDagPath& pathJoint)
{
	if (m_setExportedJoints.find (pathJoint) != m_setExportedJoints.end())
		return;

	// scan through the children
	unsigned numChildren = pathJoint.childCount();

	// if it's a light source and it doesn't have children, we won't use it as a bone
	MFnLight fnLight;
	if (fnLight.setObject(pathJoint))
	{
		// ok, it's not a joint, it's a light

	}

	m_setExportedJoints.insert (pathJoint);

	// the child path
	MDagPath pathChild = pathJoint;
	for (unsigned nChild = 0; nChild < numChildren; ++nChild)
	{			
		MStatus status;
		MObject objChild = pathJoint.child (nChild);
		if (!objChild.hasFn(MFn::kJoint))
		{
			MFnDagNode fnChild (objChild);
			//Log0 (" CCmdExportCaf::prepareBoneHierarchyToExport: Skipping child %s %s", objChild.apiTypeStr(), fnChild.name().asChar());
			continue; // skip if the child isn't a joint
		}
		pathChild.push(objChild);
		prepareBoneHierarchyToExport(pathChild);
		pathChild.pop();
	}
}


//////////////////////////////////////////////////////////////////////////
// logs the exported shape names
void CCmdExportCaf::dumpExportedBones()
{
	std::string strExportBones = toString (m_setExportedJoints);
	std::string strExportHelpers = toString (m_setDummiesToExport);
	Log ("Exporting bones: {%s} helpers: {%s}", strExportBones.c_str (), strExportHelpers.c_str());
}


// exports the bone
void CCmdExportCaf::exportSingleBone (const MDagPath& pathJoint)
{
	MStatus status;
	MFnTransform fnJoint (pathJoint, &status);
	if (status.error())
		throw status;

	std::string strBoneName = fnJoint.name().asChar();
	unsigned nCtrlId = Crc32Gen::GetCRC32 (strBoneName.c_str());
	Log0 ("Bone %s, controller id 0x%08X", strBoneName.c_str(), nCtrlId);
  
	// watch out: there may be duplicate names in the bones
  addNameEntity (strBoneName);

	CMayaCryKeyUtil KeyUtil(pathJoint);
	if (m_bManualRangeStart)
		KeyUtil.setRangeStart (m_tManualRangeStart);
	if (m_bManualRangeEnd)
		KeyUtil.setRangeEnd (m_tManualRangeEnd);
	if (m_bAnimationStep)
		KeyUtil.setMinFrameStep (m_tAnimationStep);

	// 1 digit    => 10^-1 = 0.1
	// 2 digits   => 10^-2 = 0.01
	// 3.5 digits => 10^-3.5 ~= 0.0031
	// we multiply by two because the error is the error of square length, not the length itself

	if (m_bPositionPrecision)
	{
		float fPosError = float (pow (10.0, -2*m_dPositionPrecision));
		KeyUtil.setPosError(fPosError);
	}
	if (m_bRotationPrecision)
	{
		float fQuatError = float (1 - pow (10.0, -m_dRotationPrecision));
		KeyUtil.setRotError (fQuatError);
	}

	if (m_bDisableKeyOptimization)
		KeyUtil.disableOptimization();

	KeyUtil.compute ();

	m_pFile->addChunk (
		ChunkType_Controller,
		CONTROLLER_CHUNK_DESC_0827::VERSION,
		nCtrlId);

	CONTROLLER_CHUNK_DESC_0827 desc;
	//desc.chdr          = m_pFile->getChunk();
	desc.nControllerId = nCtrlId;
	desc.numKeys         = KeyUtil.numCryKeys();
	//desc.type					 = CTRL_CRYBONE;
	m_pFile->write (desc);
	
	CryBoneKey* pKeys = KeyUtil.getCryKeys ();
	unsigned i;

	// determine whether this joint is a root, and if it is, then transform it to Cry CS
	if (isRootJoint (pathJoint))
		for (i = 0; i < desc.numKeys; ++i)
			convertMayaToCryCS (pKeys[i]);

	// The actual CGF file contains inverse quaternions of hte movement.
	// This function makes all quaternions inverse, which is needed for the file.
	KeyUtil.negateCryKeyRotations ();
	KeyUtil.cleanupCryKeyRotations ();

	//m_pFile->writeArray (KeyUtil.getCryKeys(), desc.numKeys);
	CryQuat qLast;
	Vec3 vRotLogLast(0,0,0);
	for(i=0;i<desc.numKeys;i++)
	{
		//DebugPrint(" [%d]", pKeys[i].time/GetTicksPerFrame());
		if ((qLast|pKeys[i].relquat) >= 0)
			qLast =  pKeys[i].relquat;
		else
			qLast = -pKeys[i].relquat;

		CryKeyPQLog key;
		key.nTime = pKeys[i].time;
		key.vPos = pKeys[i].relpos;
		key.vRotLog = log(qLast).v;

		AdjustRotLog (key.vRotLog, vRotLogLast);
		vRotLogLast = key.vRotLog;

		m_pFile->write(key);
	}
}


// determines whether this joint is a root. This is needed to determine whether
// to export it in the world space or in the space of its parent
bool CCmdExportCaf::isRootJoint (const MDagPath& pathJoint)
{
	MDagPath pathParent = pathJoint;
	MStatus status = pathParent.pop ();
	return !status // no parent
		|| (m_setExportedJoints.find(pathParent) == m_setExportedJoints.end()
		&& m_setDummiesToExport.find(pathParent) == m_setDummiesToExport.end()); // the parent hasn't yet been exported
}


// adds the exported bone name to the table of bone names that's exported together
// with the other chunks. throws an error if the bone with such name has already been exported.
void CCmdExportCaf::addNameEntity (const std::string& strBoneName)
{
	if (m_setExportedBones.find (strBoneName) != m_setExportedBones.end())
	{
		std::cerr << "Duplicate bone name '" << strBoneName.c_str() << "'.\nPlease use unique bone names, as they are needed for identification of animation tracks within CAF file.\n";
		throw MS::kFailure;
	}

	m_setExportedBones.insert(strBoneName);

	m_arrBoneNames.push_back(strBoneName);
}


//////////////////////////////////////////////////////////////////////////
// exports the name entity array into a single chunk
void CCmdExportCaf::exportNameEntities()
{
	Log ("Exporting %d name entities", m_arrBoneNames.size());

	m_pFile->addChunk (
		ChunkType_BoneNameList,
		BONENAMELIST_CHUNK_DESC_0745::VERSION,
		newChunkId()
		);
  
	BONENAMELIST_CHUNK_DESC_0745 desc;
	desc.numEntities = (int)m_arrBoneNames.size();
	m_pFile->write (desc);

	for (StringArray::const_iterator it = m_arrBoneNames.begin(); it != m_arrBoneNames.end(); ++it)
		m_pFile->writeArray (it->c_str(), it->length()+1);
	m_pFile->write ('\0');
}
