#include <maya/MGlobal.h>
#include <maya/MString.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MFloatMatrix.h>
#include <maya/MTime.h>
#include <maya/MArgList.h>
#include <maya/MPlug.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnCamera.h>
#include <maya/MDagPath.h>
#include <maya/MItDag.h>
#include <maya/MSelectionList.h>
#include <maya/MPlug.h>
#include <maya/MPlugArray.h>
#include <maya/MAnimControl.h>
#include <maya/MPxCommand.h>
#include <maya/MComputation.h>
#include <maya/MFnPlugin.h>
#define mCommandName "animInfo"                         // Command name
#define INDENT_STEP  2                                          // Indentation steps
#define MAX_NB_PLUGS 100                                        // Max number of plugs
#define MAX_NB_CAM   32                                         // Max number of camera
#define FILE_TYPE    "Maya per frame data"      // File type
#define VERSION_ID   0                                          // Version number
static char * stIndentStr = "                                                ";
class animInfoCmd : public MPxCommand
{
public:
                                 animInfoCmd();
    virtual             ~animInfoCmd();
    virtual MStatus doIt ( const MArgList& args );
    static void* creator();
private:
        static MStatus nodeFromName(MString name, MObject & obj);
        const char * indent() const {return stIndentStr+strlen(stIndentStr)-fIndent;}
        MStatus parseArgs ( const MArgList& args );
        void readSceneStartEnd();
        MStatus writeFrameInfo( double frame );
        MStatus writeCameraInfo( const MFnCamera & cam );
        double          fStartFrame;
        double          fEndFrame;
        double          fByFrame;
        int                     fIndent;
        FILE *          fOutput;
        int                     fNbCameraPath;
        MDagPath        fCameraPath[MAX_NB_CAM];
        
        
        int                     fNbPlugs;
        MPlug           fPlugs[MAX_NB_PLUGS];
};
animInfoCmd::animInfoCmd() :
        fStartFrame(-1),
        fEndFrame  (-2),
        fByFrame   ( 1),
        fIndent    (INDENT_STEP),
        fOutput    (stdout),
        fNbCameraPath(0),
        fNbPlugs   (0)
{}
animInfoCmd::~animInfoCmd() {}
MStatus animInfoCmd::nodeFromName(MString name, MObject & obj)
{
        MSelectionList tempList;
    tempList.add( name );
    if ( tempList.length() > 0 ) 
        {
                tempList.getDependNode( 0, obj );
                return MS::kSuccess;
        }
        return MS::kFailure;
}
#define MATCH(str, shortName, longName) \
                (((str)==(shortName))||((str)==(longName)))
static int stringArg(const MArgList& args, unsigned int &indx, MString & res)
{
        if (indx < args.length())
        {
                MStatus stat;
                indx++;
                res = args.asString( indx, &stat );
                if (stat == MS::kSuccess) 
                        return 1;
        }
        return 0;
}
static int doubleArg(const MArgList& args, unsigned int &indx, double & res)
{
        if (indx < args.length())
        {
                MStatus stat;
                indx++;
                res = args.asDouble( indx, &stat );
                if (stat == MS::kSuccess) 
                        return 1;
        }
        return 0;
}
MStatus animInfoCmd::parseArgs( const MArgList& args )
{
        
        MString arg;
        MStatus stat = MS::kSuccess;
        MString str;
        MObject cameraNode;
        unsigned int i;
        for ( i = 0; i < args.length(); i++ ) {
                arg = args.asString( i, &stat );
                if (stat != MS::kSuccess)
                        continue;
                if (MATCH(arg, "-on", "-outputName") && stringArg(args, i, str))
                {
                        FILE *f;
                        if ( NULL != (f = fopen(str.asChar(), "w")))
                                fOutput = f;
                        else 
                        {
                                fprintf(stderr, "cannot open '%s'\n", str.asChar());
                                fflush(stderr);
                        }
                }
                else if (MATCH(arg, "-s", "-start"))
                        doubleArg(args, i, fStartFrame);
                else if (MATCH(arg, "-e", "-end"))
                        doubleArg(args, i, fEndFrame);
                else if (MATCH(arg, "-by", "-byFrame"))
                        doubleArg(args, i, fByFrame);
                else if (MATCH(arg, "-cam", "-camera") && stringArg(args, i, str))
                {
                        if (fNbCameraPath >= MAX_NB_CAM)
                                break;
                        nodeFromName(str, cameraNode);
                        MStatus status;
                        MDagPath::getAPathTo(cameraNode, fCameraPath[fNbCameraPath]);
                        fCameraPath[fNbCameraPath].extendToShape();
                        MFnCamera cam(fCameraPath[fNbCameraPath], &status);
                        if (status == MS::kSuccess)
                                fNbCameraPath++;
                        
                }
                else if (MATCH(arg, "-p", "-plug") && stringArg(args, i, str))
                {
                        MSelectionList plugList;
                        plugList.add( str );
                        
                        for (unsigned int k = 0 ; k<plugList.length() ; k++)
                        {
                                if (fNbPlugs >= MAX_NB_PLUGS)
                                        break;
                                if (plugList.getPlug(k, fPlugs[fNbPlugs]) == MS::kSuccess)
                                        fNbPlugs++;
                        }
                }
                else
                {
                        fprintf(stderr, "Unknown argument '%s'\n", arg.asChar());
                        fflush(stderr);
                }
        }
        if (fByFrame<=0) fByFrame = 1;
        if (fNbCameraPath == 0)
        {
                
                MItDag dagIterator( MItDag::kDepthFirst, MFn::kCamera);
        for (; !dagIterator.isDone(); dagIterator.next())
        {
                        if (fNbCameraPath >= MAX_NB_CAM)
                                break;
                bool            renderable;
                if ( !dagIterator.getPath(fCameraPath[fNbCameraPath]) )
                        continue;
        
                renderable = false;
                MFnCamera fnCameraNode( fCameraPath[fNbCameraPath] );
                fnCameraNode.findPlug( "renderable" ).getValue( renderable );
        
                if (renderable)
                                fNbCameraPath++;
        }
        }
        return MS::kSuccess;
}
void animInfoCmd::readSceneStartEnd()
{
        MTime startFrame;
        MTime endFrame;
    
    
        int rangeIsSet = 0;
        MObject renderGlobNode;
        if (nodeFromName("defaultRenderGlobals", renderGlobNode) == MS::kSuccess)
        {
                MFnDependencyNode fnRenderGlobals( renderGlobNode );
                
                
                
                MPlug animPlug = fnRenderGlobals.findPlug( "animation" );
                short anim;
                animPlug.getValue( anim );
                if ( anim ) {
                        float byFrame;
                        fnRenderGlobals.findPlug( "startFrame"  ).getValue(startFrame);
                        fnRenderGlobals.findPlug( "endFrame"    ).getValue(endFrame);
                        fnRenderGlobals.findPlug( "byFrameStep" ).getValue(byFrame);
                        fByFrame = (double) byFrame;
                        rangeIsSet = 1;
                }
        }
        if (!rangeIsSet)        {
                
                startFrame = MAnimControl::minTime();
                endFrame   = MAnimControl::maxTime();
                fByFrame = 1;
        }
        fStartFrame = (int) startFrame.as( MTime::uiUnit() );
        fEndFrame   = (int)   endFrame.as( MTime::uiUnit() );
}
MStatus animInfoCmd::writeFrameInfo( double frame )
{
        int i;
        MStatus status;
        MGlobal::viewFrame (frame);                             
        fprintf(fOutput, "%s<TIME VAL=%g>\n", indent(), frame);
        fIndent += INDENT_STEP;
        for (i = 0 ; i<fNbCameraPath ; i++)
        {
                MFnCamera cam(fCameraPath[i], &status);
                if (status == MS::kSuccess)
                        writeCameraInfo(cam);
        }
        double val;
        for (i = 0 ; i<fNbPlugs ; i++)
        {
                if (fPlugs[i].getValue(val) == MS::kSuccess)
                        fprintf(fOutput, "%s<PARAM NAME=\"%s\" VAL=%lg>\n", 
                                        indent(), fPlugs[i].name().asChar(), val);
        }
        fIndent -= INDENT_STEP;
        fprintf(fOutput, "%s</TIME>\n", indent());
        return MS::kSuccess;
}
MStatus animInfoCmd::writeCameraInfo( const MFnCamera & cam )
{
        MStatus status;
        fprintf(fOutput, "%s<CAMERA NAME=\"%s\">\n", 
                        indent(), cam.name().asChar());
        fIndent += INDENT_STEP;
        MPoint pt = cam.eyePoint(MSpace::kWorld, &status);
        fprintf(fOutput, "%s<EYE X=%g Y=%g Z=%g>\n", indent(), pt.x, pt.y, pt.z);
        MVector v = cam.viewDirection(MSpace::kWorld);
        fprintf(fOutput, "%s<DIR X=%g Y=%g Z=%g>\n", indent(), v.x, v.y, v.z);
        v = cam.upDirection(MSpace::kWorld);
        fprintf(fOutput, "%s<UP X=%g Y=%g Z=%g>\n", indent(), v.x, v.y, v.z);
        v = cam.rightDirection(MSpace::kWorld);
        fprintf(fOutput, "%s<RIGHT X=%g Y=%g Z=%g>\n", indent(), v.x, v.y, v.z);
        pt = cam.centerOfInterestPoint(MSpace::kWorld, &status);
        if (status == MS::kSuccess)
                fprintf(fOutput, "%s<COI X=%g Y=%g Z=%g>\n", indent(), pt.x,pt.y,pt.z);
        else
                fprintf(fOutput, "%s<COI NONE>\n", indent());
        fprintf(fOutput, "%s<ASPECTRATIO VAL=%g>\n", indent(), cam.aspectRatio());
        fprintf(fOutput, "%s<FSTOP VAL=%g>\n", indent(), cam.fStop());
        fprintf(fOutput, "%s<FOCALLENGTH VAL=%g>\n", indent(), cam.focalLength());
        fprintf(fOutput, "%s<FOCUS VAL=%g NEAR=%g FAR=%g>\n", indent(),
                        cam.focusDistance(), 
                        cam.nearFocusDistance(), cam.farFocusDistance());
        MFloatMatrix mat = cam.projectionMatrix();
        fprintf(fOutput, "%s<MAT A00=%g A01=%g A02=%g A03=%g\n", indent(),
                        mat(0,0), mat(0,1), mat(0,2), mat(0,3));
        fprintf(fOutput, "%s     A10=%g A11=%g A12=%g A13=%g\n", indent(),
                        mat(1,0), mat(1,1), mat(1,2), mat(1,3));
        fprintf(fOutput, "%s     A20=%g A21=%g A22=%g A23=%g\n", indent(),
                        mat(2,0), mat(2,1), mat(2,2), mat(2,3));
        fprintf(fOutput, "%s     A30=%g A31=%g A32=%g A33=%g>\n", indent(),
                        mat(3,0), mat(3,1), mat(3,2), mat(3,3));
        fIndent -= INDENT_STEP;
        fprintf(fOutput, "%s</CAMERA>\n", indent());
        return MS::kSuccess;
}
MStatus animInfoCmd::doIt( const MArgList& args )
{
        
        
        MStatus stat = parseArgs(args);
        if (stat != MS::kSuccess) {
                if (fOutput != stdout) 
                        fclose(fOutput);
                return stat;
        }
        if (fStartFrame > fEndFrame)
                readSceneStartEnd();
        
        MTime currentFrame = MAnimControl::currentTime();
        fprintf(fOutput, "<FILE TYPE=\"%s\" VERSION=%d>\n", FILE_TYPE, VERSION_ID);
        fprintf(fOutput, "\n%s<SEQUENCE START=%g END=%g STEP=%g>\n", 
                        indent(), fStartFrame, fEndFrame, fByFrame);
        fIndent += INDENT_STEP;
        MComputation computation;
        computation.beginComputation();
        for (double frame = fStartFrame ; frame <= fEndFrame ; frame += fByFrame)
        {
                if (computation.isInterruptRequested())
                {
                        fprintf(fOutput, "%s<INTERRUPTION>\n", indent());
                        break ;
                }
                writeFrameInfo(frame);
        }
        computation.endComputation();
        fIndent -= INDENT_STEP;
        fprintf(fOutput, "%s</SEQUENCE>\n", indent());
        fflush(fOutput);
        if (fOutput != stdout) 
                fclose(fOutput);
        
        MGlobal::viewFrame (currentFrame);
        return MS::kSuccess;
}
void * animInfoCmd::creator() { return new animInfoCmd(); }
MStatus initializePlugin( MObject obj )
{
    MFnPlugin plugin( obj, PLUGIN_COMPANY, "4.0", "Any");
    MStatus status = plugin.registerCommand(mCommandName,
                                                                                        animInfoCmd::creator );
        if (!status) status.perror("registerCommand");
    return status;
}
MStatus uninitializePlugin( MObject obj )
{
    MFnPlugin plugin( obj );
    MStatus status = plugin.deregisterCommand(mCommandName);
        if (!status) status.perror("deregisterCommand");
    return status;
}