#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
#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;  
        MStatus         action( int flag );     
};
moveCmd::moveCmd( )
{
        setCommandString( MOVENAME );
}
moveCmd::~moveCmd()
{}
void* moveCmd::creator()
{
        return new moveCmd;
}
bool moveCmd::isUndoable() const
{
        return true;
}
void moveCmd::setVector( double x, double y, double z)
{
        delta.x = x;
        delta.y = y;
        delta.z = z;
}
MStatus moveCmd::finalize()
{
    MArgList command;
    command.addArg( commandString() );
    command.addArg( delta.x );
    command.addArg( delta.y );
    command.addArg( delta.z );
        
        
        
    return MPxToolCommand::doFinalize( command );
}
MStatus moveCmd::doIt( const MArgList& args )
{
        MStatus stat;
        MVector vector( 1.0, 0.0, 0.0 );        
        unsigned i = 0;
        switch ( args.length() )         
        {
                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( )
{
        return action( UNDOIT );
}
MStatus moveCmd::redoIt( )
{
        return action( REDOIT );
}
MStatus moveCmd::action( int flag )
{
        MStatus stat;
        MVector vector = delta;
        switch( flag )
        {
                case UNDOIT:    
                        vector.x = -vector.x;
                        vector.y = -vector.y;
                        vector.z = -vector.z;
                        break;
                case REDOIT:    
                        break;
                case DOIT:              
                        break;
                default:
                        break;
        }
        
        
        MSelectionList slist;
        MGlobal::getActiveSelectionList( slist );
        MItSelectionList iter( slist, MFn::kInvalid, &stat );
        if ( MS::kSuccess == stat ) {
                MDagPath        mdagPath;               
                MObject         mComponent;             
                MSpace::Space spc = MSpace::kWorld;
                
                
                for ( ; !iter.isDone(); iter.next() ) 
                {
                        
                        
                        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();
                        }
                } 
        }
        else {
                cerr << "Error creating selection list iterator" << endl;
        }
        return MS::kSuccess;
}
#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 );
        
        
        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 ( !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 );
                
                
                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;
                }
                
                
                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 ( !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();
                
                
                
                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 );
                
                
                
                
                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 &  )
{
        MString str( MOVEHELPSTR );
    return setHelpString( str );
}
#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;
}
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;
}