#include "cgfxShaderCommon.h"
#include "cgfxShaderCmd.h"
#include "cgfxShaderNode.h"
#include "cgfxFindImage.h"
#include <maya/MArgDatabase.h>
#include <maya/MCommandResult.h>
#include <maya/MDagPath.h>
#include <maya/MFeedbackLine.h>
#include <maya/MFnDagNode.h>
#include <maya/MGlobal.h>
#include <Cg/cg.h>
#if defined(WIN32) || defined(LINUX)
                #include <GL/gl.h>
#else
                #include <AGL/agl.h>
#endif
#include <GL/glext.h>
#define kMaxTexCoordsFlag           "-mtc"
#define kMaxTexCoordsFlagLong       "-maxTexCoords"
#define kPluginPathFlag             "-pp"
#define kPluginPathFlagLong         "-pluginPath"
#define kFxFlag                                     "-fx"
#define kFxFlagLong                                 "-fxFile"
#define kFxTechniqueFlag                    "-t"
#define kFxTechniqueFlagLong        "-technique"
#define kNameFlag                                   "-n"
#define kNameFlagLong                       "-name"
#define kListTechniquesFlag         "-lt"
#define kListTechniquesFlagLong     "-listTechniques"
#define kListParametersFlag                 "-lp"
#define kListParametersFlagLong     "-listParameters"
#define kParameterFlag                      "-p"
#define kParameterFlagLong                  "-parameter"
#define kTexCoordSourceFlag         "-tcs"
#define kTexCoordSourceFlagLong     "-texCoordSource"
#if MAYA_API_VERSION >= 700
        #define kColorSourceFlag         "-cs"
        #define kColorSourceFlagLong     "-colorSource"
#endif
#define kEmptyUVFlag                "-euv"
#define kEmptyUVFlagLong            "-emptyUV"
#define kEmptyUVShapesFlag          "-eus"
#define kEmptyUVShapesFlagLong      "-emptyUVShapes"
#define kCaseInsensitiveFlag        "-ci"
#define kCaseInsensitiveFlagLong    "-caseInsensitive"
#define kDescriptionFlag            "-des"
#define kDescriptionFlagLong        "-description"
MString cgfxShaderCmd::sPluginPath;    
MStatus
cgfxShaderCmd::doIt( const MArgList& args )
{
        MStatus stat;
        try
        {
                stat = doCmd( args );
        }
        catch ( cgfxShaderCommon::InternalError* e )   
        {
                reportInternalError( __FILE__, (size_t)e );
                stat = MS::kFailure;
        }
        catch ( ... )
        {
                reportInternalError( __FILE__, __LINE__ );
                stat = MS::kFailure;
        }
        return stat;
}                                      
MStatus
cgfxShaderCmd::redoIt()
{
#ifdef KH_DEBUG
        MString ss = "  .. Redo ";
        ss += fArgString;
        ss += "\n";
        ::OutputDebugString( ss.asChar() );
#endif
        MStatus stat;
        try
        {
                
                MObject oNode;
                stat = fNodeSelection.getDependNode( 0, oNode );
                M_CHECK( stat );
                MFnDependencyNode fnNode( oNode, &stat );
                M_CHECK( stat && fnNode.typeId() == cgfxShaderNode::sId );
                cgfxShaderNode* pNode = (cgfxShaderNode*)fnNode.userNode();
                M_CHECK( pNode );
                
                stat = redoCmd( oNode, fnNode, pNode );
        }
        catch ( cgfxShaderCommon::InternalError* e )   
        {
                reportInternalError( __FILE__, (size_t)e );
                stat = MS::kFailure;
        }
        catch ( ... )
        {
                reportInternalError( __FILE__, __LINE__ );
                stat = MS::kFailure;
        }
#ifdef KH_DEBUG
        ss = "  .. redone\n";
        ::OutputDebugString( ss.asChar() );
#endif
        return stat;
}                                      
MStatus
cgfxShaderCmd::undoIt()
{
#ifdef KH_DEBUG
        MString ss = "  .. Undo ";
        ss += fArgString;
        ss += "\n";
        ::OutputDebugString( ss.asChar() );
#endif
        MStatus stat;
        try
        {
                stat = undoCmd();
        }
        catch ( cgfxShaderCommon::InternalError* e )   
        {
                reportInternalError( __FILE__, (size_t)e );
                stat = MS::kFailure;
        }
        catch ( ... )
        {
                reportInternalError( __FILE__, __LINE__ );
                stat = MS::kFailure;
        }
#ifdef KH_DEBUG
        ss = "  .. undone\n";
        ::OutputDebugString( ss.asChar() );
#endif
        return stat;
}                                      
MStatus
cgfxShaderCmd::doCmd(const MArgList& args)
{
        
        
        
#if defined(_WIN32) && defined(CGFX_DEBUG_MEMORY)
        if (tmpFlag == -1)
        {
                tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
                
                
                tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF;
                
                
                tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
                _CrtSetDbgFlag( tmpFlag );
        }
#endif 
        MStatus        status;
        MSelectionList selList;
        MObject        oNode;
        MString        sResult;
        MStringArray   saResult;
        MString        sFeedback;
        MString        sTemp;
        MString        sWho = "cgfxShader";
        status = parseArgs(args, selList);
        if (!status)
        {
                return status;
        }
        
        
        
        
        
        if ( fPluginPath )
        {
                setResult( sPluginPath );
                return MS::kSuccess;
        }
        
        
        
        
        
        
        
        
        
        
        
        
        
        if ( fMaxTexCoords )    
        {
                GLint     mtc = 0;
                M3dView vw = M3dView::active3dView( &status );
                if ( status &&
                        vw.beginGL() )
                {
                        glGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, &mtc );
                        GLint mic = 0;
                        glGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &mic );
                        if (mic < mtc)
                                mtc = mic;
                        if ( mtc < 1 )
                                mtc = 1;
                        else if ( mtc > CGFXSHADERNODE_GL_TEXTURE_MAX )
                                mtc = CGFXSHADERNODE_GL_TEXTURE_MAX;
                        
                        vw.endGL();
                }
                setResult( (int)mtc );
                return MS::kSuccess;
        }
        
        MFnDependencyNode fnNode;
        cgfxShaderNode*   pNode = NULL;
        if ( fIsEdit || fIsQuery )
        {
                
                
                
                
                if (selList.length() != 1)
                {
                        status = MS::kNotFound;
                        return status;
                }
                
                
                
                MStringArray tmpList;
                selList.getSelectionStrings(tmpList);
                fNodeName = tmpList[0];
                if ( fNodeName.length() ) 
                {
                        sWho += " \"";
                        sWho += fNodeName;
                        sWho += "\"";
                }
                status = selList.getDependNode(0, oNode);
                if (!status)
                {
                        return status;
                }
                status = fnNode.setObject( oNode );
                if (!status)
                {
                        status.perror("cgfxShader");
                        return status;
                }
                if (fnNode.typeId() != cgfxShaderNode::sId)
                {
                        status = MS::kInvalidParameter;
                        status.perror("cgfxShader");
                        return status;
                }
                pNode = (cgfxShaderNode*)fnNode.userNode();
                if (!pNode)
                {
                        status = MS::kInvalidParameter;
                        status.perror("cgfxShader");
                        return status;
                }
        }
        
        
        
        
        
        
        
        
        
        
        
        
        if ( fListTechniques )
        {
                setResult( pNode->getTechniqueList() );
                return status;
        }
        
        
        
        
        
        
        
        
        
        
        
        
        
        if ( fListParameters )
        {
                cgfxAttrDefList* list = cgfxAttrDef::attrsFromNode( oNode );
                for ( cgfxAttrDefList::iterator it = list; it; ++it )
                {
                        cgfxAttrDef* aDef = *it;
                        if ( fDescription )
                        {
                                sResult = aDef->fName.length() ? aDef->fName : " ";
                                sResult += "\t";
                                sTemp = aDef->typeName();
                                sResult += sTemp.length() ? sTemp : " ";
                                sResult += "\t";               
                                sResult += aDef->fSemantic.length() ? aDef->fSemantic : " ";
                                sResult += "\t";
                                sResult += aDef->fDescription.length() ? aDef->fDescription : " ";
                                sResult += "\t";               
                                const char* suffix = aDef->getExtraAttrSuffix();
                                sResult += suffix ? suffix : " ";
                        }
                        else
                                sResult = aDef->fName;
                        saResult.append( sResult );
                }
                setResult( saResult );
                return status;
        }
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        if ( fParameterName.length() > 0 )
        {
                cgfxAttrDefList* list = cgfxAttrDef::attrsFromNode( oNode );
                cgfxAttrDefList::iterator it; 
                if ( fCaseInsensitive )
                        it = list->findInsensitive( fParameterName );
                else
                        it = list->find( fParameterName );
                if ( fDescription )
                {
                        if ( it )
                        {
                                cgfxAttrDef* aDef = *it;
                                saResult.append( aDef->fName );
                                saResult.append( aDef->typeName() );
                                saResult.append( aDef->fSemantic );
                                saResult.append( aDef->fDescription );
                                const char* suffix = aDef->getExtraAttrSuffix();
                                saResult.append( suffix ? suffix : "" );
                        }
                        setResult( saResult );
                }
                else
                {
                        if ( it )
                                sResult = (*it)->typeName();
                        setResult( sResult );
                }
                return status;
        }
        
        
        
        
        
        
        
        
        
        
        
        
        if ( fEmptyUV )
        {
                setResult( pNode->getEmptyUVSets() );
                return MS::kSuccess;
        }
        
        
        
        
        
        
        
        if ( fEmptyUVShapes )
        {
                const MObjectArray& oaShapes = pNode->getEmptyUVSetShapes();
                MFnDagNode          fnDagNode;
                MDagPath            dpShape;
                for ( unsigned iShape = 0; iShape < oaShapes.length(); ++iShape )
                {
                        fnDagNode.setObject( oaShapes[ iShape ] );
                        fnDagNode.getPath( dpShape );
                        saResult.append( dpShape.partialPathName() );
                }
                setResult( saResult );
                return MS::kSuccess;
        }
        
        
        
        
        if ( fTexCoordSource )
        {
                setResult( pNode->getTexCoordSource() );
                return MS::kSuccess;
        }
#if MAYA_API_VERSION >= 700
        
        
        
        
        if ( fColorSource )
        {
                setResult( pNode->getColorSource() );
                return MS::kSuccess;
        }
#endif
        
        if ( fIsQuery )
                return MS::kInvalidParameter;
        
        
        
        if (fNewFxFile.length() > 0)
        {
                
                
                const char* errors = 0;
                
                MString file = cgfxFindFile(fNewFxFile);
                pNode->setShaderFxFileChanged( true );
                
                MStringArray fileOptions; 
                cgfxGetFxIncludePath( file, fileOptions );
                const char *opts[_CGFX_PLUGIN_MAX_COMPILER_ARGS_];
                unsigned int numOpts = fileOptions.length();            
                if (numOpts)
                {
                        numOpts = (numOpts > _CGFX_PLUGIN_MAX_COMPILER_ARGS_) ? _CGFX_PLUGIN_MAX_COMPILER_ARGS_ : numOpts;
                        for (unsigned int i=0; i<numOpts; i++)
                        {
                                opts[i] = fileOptions[i].asChar();
                        }
                        opts[numOpts] = NULL;
                }
                fNewEffect = cgCreateEffectFromFile(cgfxShaderNode::sCgContext, file.asChar(), opts);
                if (fNewEffect)
                {           
                        
                        
                        const MGlobal::MMayaState mayaState = MGlobal::mayaState(&status);
                        if ( !status ) return status;
                        if ( mayaState == MGlobal::kBatch ) return MS::kSuccess;
                        fNewFxFile = file;
                        M3dView view = M3dView::active3dView();
                        
                        
                        
                        
                        
                        if (!view.beginGL()) 
                        {
                                MString es = "There is no active view to bind " + sWho + " to.";
                                MGlobal::displayWarning( es );
                                return MS::kSuccess;
                        }
                        view.endGL();
                }
                
                if (fNewEffect)
                {
                        sFeedback = sWho;
                        sFeedback += " loaded effect \"";
                        sFeedback += file;
                        sFeedback += "\"";
                        MGlobal::displayInfo( sFeedback );
                }
                else
                {
                        if ( errors )
                                MGlobal::displayError( errors );
                        sFeedback = sWho;
                        sFeedback += " unable to load effect \"";
                        sFeedback += file.length() ? file : fNewFxFile;
                        sFeedback += "\"";
                        MGlobal::displayError( sFeedback );
                        return MS::kFailure;
                }
        }
        
        
        if (fNewTechnique.length() == 0 && pNode)
                fNewTechnique = pNode->getTechnique();
        
        
        
        fDagMod = new MDGModifier;
        
        if ( !fIsEdit )
        {
                
                oNode = fDagMod->createNode(cgfxShaderNode::sId, &status);
                M_CHECK( status );
                if ( fNodeName.length() > 0 )
                {
                        status = fDagMod->renameNode(oNode, fNodeName);
                        M_CHECK( status );
                }
                status = fnNode.setObject( oNode );
                M_CHECK( status && fnNode.typeId() == cgfxShaderNode::sId );
                pNode = (cgfxShaderNode*)fnNode.userNode();
                M_CHECK( pNode );
                
                
                status = MGlobal::getActiveSelectionList( fOldSelection );
                M_CHECK( status );
        }
        
        
        
        
        
        
        
        
        
        
        
        cgfxAttrDef::updateNode( fNewEffect,              
                pNode,                   
                fDagMod,                 
                fNewAttrDefList,         
                fNewAttributeList );     
        
        status = fNodeSelection.add( oNode );
        M_CHECK( status );
        
        fOldFxFile    = pNode->shaderFxFile();
        fOldTechnique = pNode->getTechnique();
        pNode->getAttributeList( fOldAttributeList );
        fOldEffect = pNode->effect();           
        fOldAttrDefList = pNode->attrDefList(); 
        if ( fOldAttrDefList )
                fOldAttrDefList->addRef();
        
        
        
        
        
        
        
        return redoCmd( oNode, fnNode, pNode );
}                                      
MStatus
cgfxShaderCmd::redoCmd( MObject&           oNode,
                                                                                         MFnDependencyNode& fnNode,
                                                                                         cgfxShaderNode*    pNode )
                                                                                         
                                                                                         
                                                                                         
                                                                                         
                                                                                         
                                                                                         
                                                                                         
                                                                                         
                                                                                         
                                                                                         
                                                                                         
                                                                                         
                                                                                         
{
        MStatus status;
        
        
        
        pNode->setAttrDefList( NULL );
        
        status = fDagMod->doIt();
        M_CHECK( status );
        pNode->setAttributeList(fNewAttributeList);
        pNode->setAttrDefList(fNewAttrDefList);
        pNode->setEffect(fNewEffect);
        cgfxAttrDef::initializeAttributes( oNode, fNewAttrDefList, false, fDagMod);
        fnNode.findPlug( pNode->sShader ).setValue( fNewFxFile );
        fnNode.findPlug( pNode->sTechnique ).setValue( fNewTechnique );
        
        fNewTechnique = pNode->getTechnique();
        if ( !fIsEdit ) 
        {
                
                
                fNodeName = fnNode.name();
                
                
                
                
                
                MSelectionList selList;
                selList.add(oNode);
                MGlobal::setActiveSelectionList(selList);
        }
        return MS::kSuccess;
}                                      
MStatus
cgfxShaderCmd::undoCmd()
{
        MStatus status;
        
        
        MObject oNode;
        status = fNodeSelection.getDependNode(0, oNode);
        M_CHECK( status );
        MFnDependencyNode fnNode( oNode, &status );
        M_CHECK( status && fnNode.typeId() == cgfxShaderNode::sId );
        cgfxShaderNode* pNode = (cgfxShaderNode*)fnNode.userNode();
        M_CHECK( pNode );
        
        
        
        pNode->setAttrDefList( NULL );
        
        
        status = fDagMod->undoIt();
        M_CHECK( status );
        if ( fIsEdit )
        {
                pNode->setEffect( fOldEffect );
                pNode->setAttrDefList( fOldAttrDefList );
                pNode->setAttributeList( fOldAttributeList );
                cgfxAttrDef::initializeAttributes( oNode, fOldAttrDefList, true, fDagMod);
                fnNode.findPlug( pNode->sShader ).setValue( fOldFxFile );
                fnNode.findPlug( pNode->sTechnique ).setValue( fOldTechnique );
        }
        else 
        {
                MGlobal::setActiveSelectionList( fOldSelection );
        }
        return MS::kSuccess;
}                                      
MSyntax cgfxShaderCmd::newSyntax()
{
        MSyntax syntax;
        syntax.enableEdit();
        syntax.enableQuery();
        syntax.addFlag( kPluginPathFlag, kPluginPathFlagLong );
        syntax.addFlag( kMaxTexCoordsFlag, kMaxTexCoordsFlagLong );
        syntax.addFlag(kFxFlag, kFxFlagLong, MSyntax::kString);
        syntax.addFlag(kFxTechniqueFlag, kFxTechniqueFlagLong, MSyntax::kString);
        syntax.addFlag( kListTechniquesFlag, kListTechniquesFlagLong );
        syntax.addFlag(kNameFlag, kNameFlagLong, MSyntax::kString);
        syntax.addFlag(kListParametersFlag, kListParametersFlagLong);
        syntax.addFlag(kParameterFlag, kParameterFlagLong, MSyntax::kString);
        syntax.addFlag( kEmptyUVFlag, kEmptyUVFlagLong );
        syntax.addFlag( kEmptyUVShapesFlag, kEmptyUVShapesFlagLong );
        syntax.addFlag( kTexCoordSourceFlag, kTexCoordSourceFlagLong );
#if MAYA_API_VERSION >= 700
        syntax.addFlag( kColorSourceFlag, kColorSourceFlagLong );
#endif
        syntax.addFlag( kCaseInsensitiveFlag, kCaseInsensitiveFlagLong );
        syntax.addFlag( kDescriptionFlag, kDescriptionFlagLong );
        syntax.setObjectType(MSyntax::kSelectionList, 0, 1);
        
        
        
        
        
        return syntax;
}
void* cgfxShaderCmd::creator()
{
        return new cgfxShaderCmd();
}
cgfxShaderCmd::cgfxShaderCmd()
:   fIsEdit( false )
,   fIsQuery( false )
,   fMaxTexCoords( false )
,   fPluginPath( false )
,   fEmptyUV( false )
,   fEmptyUVShapes( false )
,   fListParameters(false)
,   fListTechniques( false )
,   fTexCoordSource( false )
#if MAYA_API_VERSION >= 700
,   fColorSource( false )
#endif
,   fCaseInsensitive( false )
,   fDescription( false )
,       fOldEffect(0)
,       fOldAttrDefList(0)
,       fNewEffect(0)
,       fNewAttrDefList(0)
,       fDagMod(0)
{  }
cgfxShaderCmd::~cgfxShaderCmd()
{
        try
        {
#ifdef KH_DEBUG
                if ( !fIsQuery )
                {
                        MString ss = "  .. ~cmd ";
                        ss += fArgString;
                        ss += "\n";
                        ::OutputDebugString( ss.asChar() );
                }
#endif
                if (fOldAttrDefList)
                {
                        fOldAttrDefList->release();
                        fOldAttrDefList = 0;
                }
                if (fNewAttrDefList)
                {
                        fNewAttrDefList->release();
                        fNewAttrDefList = 0;
                }
                delete fDagMod;
        }
        catch ( cgfxShaderCommon::InternalError* e )   
        {
                reportInternalError( __FILE__, (size_t)e );
        }
        catch ( ... )
        {
                reportInternalError( __FILE__, __LINE__ );
        }
}
bool cgfxShaderCmd::isUndoable() const
{
        return !fIsQuery;
}
MStatus cgfxShaderCmd::parseArgs(const MArgList& args, MSelectionList& selList)
{
        MStatus         status;
        MString         sMsg;
        selList.clear();
        fArgString.clear();
        for ( unsigned iArg = 0; iArg < args.length(); ++iArg )
        {
                if ( iArg > 0 )
                        fArgString += " ";
                fArgString += args.asString( iArg );
        }
#ifdef KH_DEBUG
        MString ss = "  .. Cmd  ";
        ss += fArgString;
        ss += "\n";
        ::OutputDebugString( ss.asChar() );
#endif
        MArgDatabase argData( syntax(), args, &status );
        if ( !status )
                return status;
        bool bCgfxShaderNodeRequired = true;
        fIsEdit = argData.isEdit();
        fIsQuery = argData.isQuery();
        if ( argData.isFlagSet( kMaxTexCoordsFlag ) )       
        {
                bCgfxShaderNodeRequired = false;
                fMaxTexCoords = true;    
                fIsQuery = true;
        }
        if ( argData.isFlagSet( kPluginPathFlag ) )       
        {
                bCgfxShaderNodeRequired = false;
                fPluginPath = true;    
                fIsQuery = true;
        }
        if ( argData.isFlagSet( kEmptyUVFlag ) )       
        {
                fEmptyUV = true;    
                fIsQuery = true;
        }
        if ( argData.isFlagSet( kEmptyUVShapesFlag ) )       
        {
                fEmptyUVShapes = true;    
                fIsQuery = true;
        }
        if ( argData.isFlagSet( kTexCoordSourceFlag ) )       
        {
                fTexCoordSource = true;    
                fIsQuery = true;
        }
#if MAYA_API_VERSION >= 700
        if ( argData.isFlagSet( kColorSourceFlag ) )       
        {
                fColorSource = true;    
                fIsQuery = true;
        }
#endif
        if (argData.isFlagSet(kFxFlag))
        {
                argData.getFlagArgument(kFxFlag, 0, fNewFxFile);
        }
        if (argData.isFlagSet(kFxTechniqueFlag))
        {
                argData.getFlagArgument( kFxFlag, 0, fNewTechnique );
        }
        if (argData.isFlagSet(kNameFlag))
        {
                argData.getFlagArgument(kNameFlag, 0, fNodeName);
        }
        if (argData.isFlagSet(kListParametersFlag))
        {
                fListParameters = true;
                fIsQuery = true;
        }
        if ( argData.isFlagSet( kListTechniquesFlag ) )
        {
                fListTechniques = true;
                fIsQuery = true;
        }
        if (argData.isFlagSet(kParameterFlag))
        {
                argData.getFlagArgument(kParameterFlag, 0, fParameterName);
                fIsQuery = true;
        }
        if ( argData.isFlagSet( kCaseInsensitiveFlag ) )       
        {
                fCaseInsensitive = true;    
                fIsQuery = true;
        }
        if ( argData.isFlagSet( kDescriptionFlag ) )       
        {
                fDescription = true;    
                fIsQuery = true;
        }
        
        if ( fIsQuery &&
                fIsEdit )
        {
                MString es = "cgfxShader: invalid use of -e/-edit flag";
                MGlobal::displayError( es );
                return MS::kInvalidParameter;
        }
        
        if ( bCgfxShaderNodeRequired )
        {
                argData.getObjects(selList);
                if ( selList.length() == 0 )
                        MGlobal::getActiveSelectionList( selList );
                if ( selList.length() != 1 )
                {
                        sMsg = "Exactly one node must be specified or selected for command:  cgfxShader ";
                        sMsg += fArgString;
                        MGlobal::displayError( sMsg );
                        status = MS::kInvalidParameter;
                }
        }
        return status;
}
void
cgfxShaderCmd::reportInternalError( const char* sFile, size_t errcode )
{
        MString es = "cgfxShader internal error ";
        es += (int)errcode;
        if ( this &&
                fArgString.length() > 0 )
        {
                es += " with args: ";
                es += fArgString;
        }
#ifdef _WINDOWS
        ::OutputDebugString( es.asChar() );
        ::OutputDebugString( "\n" );
#endif
        MGlobal::displayError( es );
}