#include "stdafx.h"
#include "MayaUtils.h"

//////////////////////////////////////////////////////////////////////////
// finds the skin cluster by a shape
// returns kNullObj if no skin cluster was found
MObject findSkinCluster (const MObject& objShape, MItDependencyGraph::Direction nDirection)
{
	MStatus status;
	MObject obj = objShape;
	//Log ("Searching for skin cluster for shape of type '%s' name '%s' direction %s",
	//	obj.apiTypeStr (), MFnDependencyNode(obj).name ().asChar (),
	//	nDirection == MItDependencyGraph::kUpstream ? "Upstream" : "Downstream");
	MItDependencyGraph itDep (obj, MFn::kSkinClusterFilter, nDirection, MItDependencyGraph::kBreadthFirst, MItDependencyGraph::kNodeLevel, &status);
	if (!status)
	{
		//std::cerr << "Cannot construct skin cluster dag iterator: " << status.errorString ().asChar () << "\n";
		// couldn't construct the skin cluster filter
		return MObject::kNullObj;
	}

	if (itDep.isDone ())
		// no skin cluster for this shape
		return MObject::kNullObj;

	MObject objSkinCluster = itDep.thisNode (&status);
	if (!status)
	{
		std::cerr << "Cannot query Dag iterator node: " << status.errorString ().asChar () << "\n";
		return MObject::kNullObj;
	}

	assert (objSkinCluster.hasFn(MFn::kSkinClusterFilter));

	return objSkinCluster;
}



//////////////////////////////////////////////////////////////////////////
// Finds the object connected to the given object via the given attribute
// returns kNullObj if attribute not found or error
MObject findConnected (const MObject& obj, const MString& strAttribute, bool bIncomingConnection)
{
	MStatus status;
	MFnDependencyNode fnNode (obj, &status);
	if (!status)
		return MObject::kNullObj;

	MPlug plug = fnNode.findPlug (strAttribute, &status);
	if (!status)
		return MObject::kNullObj;

	MPlugArray arrInPlugs;
	bool bConnected = plug.connectedTo (arrInPlugs, bIncomingConnection, !bIncomingConnection, &status);
	if (!bConnected || !status || arrInPlugs.length () <= 0)
		return MObject::kNullObj;

	if (arrInPlugs.length () > 1)
		Log ("*WARNING* for the object %s, there are %d %s connections to attribute %s (ignored)",
			fnNode.name().asChar (),
			arrInPlugs.length (),
			bIncomingConnection?"INcoming":"OUTgoing",
			strAttribute.asChar ());

  MObject objResult = arrInPlugs[0].node (&status);
	if (!status)
	{
		std::cerr << "Cannot retrieve information from a plug: " << status.errorString ().asChar () << '\n';
		std::cerr.flush();
		return MObject::kNullObj;
	}

	return objResult;
}

// reduces the given path to transform path
extern MDagPath reduceToTransform (const MDagPath& pathShape)
{
	MDagPath pathTM = pathShape;
	while (!pathTM.hasFn(MFn::kTransform) && (pathTM.pathCount() > 0 || pathTM.length() > 0) )
		if (!pathTM.pop())
			return pathShape;
	if (pathTM.pathCount()==0 && pathTM.length()==0)
		return pathShape; // failed
	else
		return pathTM;
}


//////////////////////////////////////////////////////////////////////////
// comparison operator for MDagPath ordering (e.g. in sets and maps)
bool MDagPathOrder::operator () (const MDagPath& left, const MDagPath& right) const
{
	return strcmp(left.fullPathName().asChar(), right.fullPathName().asChar()) < 0;
}



//////////////////////////////////////////////////////////////////////////
// returns the verbose string corresponding to the given rotation order constant
const char* getRotOrdStr (MTransformationMatrix::RotationOrder nRotOrd)
{
	const char* szRotOrd[] = {
		"Invalid",
		"XYZ",
		"YZX",
		"ZXY",
		"XZY",
		"YXZ",
		"ZYX",
		"Last"
	};

  if (nRotOrd >= 0 && nRotOrd < sizeof(szRotOrd)/sizeof(szRotOrd[0]))
		return szRotOrd[nRotOrd];
	else
		return "#Unknown#";
}


// Finds out if the given node has a flipped transformation (negative parity)
// return true if the node parity is positive (right-handed coordinate system) and false otherwise
bool getNodeParity (const MDagPath& pathNode)
{
	MStatus status;
//	MObject objTransform = pathNode.transform(&status);
//	if (!status)
//		return false;
//
//	MFnDagNode fnTransform (objTransform, &status);
//	if (!status)
//	{
//		Log ("*WARNING* could not retrieve the lowest transfrom MFnTransform from node %s: %s", pathNode.fullPathName().asChar(), status.errorString().asChar());
//		return false;
//	}

	MMatrix mxTM
		= pathNode.inclusiveMatrix(&status);
		//= fnTransform.transformationMatrix(&status);
	if (!status)
	{
		Log ("*WARNING* could not retrieve the transformation matrix from node %s: %s", pathNode.fullPathName().asChar(), status.errorString().asChar());
		return false;
	}

	return (mxTM.det3x3() >= 0);
}



//////////////////////////////////////////////////////////////////////////
// finds the influence objects (bones) influencing the given shape
// does not affect the given array if no bones were found;
// throws a MStatus in the case of a fatal error
void findShapeInfluences (const MObject& objShape, MDagPathArray& arrBones)
{
	arrBones.clear();

	MObject objSkinCluster = findSkinCluster (objShape, MItDependencyGraph::kUpstream);
	if (objSkinCluster.isNull ())
		return;

	MStatus status;
	MFnSkinCluster fnSkinCluster (objSkinCluster,  &status);
	if (!status)
		return; // failed to attach skin cluster to the function set

	fnSkinCluster.influenceObjects (arrBones, &status);
	if (!status)
		throw status;
}


CMayaAutoAnimTempState::CMayaAutoAnimTempState()
{
	m_bAutoKeyMode = MAnimControl::autoKeyMode ();
	//MAnimControl::setAutoKeyMode (false);
	/*
	if (m_bAutoKeyMode)
	{
		std::cerr << "Cry Exporter Warning: AutoKeyMode is on. The exporter needs to reset bone positions to rest pose.\n";
		std::cerr << "Due to certain problems with Rest Position related methods in Maya 4.x, the Exporter can not temporarily turn the auto key mode off.\n";
		std::cerr << "Extra keys at the current frame (" << MAnimControl::currentTime ().as (MTime::uiUnit ()) << ") may be created for the skeleton joints.\n";
	}
	*/
}

CMayaAutoAnimTempState::~CMayaAutoAnimTempState()
{
	//MAnimControl::setAutoKeyMode (m_bAutoKeyMode);
}

// inserts the given object into the set, returns the position in the set
unsigned MObjectSet::insert (const MObject& obj)
{
	// first find if the object is already in the set
	for (unsigned i = 0; i < size(); ++i)
		if (obj == (*this)[i])
			return i;
	
	MStatus status = MObjectArray::append (obj);
	if (!status)
		throw status;

	return size()-1;
}

// erases the object from the set, returns true if the object was found and erased
bool MObjectSet::erase (const MObject& obj)
{
	bool bResult = false;
	// first find if the object is already in the set
	for (unsigned i = 0; i < size(); ++i)
		if (obj == (*this)[i])
		{
			MStatus status = MObjectArray::remove (i);
			if (!status)
				throw status;
			bResult = true;
		}
	return bResult;
}

// returns the size of the set
unsigned MObjectSet::size()const
{
	return MObjectArray::length();
}

// returns the i-th element of the set
MObject& MObjectSet::operator [] (unsigned i)
{
	return (*static_cast<MObjectArray*>(this))[i];
}

const MObject& MObjectSet::operator [] (unsigned i)const
{
	return (*static_cast<const MObjectArray*>(this))[i];
}

// returns true if the size() == 0
bool MObjectSet::empty()const
{
	return size() == 0;
}

// escapes the given string so that, when passed to MEL, it is un-escaped and converted into the original string
// E.g.: C:\my-file.cgf is converted into C:\\my-file.cgf
MString escapeMString (const MString& strOriginal)
{
	MString strResult;
	for (unsigned i = 0; i < strOriginal.length(); ++i)
	{
		char c[2] = {strOriginal.asChar()[i], '\0'};
		switch (c[0])
		{
		case '\n':
			strResult += "\\n";
		case '\r':
			strResult += "\\r";
		case '\\':
			strResult += "\\\\";
		default:
			strResult += c;
		}
	}
	return strResult;
}

// makes the text list out of the given set of objects
std::string toString (const MDagPathSet& setPaths)
{
	std::string strExportShapes;
	for (MDagPathSet::const_iterator itShape = setPaths.begin(); itShape != setPaths.end(); ++itShape)
	{
		if (itShape != setPaths.begin ())
			strExportShapes += ", ";
		strExportShapes += itShape->partialPathName ().asChar ();
	}

	return strExportShapes;
}

// for the given joint, returns the root of the skeleton it belongs to
MDagPath getHierarchyRoot (const MDagPath& pathJoint)
{
	MDagPath pathResult = pathJoint;
	MDagPath pathTemp = pathJoint;
	while (pathTemp.pop())
	{
		if (pathTemp.node().hasFn(MFn::kTransform))
			pathResult = pathTemp;
	}
	return pathTemp;
}


// finds the lowest common parent in the DAG for all the given paths
MDagPath getCommonParent (const MDagPathSet& setPathResult)
{
	MDagPath pathResult;
	Log0("getCommonParent(%s)", toString(setPathResult).c_str());
	for (MDagPathSet::const_iterator it = setPathResult.begin(); it != setPathResult.end(); ++it)
	{
		if (it == setPathResult.begin())
		{
			pathResult = *it;
			continue;
		}
		
		if (pathResult == *it)
			continue;

		MFnDagNode fnNode (*it);
		if (fnNode.isChildOf(pathResult.node()))
			continue;

		if (fnNode.isParentOf(pathResult.node()))
		{
			pathResult = *it;
			continue;
		}

		MDagPath pathResultPrev = pathResult;
		// they both are from different subtrees - find the common parent
		while (pathResult.pop() && !fnNode.isChildOf(pathResult.node()))
			continue;

		if (!pathResult.isValid())
		{
			Log("*WARNING* getCommonParent: Couldn't reach the world matrix");
			break;
		}
		Log0 ("Common parent for %s and %s is %s", pathResultPrev.partialPathName().asChar(), it->partialPathName().asChar(), pathResult.partialPathName().asChar());
	}
	while (!pathResult.hasFn(MFn::kTransform))
		if (!pathResult.pop())
			break;

	return pathResult;
}
