//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

//
// dagMessageCmd.cpp
//
// Description:
//     Sample plug-in that demonstrates how to register/de-register
//     a callback with the MDagMessage class.
//
//     This plug-in will register a new command in maya called
//     "dagMessage" which adds a callback for the all nodes on
//     the active selection list. A message is printed to stdout 
//     whenever a connection is made or broken for those nodes. If
//		nothing is selected, the callback will be for all nodes.
//
//	   dagMessage -help will list the options.
//
#include <maya/MIOStream.h>
#include <maya/MPxCommand.h>
#include <maya/MFnPlugin.h>
#include <maya/MArgList.h>
#include <maya/MArgDatabase.h>
#include <maya/MIntArray.h>
#include <maya/MSelectionList.h>
#include <maya/MGlobal.h>
#include <maya/MPlug.h>
#include <maya/MSyntax.h>
#include <maya/MDagMessage.h>
#include <maya/MDGMessage.h>
#include <maya/MModelMessage.h>
#include <maya/MDagPath.h>
#include <maya/MFnDagNode.h>
#include <maya/MCallbackIdArray.h>
#include <maya/MObjectArray.h>

#define kCmdName					"dagMessage"

#define kAllDagFlag 				"-ad"
#define kAllDagFlagLong				"-allDag"
#define kParentAddedFlag			"-pa"
#define kParentAddedFlagLong		"-parentAdded"
#define kParentRemovedFlag			"-pr"
#define kParentRemovedFlagLong		"-parentRemoved"
#define kChildAddedFlag				"-ca"
#define kChildAddedFlagLong			"-childAdded"
#define kChildRemovedFlag			"-cr"
#define kChildRemovedFlagLong		"-childRemoved"
#define kChildReorderedFlag			"-cro"
#define kChildReorderedFlagLong		"-childReordered"

#define kHelpFlag					"-h"
#define kHelpFlagLong				"-help"

#define CheckErrorContinue(stat, msg)	\
	if (MS::kSuccess != stat) {			\
		displayError(msg);				\
		continue;						\
	}							

#define CheckErrorReturn(stat, msg)		\
	if (MS::kSuccess != stat) {			\
		displayError(msg);				\
		return;							\
	}

// This table will keep track of the registered callbacks
// so they can be removed when the plug-ins is unloaded.
//
MCallbackIdArray callbackIds;

// Node added to model callback.
static void userNodeRemovedCB(MObject& node,void *clientData)
{
	if (! node.isNull()) {
		bool doDisplay = true;

		MStatus status;
		MFnDagNode dagNode(node,&status);
		if ( status.error() ) {
			doDisplay = false;
			MGlobal::displayInfo("Error: failed to get dag node.");
		}

		if ( doDisplay ) {
			MString s = dagNode.name();
			MString info("DAG Model -  Node removed: ");
			info+= s;

			MGlobal::displayInfo(info);	
		}
	}

	// remove the callback
	MCallbackId id = MMessage::currentCallbackId();
	MMessage::removeCallback(id);
}

// Node added to model callback.
static void userNodeAddedCB(MObject& node,void *clientData)
{
	MStatus status;

	if (! node.isNull()) {
		bool doDisplay = true;

		MDagPath path;
		status = MDagPath::getAPathTo(node,path);
		if ( status.error() ) {
			doDisplay = false;
			MGlobal::displayInfo("Error: failed to get dag path to node.");
		}

		if ( doDisplay ) {
			MString s = path.fullPathName();
			MString info("DAG Model -  Node added: ");
			info+= s;

			path.transform(&status);
			if (MS::kInvalidParameter == status) {
				info += "(WORLD)";
			}

			MGlobal::displayInfo(info);	
		}
	}

	// remove the callback
	MCallbackId id = MMessage::currentCallbackId();
	MMessage::removeCallback(id);

	// listen for removal message
	/* MCallbackId id = */ MModelMessage::addNodeRemovedFromModelCallback( node, userNodeRemovedCB, 0, &status );
	if ( status.error() ) {
		MGlobal::displayError("Failed to install node removed from model callback.\n");
		return;
	}
}

// Install a node added callback for the node specified
// by dagPath.
static void installNodeAddedCallback( MDagPath& dagPath )
{
	MStatus status;

	MObject dagNode = dagPath.node();
	if ( dagNode.isNull() )
		return;

	/* MCallbackId id = */ MModelMessage::addNodeAddedToModelCallback( dagNode, userNodeAddedCB, 0, &status );
	if ( status.error() ) {
		MGlobal::displayError("Failed to install node added to model callback.\n");
		return;
	}
}

// Decide if the dag is in the model. Dag paths and names
// may not be setup if the dag has not been added to
// the model.
static bool dagNotInModel( MDagPath& dagPath )
{
	MStatus status;
	MFnDagNode dagFn( dagPath, &status );
	if ( status.error() )
		return false;
	bool inModel = dagFn.inModel( &status );
	if ( status.error() )
		return false;
	return ( inModel == false );
}

void userDAGGenericCB(MDagMessage::DagMessage msg, MDagPath &child,
					  MDagPath &parent, void *)
{	
	MString dagStr("DAG Changed - ");
	switch (msg) {
		case MDagMessage::kParentAdded:
			dagStr += "Parent Added: ";
			break;
		case MDagMessage::kParentRemoved:
			dagStr += "Parent Removed: ";
			break;
		case MDagMessage::kChildAdded:
			dagStr += "Child Added: ";
			break;
		case MDagMessage::kChildRemoved:
			dagStr += "Child Removed: ";
			break;
		case MDagMessage::kChildReordered:
			dagStr += "Child Reordered: ";
			break;
		default:
			dagStr += "Unknown Type: ";
			break;
	}

	dagStr += "child = ";
	dagStr += child.fullPathName();
	dagStr += ", parent = ";
	dagStr += parent.fullPathName();

	// Check to see if the parent is the world object.
	//
	MStatus pStat;
	parent.transform(&pStat);
	if (MS::kInvalidParameter == pStat) {
		dagStr += "(WORLD)";
	}

	// Install callbacks if node is not in the model.
	// Callback is for node added to model.
	bool incomplete = false;
	if ( dagNotInModel( child ) ) {
		installNodeAddedCallback( child );
		incomplete = true;
	}
	if ( dagNotInModel( parent ) ) {
		installNodeAddedCallback( parent);
		incomplete = true;
	}

	// Warn user that dag path info may be
	// incomplete
	if (incomplete)
		dagStr += "\t// May be incomplete!";	

	MGlobal::displayInfo(dagStr);
}


//////////////////////////////////////////////////////////////////////////
//
// Command class declaration
//
//////////////////////////////////////////////////////////////////////////

class dagMessageCmd : public MPxCommand
{
public:
					dagMessageCmd() {};
	virtual			~dagMessageCmd(); 
	MStatus			doIt( const MArgList& args );

	static MSyntax	newSyntax();
	static void*	creator();

private:

	MStatus			addGenericCallback(MDagPath *dagPath, 
									   MDagMessage::DagMessage msg, 
									   MString cbName);	
};

//////////////////////////////////////////////////////////////////////////
//
// Command class implementation
//
//////////////////////////////////////////////////////////////////////////

dagMessageCmd::~dagMessageCmd() {}

void* dagMessageCmd::creator()
{
	return new dagMessageCmd();
}

MSyntax dagMessageCmd::newSyntax()
{
	MSyntax syntax;

	syntax.useSelectionAsDefault(true);
	syntax.setObjectType(MSyntax::kSelectionList);
	syntax.setMinObjects(0);

	syntax.addFlag(kAllDagFlag, kAllDagFlagLong);
	syntax.addFlag(kParentAddedFlag, kParentAddedFlagLong);
	syntax.addFlag(kParentRemovedFlag, kParentRemovedFlagLong);
	syntax.addFlag(kChildAddedFlag, kChildAddedFlagLong);
	syntax.addFlag(kChildRemovedFlag, kChildRemovedFlagLong);
	syntax.addFlag(kChildReorderedFlag, kChildReorderedFlagLong);
	syntax.addFlag(kHelpFlag, kHelpFlagLong);
	return syntax;
}

MStatus dagMessageCmd::addGenericCallback(MDagPath *dagPath, 
										  MDagMessage::DagMessage msg, 
										  MString cbName)
{
	MStatus status = MS::kFailure;

	if (NULL == dagPath) {
		MCallbackId id = MDagMessage::addDagCallback(	msg,
														userDAGGenericCB, 
														NULL, 
														&status);
		if (MS::kSuccess == status) {
			MString info("Adding a callback for");
			info += cbName;
			info += "on all nodes";
			MGlobal::displayInfo(info);			
			callbackIds.append( id );
		} else {
			MString err("Could not add callback to");
			err += dagPath->fullPathName();
			MGlobal::displayError(err);
		}
	} else {
		MCallbackId id = MDagMessage::addDagCallback(*dagPath,
													msg,
													userDAGGenericCB, 
													NULL, 
													&status);
		if (MS::kSuccess == status) {
			MString info("Adding a callback for");
			info += cbName;
			info += "on ";
			info += dagPath->fullPathName();
			MGlobal::displayInfo(info);			
			callbackIds.append( id );
		} else {
			MString err("Could not add callback to");
			err += dagPath->fullPathName();
			MGlobal::displayError(err);
		}
	}

	return status;
}

MStatus dagMessageCmd::doIt( const MArgList& args)
//
// Takes the  nodes that are on the active selection list and adds an
// attriubte changed callback to each one.
//
{
	MStatus 		status;
	MSelectionList 	list;
    MArgDatabase argData(syntax(), args);

	status = argData.getObjects(list);
	if (MS::kSuccess != status) {
		MGlobal::displayError("Error getting objects");
		return status;
	}

	//	Get the flags
	//
	bool allDagUsed = argData.isFlagSet(kAllDagFlag);
	bool parentAddedUsed = argData.isFlagSet(kParentAddedFlag);
	bool parentRemovedUsed = argData.isFlagSet(kParentRemovedFlag);
	bool childAddedUsed = argData.isFlagSet(kChildAddedFlag);
	bool childRemovedUsed = argData.isFlagSet(kChildRemovedFlag);
	bool childReorderedUsed = argData.isFlagSet(kChildReorderedFlag);
	bool helpUsed = argData.isFlagSet(kHelpFlag);

	bool nothingSet = (	!allDagUsed && !parentAddedUsed && 
						!parentRemovedUsed && !childAddedUsed && 
						!childRemovedUsed && !childReorderedUsed && 
						!helpUsed);

	if (nothingSet) {
		MGlobal::displayError("A flag must be used. dagMessage -help for availible flags.");
		return MS::kFailure;
	}

	if (argData.isFlagSet(kHelpFlag)) {
		MGlobal::displayInfo("dagMessage -help");
		MGlobal::displayInfo("\tdagMessage adds a callback to the selected nodes,");
		MGlobal::displayInfo("\tor if no nodes are selected, to all nodes. The callback");
		MGlobal::displayInfo("\tprints a message when called. When the plug-in is unloaded");
		MGlobal::displayInfo("\tthe callbacks are removed.");
		MGlobal::displayInfo("");
		MGlobal::displayInfo("\t-h -help : This message is printed");
		MGlobal::displayInfo("\t-ad -allDag : parent changes and child reorders");
		MGlobal::displayInfo("\t-pa -parentAdded : A parent is added");
		MGlobal::displayInfo("\t-pr -parentRemoved : A parent is removed");
		MGlobal::displayInfo("\t-ca -childAdded : A child is added (only for individual nodes)");
		MGlobal::displayInfo("\t-cr -childRemoved : A child is removed (only for individual nodes)");
		MGlobal::displayInfo("\t-cro -childReordered : A child is reordered");
		MGlobal::displayInfo("");
	}

	unsigned nObjs = list.length();
	if (nObjs == 0) {
		//	Add the callback for all changes of the specified type.
		//
		if (allDagUsed) {
			MCallbackId id = MDagMessage::addAllDagChangesCallback(userDAGGenericCB, NULL, &status);
			if (status) {
				callbackIds.append( id );
				MGlobal::displayInfo("Added a callback for all Dag changes on all nodes.\n");
			} else {
				MGlobal::displayError("Could not add a -allDag callback");
				return status;
			}
		}

		if (parentAddedUsed) {
			status = addGenericCallback(NULL, 
										MDagMessage::kParentAdded,
										MString(" parent added "));
			if (MS::kSuccess != status) {
				return status;
			}
		}

		if (parentRemovedUsed) {
			status = addGenericCallback(NULL, 
										MDagMessage::kParentRemoved,
										MString(" parent removed "));
			if (MS::kSuccess != status) {
				return status;
			}
		}		

		if (childAddedUsed) {
			MGlobal::displayError("-childAdded can only be used when a node is selected");
			status = MS::kFailure;
			return status;
		}

		if (childRemovedUsed) {
			MGlobal::displayError("-childRemoved can only be used when a node is selected");
			status = MS::kFailure;
			return status;
		}	

		if (childReorderedUsed) {
			status = addGenericCallback(NULL, 
										MDagMessage::kChildReordered,
										MString(" child reordered "));
			if (MS::kSuccess != status) {
				return status;
			}
		}	
	} else {
		for (unsigned int i=0; i< nObjs; i++) {
			MDagPath dagPath;
			list.getDagPath(i, dagPath);

			if (!dagPath.isValid()) {
				continue;
			}

			//	Add the callback for all changes of the specified type.
			//
			if (allDagUsed) {
				MCallbackId id = MDagMessage::addAllDagChangesCallback(dagPath, userDAGGenericCB, NULL, &status);
				if (status) {
					callbackIds.append( id );
					MString infoStr("Added a callback for all Dag changes on ");
					infoStr += dagPath.fullPathName();
					MGlobal::displayInfo(infoStr);
				} else {
					MGlobal::displayError("Could not add a -allDag callback");
					return status;
				}
			}

			if (parentAddedUsed) {
				status = addGenericCallback(&dagPath, 
											MDagMessage::kParentAdded,
											MString(" parent added "));
				if (MS::kSuccess != status) {
					return status;
				}
			}

			if (parentRemovedUsed) {
				status = addGenericCallback(&dagPath, 
											MDagMessage::kParentRemoved,
											MString(" parent removed "));
				if (MS::kSuccess != status) {
					return status;
				}
			}		

			if (childAddedUsed) {
				status = addGenericCallback(&dagPath, 
											MDagMessage::kChildAdded,
											MString(" child added "));
				if (MS::kSuccess != status) {
					return status;
				}
			}

			if (childRemovedUsed) {
				status = addGenericCallback(&dagPath, 
											MDagMessage::kChildRemoved,
											MString(" child removed "));
				if (MS::kSuccess != status) {
					return status;
				}
			}	

			if (childReorderedUsed) {
				status = addGenericCallback(&dagPath, 
											MDagMessage::kChildReordered,
											MString(" child reordered "));
				if (MS::kSuccess != status) {
					return status;
				}
			}	
		}
	}
	
	return status;
}

//////////////////////////////////////////////////////////////////////////
//
// Plugin registration
//
//////////////////////////////////////////////////////////////////////////

MStatus initializePlugin( MObject obj )
{
	MFnPlugin plugin( obj );
	return plugin.registerCommand( kCmdName,
									dagMessageCmd::creator,
									dagMessageCmd::newSyntax);
}

MStatus uninitializePlugin( MObject obj)
{
	// Remove callbacks
	//
	for (unsigned int i=0; i<callbackIds.length(); i++ ) {
		MMessage::removeCallback( callbackIds[i] );
	}

	MFnPlugin plugin( obj );
	return plugin.deregisterCommand( kCmdName );
}


