//-
// ==========================================================================
// 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.
// ==========================================================================
//+

////////////////////////////////////////////////////////////////////////
// 
// moveTool.cc
// 
// Description:
//    Interactive tool for moving objects and components.
//
//    This plug-in will register the following two commands in Maya:
//       moveToolCmd <x> <y> <z>
//       moveToolContext
// 
////////////////////////////////////////////////////////////////////////
#include <maya/MIOStream.h>
#include <stdio.h>
#include <stdlib.h>

#include <maya/MPxToolCommand.h>
#include <maya/MFnPlugin.h>
#include <maya/MArgList.h>
#include <maya/MGlobal.h>
#include <maya/MItSelectionList.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MDagPath.h>

#include <maya/MFnTransform.h>
#include <maya/MItCurveCV.h>
#include <maya/MItSurfaceCV.h>
#include <maya/MItMeshVertex.h>

#include <maya/MPxSelectionContext.h>
#include <maya/MPxContextCommand.h>
#include <maya/M3dView.h>
#include <maya/MFnCamera.h>

#define CHECKRESULT(stat,msg)     \
	if ( MS::kSuccess != stat ) { \
		cerr << msg << endl;      \
	}

#define kVectorEpsilon 1.0e-3

/////////////////////////////////////////////////////////////
//
// The move command
//
// - this is a tool command which can be used in tool
//   contexts or in the MEL command window.
//
/////////////////////////////////////////////////////////////
#define		MOVENAME	"moveToolCmd"
#define		DOIT		0
#define		UNDOIT		1
#define		REDOIT		2

class moveCmd : public MPxToolCommand
{
public:
	moveCmd();
	virtual ~moveCmd(); 

	MStatus     doIt( const MArgList& args );
	MStatus     redoIt();
	MStatus     undoIt();
	bool        isUndoable() const;
	MStatus		finalize();
	
public:
	static void* creator();

	void		setVector( double x, double y, double z);
private:
	MVector 	delta;	// the delta vectors
	MStatus 	action( int flag );	// do the work here
};

moveCmd::moveCmd( )
{
	setCommandString( MOVENAME );
}

moveCmd::~moveCmd()
{}

void* moveCmd::creator()
{
	return new moveCmd;
}

bool moveCmd::isUndoable() const
//
// Description
//     Set this command to be undoable.
//
{
	return true;
}

void moveCmd::setVector( double x, double y, double z)
{
	delta.x = x;
	delta.y = y;
	delta.z = z;
}

MStatus moveCmd::finalize()
//
// Description
//     Command is finished, construct a string for the command
//     for journalling.
//
{
    MArgList command;
    command.addArg( commandString() );
    command.addArg( delta.x );
    command.addArg( delta.y );
    command.addArg( delta.z );

	// This call adds the command to the undo queue and sets
	// the journal string for the command.
	//
    return MPxToolCommand::doFinalize( command );
}

MStatus moveCmd::doIt( const MArgList& args )
//
// Description
// 		Test MItSelectionList class
//
{
	MStatus stat;
	MVector	vector( 1.0, 0.0, 0.0 );	// default delta
	unsigned i = 0;

	switch ( args.length() )	 // set arguments to vector
	{
		case 1:
			vector.x = args.asDouble( 0, &stat );
			break;
		case 2:
			vector.x = args.asDouble( 0, &stat );
			vector.y = args.asDouble( 1, &stat );
			break;
		case 3:
			vector = args.asVector(i,3);
			break;
		case 0:
		default:
			break;
	}
	delta = vector;

	return action( DOIT );
}

MStatus moveCmd::undoIt( )
//
// Description
// 		Undo last delta translation
//
{
	return action( UNDOIT );
}

MStatus moveCmd::redoIt( )
//
// Description
// 		Redo last delta translation
//
{
	return action( REDOIT );
}

MStatus moveCmd::action( int flag )
//
// Description
// 		Do the actual work here to move the objects	by vector
//
{
	MStatus stat;
	MVector vector = delta;

	switch( flag )
	{
		case UNDOIT:	// undo
			vector.x = -vector.x;
			vector.y = -vector.y;
			vector.z = -vector.z;
			break;
		case REDOIT:	// redo
			break;
		case DOIT:		// do command
			break;
		default:
			break;
	}

	// Create a selection list iterator
	//
	MSelectionList slist;
 	MGlobal::getActiveSelectionList( slist );
	MItSelectionList iter( slist, MFn::kInvalid, &stat );

	if ( MS::kSuccess == stat ) {
		MDagPath 	mdagPath;		// Item dag path
		MObject 	mComponent;		// Current component
		MSpace::Space spc = MSpace::kWorld;

		// Translate all selected objects
		//
		for ( ; !iter.isDone(); iter.next() ) 
		{
			// Get path and possibly a component
			//
			iter.getDagPath( mdagPath, mComponent );

			MFnTransform transFn( mdagPath, &stat );
			if ( MS::kSuccess == stat ) {
				stat = transFn.translateBy( vector, spc );
				CHECKRESULT(stat,"Error doing translate on transform");
				continue;
			}

			MItCurveCV cvFn( mdagPath, mComponent, &stat );
			if ( MS::kSuccess == stat ) {
				for ( ; !cvFn.isDone(); cvFn.next() ) {
					stat = cvFn.translateBy( vector, spc );
					CHECKRESULT(stat,"Error setting CV");
				}
				cvFn.updateCurve();
			}

			MItSurfaceCV sCvFn( mdagPath, mComponent, true, &stat );
			if ( MS::kSuccess == stat ) {
				for ( ; !sCvFn.isDone(); sCvFn.nextRow() ) {
					for ( ; !sCvFn.isRowDone(); sCvFn.next() ) {
						stat = sCvFn.translateBy( vector, spc );
						CHECKRESULT(stat,"Error setting CV");
					}
				}
				sCvFn.updateSurface();
			}

			MItMeshVertex vtxFn( mdagPath, mComponent, &stat );
			if ( MS::kSuccess == stat ) {
				for ( ; !vtxFn.isDone(); vtxFn.next() ) {
					stat = vtxFn.translateBy( vector, spc );
					CHECKRESULT(stat,"Error setting Vertex");
				}
				vtxFn.updateSurface();
			}
		} // for
	}
	else {
		cerr << "Error creating selection list iterator" << endl;
	}
	return MS::kSuccess;
}


/////////////////////////////////////////////////////////////
//
// The moveTool Context
//
// - tool contexts are custom event handlers. The selection
//   context class defaults to maya's selection mode and
//   allows you to override press/drag/release events.
//
/////////////////////////////////////////////////////////////
#define     MOVEHELPSTR        "drag to move selected object"
#define     MOVETITLESTR       "moveTool"
#define		TOP			0
#define		FRONT		1
#define		SIDE		2
#define		PERSP		3

class moveContext : public MPxSelectionContext
{
public:
    moveContext();
    virtual void    toolOnSetup( MEvent & event );
    virtual MStatus doPress( MEvent & event );
    virtual MStatus doDrag( MEvent & event );
    virtual MStatus doRelease( MEvent & event );
    virtual MStatus doEnterRegion( MEvent & event );

private:
	int currWin;
	MEvent::MouseButtonType downButton;
	M3dView view;
	short startPos_x, endPos_x, start_x, last_x;
	short startPos_y, endPos_y, start_y, last_y;
	moveCmd * cmd;
};


moveContext::moveContext()
{
	MString str( MOVETITLESTR );
    setTitleString( str );

	// Tell the context which XPM to use so the tool can properly
	// be a candidate for the 6th position on the mini-bar.
	setImage("moveTool.xpm", MPxContext::kImage1 );
}

void moveContext::toolOnSetup( MEvent & )
{
	MString str( MOVEHELPSTR );
    setHelpString( str );
}

MStatus moveContext::doPress( MEvent & event )
{
	MStatus stat = MPxSelectionContext::doPress( event );
	MSpace::Space spc = MSpace::kWorld;

	// If we are not in selecting mode (i.e. an object has been selected)
	// then set up for the translation.
	//
	if ( !isSelecting() ) {
		event.getPosition( startPos_x, startPos_y );
		view = M3dView::active3dView();

		MDagPath camera;
		stat = view.getCamera( camera );
		if ( stat != MS::kSuccess ) {
			cerr << "Error: M3dView::getCamera" << endl;
			return stat;
		}
		MFnCamera fnCamera( camera );
		MVector upDir = fnCamera.upDirection( spc );
		MVector rightDir = fnCamera.rightDirection( spc );

		// Determine the camera used in the current view
		//
		if ( fnCamera.isOrtho() ) {
			if ( upDir.isEquivalent(MVector::zNegAxis,kVectorEpsilon) ) {
				currWin = TOP;
			} else if ( rightDir.isEquivalent(MVector::xAxis,kVectorEpsilon) ) {
				currWin = FRONT;
			} else  {
				currWin = SIDE;
			}
		}
		else {
			currWin = PERSP;
		}

		// Create an instance of the move tool command.
		//
		cmd = (moveCmd*)newToolCommand();

		cmd->setVector( 0.0, 0.0, 0.0 );
	}
	return stat;
}

MStatus moveContext::doDrag( MEvent & event )
{
	MStatus stat;
	stat = MPxSelectionContext::doDrag( event );

	// If we are not in selecting mode (i.e. an object has been selected)
	// then do the translation.
	//
	if ( !isSelecting() ) {
		event.getPosition( endPos_x, endPos_y );
		MPoint endW, startW;
		MVector vec;
		view.viewToWorld( startPos_x, startPos_y, startW, vec );
		view.viewToWorld( endPos_x, endPos_y, endW, vec );
		downButton = event.mouseButton();

		// We reset the the move vector each time a drag event occurs 
		// and then recalculate it based on the start position. 
		//
		cmd->undoIt();

		switch( currWin )
		{
			case TOP:
				switch ( downButton )
				{
					case MEvent::kMiddleMouse :
						cmd->setVector( endW.x - startW.x, 0.0, 0.0 );
						break;
					case MEvent::kLeftMouse :
					default:
						cmd->setVector( endW.x - startW.x, 0.0,
											   endW.z - startW.z );
						break;
				}
				break;	

			case FRONT:
				switch ( downButton )
				{
					case MEvent::kMiddleMouse :
						cmd->setVector( endW.x - startW.x, 0.0, 0.0 );
						break;
					case MEvent::kLeftMouse :
					default:
						cmd->setVector( endW.x - startW.x,
											   endW.y - startW.y, 0.0 );
						break;
				}
				break;	

			case SIDE:
				switch ( downButton )
				{
					case MEvent::kMiddleMouse :
						cmd->setVector( 0.0, 0.0, endW.z - startW.z );
						break;
					case MEvent::kLeftMouse :
					default:
						cmd->setVector( 0.0, endW.y - startW.y,
											   endW.z - startW.z );
						break;
				}
				break;	

			case PERSP:
				break;
		}

		stat = cmd->redoIt();
		view.refresh( true );
	}
	return stat;
}

MStatus moveContext::doRelease( MEvent & event )
{
	MStatus stat = MPxSelectionContext::doRelease( event );
	if ( !isSelecting() ) {
		event.getPosition( endPos_x, endPos_y );

		// Delete the move command if we have moved less then 2 pixels
		// otherwise call finalize to set up the journal and add the
		// command to the undo queue.
		//
		if ( abs(startPos_x - endPos_x) < 2 && abs(startPos_y - endPos_y) < 2 ) {
			delete cmd;
			view.refresh( true );
		}
		else {
			stat = cmd->finalize();
			view.refresh( true );
		}
	}
	return stat;
}

MStatus moveContext::doEnterRegion( MEvent & /*event*/ )
//
// Print the tool description in the help line.
//
{
	MString str( MOVEHELPSTR );
    return setHelpString( str );
}


/////////////////////////////////////////////////////////////
//
// Context creation command
//
//  This is the command that will be used to create instances
//  of our context.
//
/////////////////////////////////////////////////////////////
#define     CREATE_CTX_NAME	"moveToolContext"

class moveContextCommand : public MPxContextCommand
{
public:
    moveContextCommand() {};
    virtual MPxContext * makeObj();

public:
    static void* creator();
};

MPxContext * moveContextCommand::makeObj()
{
    return new moveContext();
}

void * moveContextCommand::creator()
{
    return new moveContextCommand;
}


///////////////////////////////////////////////////////////////////////
//
// The following routines are used to register/unregister
// the commands we are creating within Maya
//
///////////////////////////////////////////////////////////////////////
MStatus initializePlugin( MObject obj )
{
	MStatus		status;
	MFnPlugin	plugin( obj, PLUGIN_COMPANY, "3.0", "Any" );

	status = plugin.registerContextCommand( CREATE_CTX_NAME,
									&moveContextCommand::creator,
									MOVENAME, &moveCmd::creator );
	if (!status) {
		status.perror("registerContextCommand");
		return status;
	}

	return status;
}

MStatus uninitializePlugin( MObject obj )
{
	MStatus		status;
	MFnPlugin	plugin( obj );

	status = plugin.deregisterContextCommand( CREATE_CTX_NAME, MOVENAME );
	if (!status) {
		status.perror("deregisterContextCommand");
		return status;
	}

	return status;
}
