#include <maya/MIOStream.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <maya/MFn.h>
#include <maya/MPxNode.h>
#include <maya/MPxManipContainer.h>
#include <maya/MPxSelectionContext.h>
#include <maya/MPxContextCommand.h>
#include <maya/MModelMessage.h>
#include <maya/MFnPlugin.h>
#include <maya/MGlobal.h>
#include <maya/MItSelectionList.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MDagPath.h>
#include <maya/MManipData.h>
#include <maya/MItDependencyNodes.h>
#include <maya/MItDependencyGraph.h>
#include <maya/MFnNurbsSurface.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnPointOnSurfaceManip.h>
static const double BUMP_SCALE = 0.5;
MStatus findSurfacePlug(const MObject& node, MPlug& plug, MObject& shape) {
        if (!node.hasFn(MFn::kDagNode))
        {
                MGlobal::displayError("Nodes passed to findSurfacePlug must be DAG"
                        " nodes");
                return MS::kFailure;
        }
        MFnDagNode nodeFn(node);
        if (nodeFn.childCount() != 1)
        {
                MGlobal::displayError("DAG node must have 1 child");
                return MS::kFailure;
        }
        shape = nodeFn.child(0);
        if (!shape.hasFn(MFn::kNurbsSurface))
        {
                MGlobal::displayError("Child node is not a nurbs surface");
                shape = MObject::kNullObj;
                return MS::kFailure;
        }
        
        
        MFnDependencyNode shapeNode(shape);
        plug = shapeNode.findPlug("create");
        return MS::kSuccess;
}
class surfaceBumpManip : public MPxManipContainer
{
public:
        surfaceBumpManip();
        virtual ~surfaceBumpManip();
        
        static void * creator();
        static MStatus initialize();
        virtual MStatus createChildren();
        virtual MStatus connectToDependNode(const MObject &node);
        virtual void draw(M3dView &view, 
                                          const MDagPath &path, 
                                          M3dView::DisplayStyle style,
                                          M3dView::DisplayStatus status);
        
        MManipData surfacePointChangedCallback(unsigned index);
public:
        static MTypeId id;
private:
        MDagPath fPointOnSurfaceManip;
        MObject fSurfaceShape;
        
        int saved_u,saved_v;
        
        MPoint savedPoint;
        unsigned dummyPlugIndex;
};
MTypeId surfaceBumpManip::id( 0x80023 );
surfaceBumpManip::surfaceBumpManip() : saved_u(-1),saved_v(-1)
{ 
        
        
}
surfaceBumpManip::~surfaceBumpManip() 
{
}
void *surfaceBumpManip::creator()
{
         return new surfaceBumpManip();
}
MStatus surfaceBumpManip::initialize()
{
        return MPxManipContainer::initialize();
}
MStatus surfaceBumpManip::createChildren()
{
        MStatus stat = MStatus::kSuccess;
        
        
        fPointOnSurfaceManip = addPointOnSurfaceManip("surfaceBumpManip", "point");
        MFnPointOnSurfaceManip pointOnSurfaceManip(fPointOnSurfaceManip);
        pointOnSurfaceManip.setDrawSurface(true);
        pointOnSurfaceManip.setDrawArrows(true);
        pointOnSurfaceManip.setParameters(0.0,0.0);
        
        return stat;
}
MStatus surfaceBumpManip::connectToDependNode(const MObject &node)
{
        MStatus stat;
        MFnPointOnSurfaceManip pointOnSurfaceManip(fPointOnSurfaceManip);
        
        
        
        MPlug surfacePlug;
        findSurfacePlug(node, surfacePlug, fSurfaceShape);
        if (!surfacePlug.isNull())
        {
                MGlobal::displayInfo(MString("Using surface plug: ") + surfacePlug.name());
                stat = pointOnSurfaceManip.connectToSurfacePlug(surfacePlug);
                if (stat != MStatus::kSuccess) {
                        MGlobal::displayError("Could not connect surface plug");
                        return stat;
                }
        }
        else {
                MGlobal::displayError("Error finding surface plug");
                return MS::kFailure;
        }
        
        
        
        
        
        
        MFnDependencyNode nodeFn(node);
        MPlug dummyPlug = nodeFn.findPlug("dummyPlug", &stat);
        if (dummyPlug.isNull())
        {
                MFnNumericAttribute attributeFn;
                MObject attr = attributeFn.create("dummyPlug", "dp", MFnNumericData::k3Double);
                nodeFn.addAttribute(attr, MFnDependencyNode::kLocalDynamicAttr);
                dummyPlug = nodeFn.findPlug("dummyPlug", &stat);
                if (dummyPlug.isNull())
                {
                        MGlobal::displayError("Could not find dummyPlug on the manipulator.");
                        return MS::kFailure;
                }
        }
        
        
        
        
        dummyPlugIndex = addManipToPlugConversionCallback(dummyPlug, 
                (manipToPlugConversionCallback) 
                &surfaceBumpManip::surfacePointChangedCallback);
        
        
        
        
        MFnTransform transform(node);
        MTransformationMatrix matrix = transform.transformation();
        pointOnSurfaceManip.set(matrix);
        finishAddingManips();
        MPxManipContainer::connectToDependNode(node);
        return stat;
}
void surfaceBumpManip::draw(M3dView & view, 
                                         const MDagPath & path, 
                                         M3dView::DisplayStyle style,
                                         M3dView::DisplayStatus status)
{
        
        
        MPxManipContainer::draw(view, path, style, status);
}
MManipData surfaceBumpManip::surfacePointChangedCallback(unsigned index) {
        
        
        MFnNumericData numericData;
        MObject obj = numericData.create( MFnNumericData::k3Double );
        numericData.setData(0.0,0.0,0.0);
        if (index != dummyPlugIndex)
        {
                MGlobal::displayError("Invalid index in surface point changed callback!");
                return obj;
        }
        MFnNurbsSurface nurbsSurface(fSurfaceShape);
        
        
        double u = 0.0;
        double v = 0.0;
        MFnPointOnSurfaceManip pointOnSurfaceManip(fPointOnSurfaceManip);
        pointOnSurfaceManip.getParameters(u,v);
        
        
        
        
        
        int u_int = 0;
        int v_int = 0;
        if (nurbsSurface.formInU() == MFnNurbsSurface::kPeriodic)
        {
                u_int = (int)(floor(u+1.5)) % nurbsSurface.numSpansInU();
                if (u_int < 0)
                        u_int += nurbsSurface.numSpansInU();
        }
        else {
                u_int = (int)(floor(u+1.5));
        }
        if (nurbsSurface.formInV() == MFnNurbsSurface::kPeriodic)
        {
                v_int = (int)(floor(v+1.5)) % nurbsSurface.numSpansInV();
                if (v_int < 0)
                        v_int += nurbsSurface.numSpansInV();
        }
        else {
                v_int = (int)(floor(v+1.5));
        }
        
        
        if (u_int == saved_u && v_int == saved_v)
        {
                return MManipData(obj);
        }
        
        
        if (saved_u == -1)
        {
                nurbsSurface.getCV(u_int,v_int,savedPoint,MSpace::kObject);
                saved_u = u_int;
                saved_v = v_int;
        }
        
        nurbsSurface.setCV(saved_u,saved_v,savedPoint,MSpace::kObject);
        
        nurbsSurface.getCV(u_int,v_int,savedPoint,MSpace::kObject);
        saved_u = u_int;
        saved_v = v_int;
        
        MPoint perturbedPosition = savedPoint + 
                BUMP_SCALE*nurbsSurface.normal((double)u_int,(double)v_int,MSpace::kObject);
        nurbsSurface.setCV(u_int,v_int,perturbedPosition,MSpace::kObject);
        
        return MManipData(obj);
}
class SurfaceBumpContext : public MPxSelectionContext
{
public:
        SurfaceBumpContext();
        virtual void    toolOnSetup(MEvent &event);
        virtual void    toolOffCleanup();
        
        static void updateManipulators(void * data);
private:
        MCallbackId id1;
};
SurfaceBumpContext::SurfaceBumpContext()
{
        MString str("Plugin Surface Bump Manipulator");
        setTitleString(str);
}
void SurfaceBumpContext::toolOnSetup(MEvent &)
{
        MString str("Drag the manipulator around the surface");
        setHelpString(str);
        updateManipulators(this);
        MStatus status;
        id1 = MModelMessage::addCallback(MModelMessage::kActiveListModified,
                                                                         updateManipulators, 
                                                                         this, &status);
        if (!status) {
                MGlobal::displayError("Model addCallback failed");
        }
}
void SurfaceBumpContext::toolOffCleanup()
{
        MStatus status;
        status = MModelMessage::removeCallback(id1);
        if (!status) {
                MGlobal::displayError("Model remove callback failed");
        }
        MPxContext::toolOffCleanup();
}
void SurfaceBumpContext::updateManipulators(void * data)
{
        MStatus stat = MStatus::kSuccess;
        
        SurfaceBumpContext * ctxPtr = (SurfaceBumpContext *) data;
        ctxPtr->deleteManipulators(); 
        MSelectionList list;
        stat = MGlobal::getActiveSelectionList(list);
        MItSelectionList iter(list, MFn::kInvalid, &stat);
        if (MS::kSuccess == stat) {
                for (; !iter.isDone(); iter.next()) {
                        
                        
                        
                        MObject dependNode;
                        iter.getDependNode(dependNode);
                        if (dependNode.isNull() || !dependNode.hasFn(MFn::kDependencyNode))
                        {
                                MGlobal::displayWarning("Object in selection list is not "
                                        "a depend node.");
                                continue;
                        }
                        
                        
                        MString manipName ("surfaceBumpManip");
                        MObject manipObject;
                        surfaceBumpManip* manipulator =
                                (surfaceBumpManip *) surfaceBumpManip::newManipulator(
                                        manipName, 
                                        manipObject);
                        if (NULL != manipulator) {
                                
                                
                                ctxPtr->addManipulator(manipObject);
                                
                                
                                if (!manipulator->connectToDependNode(dependNode))
                                {
                                        MFnDependencyNode dependNodeFn(dependNode);
                                        MGlobal::displayWarning("Error connecting manipulator to"
                                                " object: " + dependNodeFn.name());
                                }
                        } 
                }
        }
}
class surfaceBumpContext : public MPxContextCommand
{
public:
        surfaceBumpContext() {};
        virtual MPxContext * makeObj();
public:
        static void* creator();
};
MPxContext *surfaceBumpContext::makeObj()
{
        return new SurfaceBumpContext();
}
void *surfaceBumpContext::creator()
{
        return new surfaceBumpContext;
}
MStatus initializePlugin(MObject obj)
{
        MStatus status;
        MFnPlugin plugin(obj, PLUGIN_COMPANY, "6.0", "Any");
        status = plugin.registerContextCommand("surfaceBumpContext",
                                                                                   &surfaceBumpContext::creator);
        if (!status) {
                MGlobal::displayError("Error registering surfaceBumpContext command");
                return status;
        }
        status = plugin.registerNode("surfaceBumpManip", surfaceBumpManip::id, 
                                                                 &surfaceBumpManip::creator, &surfaceBumpManip::initialize,
                                                                 MPxNode::kManipContainer);
        if (!status) {
                MGlobal::displayError("Error registering surfaceBumpManip node");
                return status;
        }
        return status;
}
MStatus uninitializePlugin(MObject obj)
{
        MStatus status;
        MFnPlugin plugin(obj);
        status = plugin.deregisterContextCommand("surfaceBumpContext");
        if (!status) {
                MGlobal::displayError("Error deregistering surfaceBumpContext command");
                return status;
        }
        status = plugin.deregisterNode(surfaceBumpManip::id);
        if (!status) {
                MGlobal::displayError("Error deregistering surfaceBumpManip node");
                return status;
        }
        return status;
}