//-
// ==========================================================================
// 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.
// ==========================================================================
//+

////////////////////////////////////////////////////////////////////////
// 
// moveNumericTool.cc
// 
// Description:
//    Interactive tool for moving an object.
//    This tool also accepts numeric input from the numeric input field.
//
//    This plug-in will register the following two commands in Maya:
//       moveNumericToolCmd <x> <y> <z>
//       moveNumericToolContext
// 
////////////////////////////////////////////////////////////////////////
#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>

#include <maya/MFeedbackLine.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	"moveNumericToolCmd"
#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 );
	static MStatus	getVector( MVector &vec );
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::getVector( MVector &vec )
{
	MStatus stat;
	MSelectionList slist;
 	MGlobal::getActiveSelectionList( slist );

	MDagPath 	mDagPath;
	MObject 	mComponent;
	MSpace::Space spc = MSpace::kWorld;

	// Translate first selected object
	//
	if (slist.length() > 0)
		stat = slist.getDagPath(slist.length()-1, mDagPath, mComponent);
	else
		return MS::kFailure;

	if ( MS::kSuccess != stat ) return MS::kFailure;

	MFnTransform transFn( mDagPath, &stat );
	if ( MS::kSuccess == stat ) {
		vec = transFn.translation( spc, &stat );
	}

	return stat;
}

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 );

	MDagPath 	mdagPath;		// Item dag path
	MObject 	mComponent;		// Current component
	MSpace::Space spc = MSpace::kWorld;

	// Translate first selected objects
	//
	if (slist.length() > 0)
		stat = slist.getDagPath(slist.length()-1, mdagPath, mComponent);
	else
		return MS::kFailure;

	if ( MS::kSuccess != stat ) return MS::kFailure;

	MFnTransform transFn( mdagPath, &stat );
	if ( MS::kSuccess == stat ) {
		stat = transFn.translateBy( vector, spc );
		CHECKRESULT(stat,"Error doing translate on transform");
	}

	return MS::kSuccess;
}


/////////////////////////////////////////////////////////////
//
// The moveNumericTool 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       "moveNumericTool"
#define		TOP			0
#define		FRONT		1
#define		SIDE		2
#define		PERSP		3

class moveNumericContext : public MPxSelectionContext
{
public:
    moveNumericContext();
    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 );

	virtual bool		processNumericalInput( const MDoubleArray &values,
											   const MIntArray &flags,
											   bool isAbsolute );
	virtual bool		feedbackNumericalInput() const;
	virtual MSyntax::MArgType	argTypeNumericalInput( unsigned index ) const;

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;
};


moveNumericContext::moveNumericContext()
{
	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("moveNumericTool.xpm", MPxContext::kImage1 );
}

void moveNumericContext::toolOnSetup( MEvent & )
{
	MString str( MOVEHELPSTR );
    setHelpString( str );
}

MStatus moveNumericContext::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 );
	}
	feedbackNumericalInput();
	return stat;
}

MStatus moveNumericContext::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 );
	}
	feedbackNumericalInput();
	return stat;
}

MStatus moveNumericContext::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 );
		}
	}
	feedbackNumericalInput();
	return stat;
}

MStatus moveNumericContext::doEnterRegion( MEvent & /*event*/ )
//
// Print the tool description in the help line.
//
{
	MString str( MOVEHELPSTR );
    return setHelpString( str );
}


bool moveNumericContext::processNumericalInput ( const MDoubleArray &values,
												 const MIntArray &flags,
												 bool isAbsolute )
{
	unsigned valueLength = values.length();

	cmd = (moveCmd *) newToolCommand();

	MVector vec;
	/* MStatus stat = */ moveCmd::getVector(vec);

	if (isAbsolute) {
		MVector absoluteDelta;

		if (ignoreEntry(flags, 0) || (valueLength < 1)) absoluteDelta.x = 0;
		else absoluteDelta.x = values[0] - vec.x;

		if (ignoreEntry(flags, 1) || (valueLength < 2)) absoluteDelta.y = 0;
		else absoluteDelta.y = values[1] - vec.y;

		if (ignoreEntry(flags, 2) || (valueLength < 3)) absoluteDelta.z = 0;
		else absoluteDelta.z = values[2] - vec.z;

		cmd->setVector(absoluteDelta.x, absoluteDelta.y, absoluteDelta.z);
	}
	else {
		MVector relativeDelta;

		if (ignoreEntry(flags, 0) || (valueLength < 1)) relativeDelta.x = 0;
		else relativeDelta.x = values[0];

		if (ignoreEntry(flags, 1) || (valueLength < 2)) relativeDelta.y = 0;
		else relativeDelta.y = values[1];

		if (ignoreEntry(flags, 2) || (valueLength < 3)) relativeDelta.z = 0;
		else relativeDelta.z = values[2];

		cmd->setVector(relativeDelta.x, relativeDelta.y, relativeDelta.z);
	}

	cmd->redoIt();
	cmd->finalize();

	feedbackNumericalInput();
	return true;
}

bool moveNumericContext::feedbackNumericalInput() const
{
	MFeedbackLine::setTitle("moveNumericTool");
	MFeedbackLine::setFormat("^6.3f ^6.3f ^6.3f");
	MVector vec;
	/* MStatus stat = */ moveCmd::getVector(vec);
	MFeedbackLine::setValue(0, vec.x);
	MFeedbackLine::setValue(1, vec.y);
	MFeedbackLine::setValue(2, vec.z);
	return true;
}

MSyntax::MArgType moveNumericContext::argTypeNumericalInput(unsigned /*index*/) 
const
{
	return MSyntax::kDistance;
}


/////////////////////////////////////////////////////////////
//
// Context creation command
//
//  This is the command that will be used to create instances
//  of our context.
//
/////////////////////////////////////////////////////////////
#define     CREATE_CTX_NAME	"moveNumericToolContext"

class moveNumericContextCommand : public MPxContextCommand
{
public:
    moveNumericContextCommand() {};
    virtual MPxContext * makeObj();

public:
    static void* creator();
};

MPxContext * moveNumericContextCommand::makeObj()
{
    return new moveNumericContext();
}

void * moveNumericContextCommand::creator()
{
    return new moveNumericContextCommand;
}


///////////////////////////////////////////////////////////////////////
//
// 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,
										   &moveNumericContextCommand::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;
}
