// Implementation of the class that implements CryExportUtil command that is used internally by
// the User Interface scripts shipped with the Exporter plugin
#include "StdAfx.h"
#include "cmdexportutil.h"

CCmdExportUtil::CCmdExportUtil(void):
	m_bReturnFullPaths (false),
	m_bIgnoreDummies (false)
{
}

CCmdExportUtil::~CCmdExportUtil(void)
{
}

// Does the actual job
MStatus	CCmdExportUtil::doIt ( const MArgList& args)
{
	MStatus status = parseArgs (args);
	if (status.error())
		return status;

	clearResult();

	if (m_strAction == "toCGF")
		status = prepareForGeometryExport();
	else
	if (m_strAction == "toCAF")
		status = prepareForAnimationExport();
	else
	{
		status = MS::kFailure;
		status.perror ("Unexpected action " + m_strAction);
	}
	std::cout.flush();
	std::cerr.flush();
	return status;
}

// constructs a syntax object
MSyntax CCmdExportUtil::newSyntax()
{
	MSyntax syntax;
	syntax.addFlag ("-a", "-action", MSyntax::kString);
	syntax.addFlag ("-fp", "-fullPath", MSyntax::kBoolean);
	syntax.addFlag ("-id", "-ignoreDummies", MSyntax::kBoolean);
	syntax.setObjectType(MSyntax::kSelectionList);
	syntax.useSelectionAsDefault (true);
	return syntax;
}

// parses the common arguments passed to the command (file name)
MStatus CCmdExportUtil::parseArgs (const MArgList& args)
{
	MStatus status = MS::kSuccess;

	MArgDatabase argDB(syntax(), args, &status);
	if (!status)
		return status;

	status = argDB.getFlagArgument ("-action", 0, m_strAction);
	if (!status)
		return status;

	status = argDB.getFlagArgument("-fullPath", 0, m_bReturnFullPaths);
	if (!status)
		m_bReturnFullPaths = false;

	status = argDB.getFlagArgument("-ignoreDummies", 0, m_bIgnoreDummies);
	if (!status)
		m_bIgnoreDummies = false;

	status = argDB.getObjects(m_lstSelection);
	if (!status)
		return status;

	return status;
}


// -toCGF action
// converts the selection to an array of mesh, dummy etc. objects: converts each bone to the corresponding mesh object
MStatus CCmdExportUtil::prepareForGeometryExport ()
{
	for ( MItSelectionList listIter( m_lstSelection ); !listIter.isDone(); listIter.next() )
	{
		MDagPath pathDagNode;
		MObject component;
		listIter.getDagPath( pathDagNode, component );

		if (!component.isNull())
			std::cerr << "Warning: components are not supported (" << pathDagNode.partialPathName().asChar() << ", component of a " << component.apiTypeStr() << ")\n";

		MDagPath pathShape = pathDagNode;
		MStatus status = pathShape.extendToShape();
		if (!status)
		{
			// this is a dummy (group etc)
			if (prepareConnectedFleshForGeometryExport (pathDagNode))
				continue;
			else
			{
				if (!m_bIgnoreDummies)
					prepareHierarchyForGeometryExport (pathDagNode);
			}
		}
		else
			prepareHierarchyForGeometryExport (pathDagNode);
	}

	// we don't want the shapes to be in the list - we'll only return transform nodes
	cleanupShapes();

	appendSetToResult (m_setPathResult);
	return MS::kSuccess;
}


// cleans up pure shapes from the list, leaves only transform nodes;
// this automatically deletes extra nodes like shaders etc.
void CCmdExportUtil::cleanupShapes()
{
	MDagPathSet setPathResult;
	for (MDagPathSet::const_iterator it = m_setPathResult.begin(); it!= m_setPathResult.end(); ++it)
	{
		MDagPath pathNode = *it;
		if (!pathNode.node().hasFn(MFn::kTransform))
			if (!pathNode.pop())
				continue;
		if (pathNode.node().hasFn(MFn::kTransform))
			setPathResult.insert (pathNode);
	}

	m_setPathResult.swap(setPathResult);
}


// reads transform hierarchy and adds it to the result set
void CCmdExportUtil::prepareHierarchyForGeometryExport (const MDagPath& pathHierarchy, int nRecurseLevels)
{
	m_setPathResult.insert (pathHierarchy);
	if (nRecurseLevels <= 0)
		return;

	unsigned numChildren = pathHierarchy.childCount();
	for (unsigned nChild = 0; nChild < numChildren; ++nChild)
	{
		MDagPath pathChild = pathHierarchy;
		pathChild.push(pathHierarchy.child(nChild));
		prepareHierarchyForGeometryExport(pathChild, nRecurseLevels -1 );
	}
}


// -toCAF action
// converts the selection to an array of bones suitable for animation export; converts mesh to the array of influencing bones
MStatus CCmdExportUtil::prepareForAnimationExport ()
{
	MDagPath pathDagNode;
	MFnDagNode fnNode;

	for ( MItSelectionList listIter( m_lstSelection ); !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;
		}

		// we check for the Joint (NOT Transform) MFn because if it's a transform, it may contain influenced mesh 
		// underneath, and the mesh is used to determine the bones in NonBone... function
		if (pathDagNode.node().hasFn (MFn::kJoint))
		{	// for now, it will be the root of the exported skeleton hierarchy
			prepareBoneHierarchyForAnimationExport(pathDagNode);
		}
		else
			if (!prepareNonBoneForAnimationExport (pathDagNode))
				Log ("*WARNING* skipping selection item %s of type %s", pathDagNode.fullPathName ().asChar (), pathDagNode.node().apiTypeStr ());
	}

	// top it with the parent of all the tracks
	prepareBoneHierarchyForAnimationExport(getCommonParent (m_setPathResult));

	appendSetToResult (m_setPathResult);
	return MS::kSuccess;
}


// prepares the given bone and all its children for export
void CCmdExportUtil::prepareBoneHierarchyForAnimationExport(const MDagPath& pathJoint)
{
	if (!pathJoint.isValid() || m_setPathResult.find (pathJoint) != m_setPathResult.end())
		// this hierarchy is already present here
		return;

	m_setPathResult.insert (pathJoint);

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

// prepares non-bone for export: searches for influencing bones and addds them (hierarchies) to the exported list
bool CCmdExportUtil::prepareNonBoneForAnimationExport (const MDagPath& pathDagNode)
{
	MDagPath pathShape = pathDagNode;
	Log0(" CCmdExportUtil::prepareNonBoneForAnimationExport (%s)", pathDagNode.partialPathName().asChar());
	if (!pathShape.extendToShape ())
	{
		// Not a shape; maybe a dummy (root of the hierarchy)
		if (!m_bIgnoreDummies && pathShape.hasFn(MFn::kTransform))
		{
			prepareBoneHierarchyForAnimationExport(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);
	Log0("  Found %u shape influences", arrBones.length ());
	if (arrBones.length () > 0)
	{
		for (unsigned nBone = 0; nBone < arrBones.length (); ++nBone)
			prepareBoneHierarchyForAnimationExport (arrBones[nBone]);

		return true;
	}
	else
	{
		Log ("  *WARNING* skipping mesh (%s) \"%s\": no influencing bones found", pathShape.node().apiTypeStr (), pathShape.partialPathName ().asChar ());
		return false;
	}
}


// adds the flesh connected to the given bone to the result set; returns true if it succeeded;
// false if there is no flesh connected or it is not a bone
bool CCmdExportUtil::prepareConnectedFleshForGeometryExport (const MDagPath& pathDagNode)
{
	// Prepares the flesh on the bones to export
	//
	MStatus status;
	MFnGeometryFilter fnSkinCluster (findSkinCluster (pathDagNode, MItDependencyGraph::kDownstream), &status);
	if (!status)
		return false;

	// found a cluster - export all the shapes in it
	MObjectArray arrOutGeoms;
	if (!fnSkinCluster.getOutputGeometry (arrOutGeoms))
		return false; // couldn't find the output geometry for the bone - ignore it

	// for each geometry, determine the dag nodes and add to the result set
	for (unsigned nGeom = 0; nGeom < arrOutGeoms.length (); ++nGeom)
	{
		MObject& objGeom = arrOutGeoms[nGeom];
		if (!objGeom.hasFn (MFn::kMesh))
		{
			Log ("*WARNING* Ignoring selection item's %s skin cluster %s output geometry %d of type %s: the object is not a mesh", pathDagNode.fullPathName ().asChar (), fnSkinCluster.name ().asChar (), nGeom, arrOutGeoms[nGeom].apiTypeStr ());
			continue;
		}

		MStatus status;
    MDagPathArray arrAllPaths;
		status = MDagPath::getAllPathsTo (objGeom, arrAllPaths);
		if (!status)
		{
			Log ("*WARNING Ignoring selection item's %s skin cluster %s output geometry %d of type %s: cannot retrieve all paths: %s", pathDagNode.fullPathName ().asChar (), fnSkinCluster.name ().asChar (), nGeom, arrOutGeoms[nGeom].apiTypeStr (), status.errorString ().asChar ());
			continue;
		}
		
		for (unsigned nPath = 0; nPath < arrAllPaths.length (); ++nPath)
      m_setPathResult.insert (reduceToTransform(arrAllPaths[nPath]));
	}
	return true;
}

// appends the given dag path set to the result
void CCmdExportUtil::appendSetToResult (const MDagPathSet& setResult)
{
	for (MDagPathSet::const_iterator it = setResult.begin(); it != setResult.end(); ++it)
		appendToResult (m_bReturnFullPaths?it->fullPathName():it->partialPathName());
}