#include <maya/MIOStream.h>
#include <math.h>
#include "pnTrianglesNode.h"
#include <maya/MFnPlugin.h>
#include <maya/MString.h>
#include <maya/MPlug.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFloatVector.h>
#include <maya/MGlobal.h>
#include <maya/M3dView.h>
#include <maya/MMaterial.h>
#include <maya/MDrawData.h>
#include <maya/MColor.h>
#include <maya/MDagPath.h>
#include <maya/MPoint.h>
#include <maya/MIOStream.h>
MTypeId     pnTriangles::id( 0x00105448 );
#define         pnDefaultMaxTessellation        0               // Default max tessellation
MObject     pnTriangles::attrSubdivisions;  
MObject         pnTriangles::attrColored;               
MObject         pnTriangles::attrTextured;              
MObject         pnTriangles::attrWireframe;             
MObject         pnTriangles::attrNormalMode;    
MObject         pnTriangles::attrPointMode;             
MObject         pnTriangles::attrEnableVertexProgram; 
MObject         pnTriangles::attrEnablePixelProgram; 
pnTriangles::pnTriangles() 
:       fSubdivisions( 0 ),                                                                     
        fNumTextureCoords( 0 ),                                                         
        fNumNormals( 1 ),                                                                       
        fNumColors( 0 ),                                                                        
        fPointMode( kPointCubic ),                                                      
        fNormalMode( kNormalQuadratic ),                                        
        fMaxTessellationLevel( pnDefaultMaxTessellation ),      
        fWireframe( false ),                                                            
        fInTexturedMode( false ),                                                       
        fTestVertexProgram( false ),
        fTestFragmentProgram( false ),
        fVertexShaderId( -1 ),
        fFragmentShaderId( -1 )
{
        fExtensionSupported[kPNTriangesEXT ] = false;
        fExtensionSupported[kVertexShaderEXT] = false;
        fExtensionSupported[kFragmentShaderEXT] = false;
        
        
        
        fExtensionSupported[kPNTriangesEXT] = initialize_ATI_Extension("GL_ATI_pn_triangles");
        
        
        if (fExtensionSupported[kPNTriangesEXT])
        {
                int val;
                glGetIntegerv( GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI, &val );
                fMaxTessellationLevel = val;
        }
        fExtensionSupported[kVertexShaderEXT] = initialize_ATI_Extension("GL_EXT_vertex_shader");
        fExtensionSupported[kFragmentShaderEXT] = initialize_ATI_Extension("GL_ATI_fragment_shader");
}
pnTriangles::~pnTriangles() 
{
        
        
        if (fExtensionSupported[kVertexShaderEXT])
        {
                if (fVertexShaderId != -1)
                        glDeleteVertexShaderEXT(fVertexShaderId);
        }
}
bool pnTriangles::getInternalValue( const MPlug &plug, MDataHandle &dh ) 
{
    if( plug == pnTriangles::attrSubdivisions ) 
        {
                dh.set( int(fSubdivisions) );
                return true;
    }
        else if ( plug == pnTriangles::attrColored)
        {
                dh.set( (fNumColors > 0) );
                return true;
        }
        else if ( plug == pnTriangles::attrWireframe)
        {
                dh.set( fWireframe > 0);
                return true;
        }
        else if ( plug == pnTriangles::attrTextured)
        {
                dh.set( (fNumTextureCoords > 0) );
                return true;
        }
        else if ( plug == pnTriangles::attrNormalMode )
        {
                if (fNormalMode == kNormalLinear)
                        dh.set( 0 );
                else
                        dh.set( 1 );
                return true;
        }
        else if ( plug == pnTriangles::attrPointMode )
        {
                if (fPointMode == kPointLinear)
                        dh.set( 0 );
                else
                        dh.set( 1 );
                return true;
        }
        else if ( plug == pnTriangles::attrEnableVertexProgram)
        {
                dh.set( fTestVertexProgram );
                return true;
        }
        else if ( plug == pnTriangles::attrEnablePixelProgram)
        {
                dh.set( fTestFragmentProgram );
                return true;
        }
        return false;
}
bool pnTriangles::setInternalValue( const MPlug &plug, const MDataHandle &dh ) 
{
   if( plug == pnTriangles::attrSubdivisions ) 
        {
                fSubdivisions = dh.asInt();
                
                
                if (fSubdivisions > fMaxTessellationLevel)
                        fSubdivisions = fMaxTessellationLevel;
                return true;
    }
        else if ( plug == pnTriangles::attrColored)
        {
                if (dh.asBool() == true)
                        fNumColors = 1;
                else
                        fNumColors = 0;
                return true;
        }
        else if ( plug == pnTriangles::attrWireframe)
        {
                if (dh.asBool() == true)
                        fWireframe = 1;
                else
                        fWireframe = 0;
                return true;
        }
        else if ( plug == pnTriangles::attrTextured)
        {
                if (dh.asBool() == true)
                        fNumTextureCoords = 1;
                else
                        fNumTextureCoords = 0;
                return true;
        }
        else if ( plug == pnTriangles::attrNormalMode )
        {
                if (dh.asInt() == 0)
                        fNormalMode = kNormalLinear;
                else
                        fNormalMode = kNormalQuadratic;
                return true;
        }
        else if ( plug == pnTriangles::attrPointMode )
        {
                if (dh.asInt() == 0)
                        fPointMode = kPointLinear;
                else
                        fPointMode = kPointCubic;
                return true;
        }
        else if ( plug == pnTriangles::attrEnableVertexProgram )
        {
                if (dh.asBool() == true)
                {
                        if (fExtensionSupported[kVertexShaderEXT])
                                fTestVertexProgram = 1;
                        else
                                fTestVertexProgram = 0;
                }
                else
                        fTestVertexProgram = 0;
                return true;
        }
        else if ( plug == pnTriangles::attrEnablePixelProgram )
        {
#ifdef _FRAGMENT_PROGRAM_READY_
                if (dh.asBool() == true)
                        if (fExtensionSupported[kFragmentShaderEXT])
                                fTestFragmentProgram = 1;
                        else
                                fTestFragmentProgram = 0;
                else
                        fTestFragmentProgram = 0;
#else
                fTestFragmentProgram = 0;
#endif
                return true;
        }
    return false;
}
void* pnTriangles::creator()
{
        return new pnTriangles();
}
MStatus pnTriangles::initialize()
{
        MFnNumericAttribute nAttr;
        MStatus                         stat;
        
        attrSubdivisions = nAttr.create( "subdivisions", "sd", MFnNumericData::kInt, 1 );
        nAttr.setStorable(true);
        nAttr.setInternal(true);
        nAttr.setMin(0);
    nAttr.setMax(10);
    nAttr.setDefault(1);
        nAttr.setKeyable(true);
        attrColored = nAttr.create( "colored", "cl", MFnNumericData::kBoolean, 1 );
        nAttr.setStorable(true);
        nAttr.setInternal(true);
    nAttr.setDefault( false );
        nAttr.setKeyable(true);
        attrTextured = nAttr.create( "textured", "tx", MFnNumericData::kBoolean, 1 );
        nAttr.setStorable(true);
        nAttr.setInternal(true);
    nAttr.setDefault( false );
        nAttr.setKeyable(true);
        attrWireframe = nAttr.create( "wireframe", "wf", MFnNumericData::kBoolean, 1 );
        nAttr.setStorable(true);
        nAttr.setInternal(true);
    nAttr.setDefault( false );
        nAttr.setKeyable(true);
        
        
        attrNormalMode = nAttr.create( "quadraticNormals", "qn", MFnNumericData::kInt, 1 );
        nAttr.setStorable(true);
        nAttr.setInternal(true);
        nAttr.setMin(0); 
    nAttr.setMax(1); 
    nAttr.setDefault(1);
        nAttr.setKeyable(true);
        
        attrPointMode= nAttr.create( "cubicPoints", "cp", MFnNumericData::kInt, 1 );
        nAttr.setStorable(true);
        nAttr.setInternal(true);
        nAttr.setMin(0); 
    nAttr.setMax(1); 
    nAttr.setDefault(1);
        nAttr.setKeyable(true);
        
        attrEnableVertexProgram = nAttr.create( "vertexProgram", "vp", MFnNumericData::kBoolean, 1 );
        nAttr.setStorable(true);
        nAttr.setInternal(true);
    nAttr.setDefault( false );
        nAttr.setKeyable(true);
                
        
        attrEnablePixelProgram = nAttr.create( "pixelProgram", "pp", MFnNumericData::kBoolean, 1 );
        nAttr.setStorable(true);
        nAttr.setInternal(true);
    nAttr.setDefault( false );
        nAttr.setKeyable(true);
        
        
        stat = addAttribute( attrSubdivisions );
        if (!stat) { stat.perror("addAttribute"); return stat;}
        stat = addAttribute( attrColored );
        if (!stat) { stat.perror("addAttribute"); return stat;}
        stat = addAttribute( attrWireframe );
        if (!stat) { stat.perror("addAttribute"); return stat;}
        stat = addAttribute( attrTextured );
        if (!stat) { stat.perror("addAttribute"); return stat;}
        stat = addAttribute( attrNormalMode );
        if (!stat) { stat.perror("addAttribute"); return stat;}
        stat = addAttribute( attrPointMode );
        if (!stat) { stat.perror("addAttribute"); return stat;}
        stat = addAttribute( attrEnableVertexProgram );
        if (!stat) { stat.perror("addAttribute"); return stat;}
        stat = addAttribute( attrEnablePixelProgram );
        if (!stat) { stat.perror("addAttribute"); return stat;}
        return MS::kSuccess;
}
MStatus pnTriangles::getFloat3(MObject attr, float value[3])
{
        MStatus status;
        
        
        MPlug   plug(thisMObject(), attr);
        MObject object;
        status = plug.getValue(object);
        if (!status)
        {
                status.perror("pnTrianglesNode::bind plug.getValue.");
                return status;
        }
        MFnNumericData data(object, &status);
        if (!status)
        {
                status.perror("pnTrianglesNode::bind construct data.");
                return status;
        }
        status = data.getData(value[0], value[1], value[2]);
        if (!status)
        {
                status.perror("pnTrianglesNode::bind get values.");
                return status;
        }
        return MS::kSuccess;
}
#define DEG_TO_RAD(_x_) (3.14159265358979323846264338327950F / 180.0F * (_x_))
void pnTriangles::bindVertexProgram(const MColor diffuse, 
                                                                        const MColor specular, 
                                                                        const MColor emission, 
                                                                        const MColor ambient)
{
        
        
        if (!fExtensionSupported[kVertexShaderEXT])
                return;
        
        glEnable(GL_VERTEX_SHADER_EXT);
        
        if (fVertexShaderId != -1)
        {
                glBindVertexShaderEXT (fVertexShaderId); 
                return;
        }
        fVertexShaderId = glGenVertexShadersEXT (1);
        
        GLuint Modelview = glBindParameterEXT (GL_MODELVIEW_MATRIX);
        GLuint Projection = glBindParameterEXT (GL_PROJECTION_MATRIX);
        
        
        
        GLuint Vertex = glBindParameterEXT (GL_CURRENT_VERTEX_EXT);
        
        GLuint Normal = glBindParameterEXT (GL_CURRENT_NORMAL); 
        
        
        
        glBindVertexShaderEXT (fVertexShaderId); 
        glBeginVertexShaderEXT ();
        {
                float direction[4] = { 0.57735f, 0.57735f, 0.57735f, 0.0f}; 
                GLuint lightDirection;
                GLuint diffMaterial;
                GLuint sceneAmbient;
                GLuint eyeVertex;
                GLuint clipVertex;
                GLuint eyeNormal;
                GLuint intensity;
                
                eyeVertex = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1);
                clipVertex = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1);
                eyeNormal = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1);
                intensity = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1);
                
                lightDirection = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_CONSTANT_EXT, GL_FULL_RANGE_EXT, 1);
                diffMaterial = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_CONSTANT_EXT, GL_FULL_RANGE_EXT, 1);
                sceneAmbient = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_CONSTANT_EXT, GL_FULL_RANGE_EXT, 1);
                glSetLocalConstantEXT (lightDirection, GL_FLOAT, direction);
                glSetLocalConstantEXT (diffMaterial, GL_FLOAT, (void *)&(diffuse.r));
                glSetLocalConstantEXT (sceneAmbient, GL_FLOAT, (void *)&(ambient.r));
                glShaderOp2EXT (GL_OP_MULTIPLY_MATRIX_EXT, eyeVertex, Modelview, Vertex);
                glShaderOp2EXT (GL_OP_MULTIPLY_MATRIX_EXT, GL_OUTPUT_VERTEX_EXT, Projection, eyeVertex);
                
                glShaderOp2EXT (GL_OP_MULTIPLY_MATRIX_EXT, eyeNormal, Modelview, Normal);
                glShaderOp2EXT (GL_OP_DOT3_EXT, intensity, lightDirection, eyeNormal);
                glShaderOp2EXT (GL_OP_ADD_EXT, intensity, sceneAmbient, intensity);
                glShaderOp2EXT (GL_OP_MUL_EXT, GL_OUTPUT_COLOR0_EXT, diffMaterial, intensity);
        }
        glEndVertexShaderEXT ();
}
void pnTriangles::bindFragmentProgram()
{
        
        
        if (!fExtensionSupported[kFragmentShaderEXT])
                return;
        
        glEnable(GL_FRAGMENT_SHADER_ATI);
        
        if (fFragmentShaderId != -1)
        {
                glBindFragmentShaderATI (fFragmentShaderId);
                return;
        }
        fFragmentShaderId = glGenFragmentShadersATI(1);
        glBindFragmentShaderATI (fFragmentShaderId);
        glBeginFragmentShaderATI();
        
        glEndFragmentShaderATI();
}
MStatus pnTriangles::bind(const MDrawRequest& request, M3dView& view)
{
        
        view.beginGL();
        glPushAttrib( GL_ALL_ATTRIB_BITS );
        glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);
        MColor diffuse(1.0F, 1.0F, 0.0F, 1.0F);
        MColor specular(1.0F, 1.0F, 1.0F, 1.0F);
        MColor emission(0.0F, 0.0F, 0.0F, 1.0F);
        MColor ambient(0.2F, 0.2F, 0.2F, 1.0F);
        
        
        float shininess;
        bool hasTransparency = false;
        MMaterial material = request.material();
        fInTexturedMode = material.materialIsTextured();
        
        
        
        bool useInternalMaterialSetting = false;
        
        if (!useInternalMaterialSetting)
        {
                material.getEmission( emission );
                material.getSpecular( specular );
                shininess = 13.0;
        }
        material.getHasTransparency( hasTransparency ); 
        if (!fInTexturedMode)
        {
                if (!fTestVertexProgram && !useInternalMaterialSetting)
                        material.getDiffuse( diffuse );
        }
        
        
        else
        {
                if (!useInternalMaterialSetting)
                        diffuse.r = diffuse.g = diffuse.b = diffuse.a = 1.0;
        }
        
        
        if (fTestVertexProgram)
        {
                bindVertexProgram(diffuse, specular, emission, ambient);
        }
        else if (fTestFragmentProgram)
        {
                bindFragmentProgram();
        }
        
        
        else
        {
                
                
                glEnable(GL_COLOR_MATERIAL);
                glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT);
                glColor4fv(&ambient.r);
                if (fInTexturedMode)
                {
                        glEnable( GL_TEXTURE_2D );
                        MDrawData drawData = request.drawData();
                        material.applyTexture( view, drawData );
                        float scaleS, scaleT, translateS, translateT, rotate;
                        material.getTextureTransformation(scaleS, scaleT, translateS,
                                                                                          translateT, rotate);
                        rotate = DEG_TO_RAD(-rotate);
                        float c = cosf(rotate);
                        float s = sinf(rotate);
                        translateS += ((c+s)/2.0F);
                        translateT += ((c-s)/2.0F);
                        glMatrixMode(GL_TEXTURE);
                        glPushMatrix();
                        glLoadIdentity();
                        if(scaleS != 1.0f || scaleT != 1.0f)
                                glScalef(1.0f/scaleS, 1.0f/scaleT, 1.0f);
                        if(translateS != 0.0f || translateT != 0.0f)
                                glTranslatef(0.5f-translateS, 0.5f-translateT, 0.0f);
                        else
                                glTranslatef(0.5f, 0.5f, 0.0f);
                        if(rotate != 0.0f)
                                glRotatef(-rotate, 0.0f, 0.0f, 1.0f);
                        glMatrixMode(GL_MODELVIEW);
                }
                if (!useInternalMaterialSetting)
                {
                        glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
                        glColor4fv(&diffuse.r);
                        glColorMaterial(GL_FRONT_AND_BACK, GL_SPECULAR);
                        glColor4fv(&specular.r);
                        glColorMaterial(GL_FRONT_AND_BACK, GL_EMISSION);
                        glColor4fv(&emission.r);
                        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
                }
                else
                {
                        const MDagPath dagPath =  request.multiPath();
                        material.evaluateMaterial( view, dagPath );
                        material.setMaterial(dagPath, hasTransparency);
                }
        }
        
        
        if (fExtensionSupported[kPNTriangesEXT] || (fSubdivisions == 0))
        {
                if (fSubdivisions != 0)
                {
                        glEnable( GL_PN_TRIANGLES_ATI );
                        
                        
                        if (fPointMode == kPointLinear)
                                glPNTrianglesiATI( GL_PN_TRIANGLES_POINT_MODE_ATI, 
                                                                   GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI);
                        else
                                glPNTrianglesiATI( GL_PN_TRIANGLES_POINT_MODE_ATI, 
                                                                   GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI);
                        
                        
                        if (fNormalMode == kNormalLinear)
                                glPNTrianglesiATI( GL_PN_TRIANGLES_NORMAL_MODE_ATI, 
                                                                   GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI);
                        else
                                glPNTrianglesiATI( GL_PN_TRIANGLES_NORMAL_MODE_ATI, 
                                                                   GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI);
                        
                        
                        glPNTrianglesiATI( GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI,
                                                          fSubdivisions );
                }
        }
        view.endGL();
        return MS::kSuccess;
}
MStatus pnTriangles::unbind(const MDrawRequest& request,
                                                    M3dView& view)
{
        view.beginGL();
        
        if (fTestVertexProgram)
        {
                glDisable(GL_VERTEX_SHADER_EXT);
        }
        
        else if (fTestFragmentProgram)
        {
                glDisable(GL_FRAGMENT_SHADER_ATI);
        }
        
        else
        {
                glDisable( GL_TEXTURE_2D );
                glDisable(GL_COLOR_MATERIAL);
                if (fInTexturedMode)
                {
                        glMatrixMode(GL_TEXTURE);
                        glPopMatrix();
                        glMatrixMode(GL_MODELVIEW);
                }
        }
        
        
        if (fExtensionSupported[kPNTriangesEXT] || (fSubdivisions == 0))
        {
                if (fSubdivisions != 0)
                        glDisable( GL_PN_TRIANGLES_ATI );
        }
        
        glPopClientAttrib();
        glPopAttrib();
        view.endGL();
        return MS::kSuccess;
}
unsigned int pnTriangles:: computePNTriangles(const float * vertexArray,
                                                                          const float * normalArray,
                                                                          const float * texCoordArray,
                                                                          float * pnVertexArray,
                                                                          float * pnNormalArray,
                                                                          float * pnTexCoordArray,
                                                                          float * pnColorArray)
{
        unsigned int triangleCount = 0;
        
        return triangleCount;
}
MStatus pnTriangles::geometry( const MDrawRequest& request,
                                                                M3dView& view,
                                                            int prim,
                                                                unsigned int writable,
                                                                int indexCount,
                                                                const unsigned int * indexArray,
                                                                int vertexCount,
                                                                const int * vertexIDs,
                                                                const float * vertexArray,
                                                                int normalCount,
                                                                const float ** normalArrays,
                                                                int colorCount,
                                                                const float ** colorArrays,
                                                                int texCoordCount,
                                                                const float ** texCoordArrays)
{
        
        
        if (prim != GL_TRIANGLES)
                return MS::kSuccess;    
        view.beginGL();
        if (fWireframe)
                glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        bool needColors = (fNumColors && colorCount);
        bool needTexCoords = (fNumTextureCoords && texCoordCount);
        bool needNormals = (fNumNormals && normalCount);
        if (!fExtensionSupported[kPNTriangesEXT])
        {
                
        }
        glVertexPointer(3, GL_FLOAT, 0, vertexArray);
        glEnableClientState(GL_VERTEX_ARRAY);
        if (needNormals)
        {
                glNormalPointer(GL_FLOAT, 0, normalArrays[0]);
                glEnableClientState(GL_NORMAL_ARRAY);
        }
        if (needTexCoords)
        {
                glTexCoordPointer(2, GL_FLOAT, 0, texCoordArrays[0]);
                glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        }
        if (needColors)
        {
                
                glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
                glColorPointer(4, GL_FLOAT, 0, colorArrays[0]);
                glEnableClientState(GL_COLOR_ARRAY);
        }
        glDrawElements(prim, indexCount, GL_UNSIGNED_INT, indexArray);
        if (fWireframe)
                glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        view.endGL();
        return MS::kSuccess;
}
int     pnTriangles::normalsPerVertex()
{
        return fNumNormals;
}
int     pnTriangles::texCoordsPerVertex()
{
        if (fInTexturedMode)
                return fNumTextureCoords;
        else
                return 0;
}
int     pnTriangles::colorsPerVertex()
{
        return fNumColors;
}
MStatus initializePlugin( MObject obj )
{ 
        MStatus   status;
        const MString UserClassify( "shader/surface/utility" );
        
        MFnPlugin plugin( obj, PLUGIN_COMPANY, "4.5", "Any");
        status = plugin.registerNode( "pnTriangles", pnTriangles::id, pnTriangles::creator,
                                                                  pnTriangles::initialize, MPxNode::kHwShaderNode, &UserClassify  );
        if (!status) {
                status.perror("registerNode");
                return status;
        }
        return status;
}
MStatus uninitializePlugin( MObject obj)
{
        MStatus   status;
        MFnPlugin plugin( obj );
        status = plugin.deregisterNode( pnTriangles::id );
        if (!status) {
                status.perror("deregisterNode");
                return status;
        }
        return status;
}