//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+


#include <maya/MIOStream.h>
#include <maya/MFnPlugin.h>
#include <maya/MString.h>
#include <maya/MArgList.h>

#include <maya/MPxCommand.h>

#include <maya/MGlobal.h>
#include <maya/MItSelectionList.h>

#include <maya/MFnDependencyNode.h>
#include <maya/MFnTypedAttribute.h>

#include <maya/MPxData.h>
#include <maya/MTypeId.h>
#include <maya/MPlug.h>  
#include <maya/MFnPluginData.h>
#include <maya/MFnNurbsCurve.h>

#include <assert.h>

struct CVData {

	MStatus readASCII( const MArgList&, unsigned& );
	MStatus writeASCII( ostream& );

	MStatus readBinary( istream& in );
	MStatus writeBinary( ostream& out );

	double  _doubleData;
	int		_intData;

	static CVData empty;
};

CVData CVData::empty;

///////////////////////////////////////
//
// Proxy data class declaration
//
///////////////////////////////////////

class blindComplexData : public MPxData
{
public:
    blindComplexData();
    blindComplexData( const unsigned int );
    virtual ~blindComplexData();

	//
	// Override methods in MPxData.
    virtual MStatus         readASCII( const MArgList&, unsigned& lastElement );
    virtual MStatus         readBinary( istream& in, unsigned length );
    virtual MStatus         writeASCII( ostream& out );
    virtual MStatus         writeBinary( ostream& out );

	//
	// Custom methods.
    virtual void			copy( const MPxData& ); 

	MTypeId                 typeId() const; 
	MString					name() const;

	bool				setLength( const unsigned int numCVs,
									   const bool copyOldData = false );
	
	const CVData &operator [] ( const unsigned int index ) const;
	CVData &operator [] ( const unsigned int index );

	unsigned int 					length() const;

	//
	// static methods and data.
	static const MString    typeName;
    static const MTypeId    id;
	static void*            creator();

private:

	bool indexOk( const unsigned int ) const;

	// 
	// any customized data can be declared here, in this case, an array of 
	// CVData that is associated with the CVs. 
	
	CVData*					_CVDataArrayPtr;	
	unsigned int					_length;
};

const MTypeId blindComplexData::id( 0x80002 );
const MString blindComplexData::typeName( "blindComplexData" );

class blindComplexDataCmd : public MPxCommand
{
public:
						blindComplexDataCmd();
	virtual				~blindComplexDataCmd(); 

	MStatus				doIt( const MArgList& args );
	MStatus				redoIt();
	MStatus				undoIt();
	bool				isUndoable() const;
	
public:
	static void*		creator();
	MItSelectionList*	iter;
};


//////////////////////////////////////////////////////////////////////////////
//
// Proxy data class implementation
//
//////////////////////////////////////////////////////////////////////////////

void* blindComplexData::creator()
{
    return new blindComplexData;
}

blindComplexData::blindComplexData()
    :_CVDataArrayPtr( NULL ),
	 _length(0)
{
}

blindComplexData::~blindComplexData()
{
	delete [] _CVDataArrayPtr;
	_length = 0;
}

blindComplexData::blindComplexData( const unsigned int cvNum )
	: _length(0),
	  _CVDataArrayPtr( NULL )
{
	setLength( cvNum );
}

//
// Set the length of the array  
bool blindComplexData::setLength ( const unsigned int cvNum, 
										 const bool copyOldData ) 
{
	bool ret = true;
	if ( _length != cvNum || _CVDataArrayPtr == NULL ) {
		CVData* ptr;
		if ( _length != 0 && ! copyOldData ) {
			cerr << "warning: might be erasing previous data." << endl;
		}
		ptr = new CVData[cvNum];
		if ( ptr == NULL ) {
			cerr << "out of memory, setLength() failed" << endl;
			ret = false;
		} else {
			if ( copyOldData ) {
				if ( cvNum < _length ) {
					cerr << "warning: new size not big enough for old data." 
						 << endl;
				}
				for ( unsigned int i = 0; i < cvNum; i++ ) {
					ptr[i] = _CVDataArrayPtr[i];  
				}
			}
			delete [] _CVDataArrayPtr;
			_CVDataArrayPtr = ptr;
			_length = cvNum;
		}
	} else {
		cerr << "warning: setLength() o.k., same length, no change." << endl;
	}
	return ret;
}

unsigned int blindComplexData::length ( ) const 
{
	return _length;
}

inline 
bool blindComplexData::indexOk( const unsigned int index ) const {
	return ( index < _length );
}

const CVData& blindComplexData::operator [] ( const unsigned int index ) const 
{
	if ( indexOk( index  ) ) {
		return _CVDataArrayPtr[index];
	} else {
		cerr << "indexing error.  operator [] failed, returning invalid object"
		 	 << endl;
		return CVData::empty;
	}
}

CVData &blindComplexData::operator [] ( const unsigned int index ) 
{
	if ( indexOk( index  ) ) {
		return _CVDataArrayPtr[index];
	} else {
		cerr << "indexing error.  operator [] failed, returning invalid object"
		 	 << endl;
		// 
		// to get away a compiler warning.
		return CVData::empty; 
	}
}

void blindComplexData::copy( const MPxData& other )
//
//  Deescription:
//      Perform a copy or a conversion
//
{ 
    if( other.typeId() == blindComplexData::id ) {
        const blindComplexData* otherData =
										(const blindComplexData*)&other;
		if ( _length != otherData->_length ) {
			if ( ! setLength( otherData->_length ) ) {
				return;
			}
		} 
		for ( unsigned int i = 0; i < _length; i++ ) {
			_CVDataArrayPtr[i] = (*otherData)[i];
		}

    } else {
        //  we need to convert to the other type based on its iff Tag
		cerr << "wrong data format!" << endl;
    }
    return;
}

MTypeId blindComplexData::typeId() const
{
	return blindComplexData::id;
}

MString blindComplexData::name() const
{ 
	return blindComplexData::typeName; 
}

MStatus blindComplexData::readASCII(  const MArgList& args,
        		                        unsigned& lastParsedElement )
{
    MStatus status;
	int argLength = args.length();
    if( argLength > 0 ) {
		int numDataRecord = (argLength - lastParsedElement); 
		//
		// Note: a better solution to determine the number of records is to
		// write out the number of records in the writeASCII() routine.
		//
		if ( numDataRecord % 2 != 0 ) {
			cerr << "warning: corrupted data for blindComplexData" << endl;
		}
		// 
		// 2 numbers per record.
		numDataRecord /= 2;
		setLength(numDataRecord); 
		for ( unsigned int i=0; i < _length; i++ ) {
        	status = _CVDataArrayPtr[i].readASCII(args, lastParsedElement);
			if ( status != MS::kSuccess ) {
				return status;
			}
		}
		return MS::kSuccess;
    } 
	return MS::kFailure;
}

MStatus CVData::readASCII ( const MArgList& args,
							unsigned& lastParsedElement ) 
{
	MStatus status;
	_intData = args.asInt( lastParsedElement++, &status );
	if ( status == MS::kSuccess ) {
	    _doubleData = args.asDouble( lastParsedElement++, &status );
	} 
	return status;
}
				
MStatus CVData::writeASCII ( ostream& out )  
{
	out << _intData << " " << _doubleData << " ";
	return out.fail() ? MS::kFailure : MS::kSuccess;
}

MStatus blindComplexData::writeASCII( ostream& out )
{
	MStatus status;
	for ( unsigned int i=0; i < _length; i++ ) {
		status = _CVDataArrayPtr[i].writeASCII(out);
		if ( status != MS::kSuccess ) {
			return MS::kFailure;
		}
	}
    return MS::kSuccess;
}

MStatus blindComplexData::readBinary( istream& in, unsigned length )
{
	MStatus status;
	if ( length > 0 ) {
		unsigned int recNum;
		in.read( (char*) &recNum, sizeof(recNum) );
		if ( ! in.fail() && recNum > 0 ) {
			setLength(recNum); 
			for ( unsigned int i=0; i < _length; i++ ) {
				status = _CVDataArrayPtr[i].readBinary(in);
				if ( status != MS::kSuccess ) {
					return status;
				}
			}
		}
	} else {
		return MS::kFailure;
	}
	return MS::kSuccess;
}

MStatus blindComplexData::writeBinary( ostream& out )
{
	MStatus status;
	out.write( (char*) &_length, sizeof(_length) );
	if ( ! out.fail() ) {
		for ( unsigned int i=0; i < _length; i++ ) {
			status = _CVDataArrayPtr[i].writeBinary(out);
			if ( status != MS::kSuccess ) {
				return status; 
			}
		}
	} else {
		return MS::kFailure;
	}
    return MS::kSuccess;
}

MStatus CVData::readBinary ( istream& in )
{
	in.read( (char*) &_intData, sizeof(_intData) );
	if ( !in.fail() ) {
		in.read( (char*) &_doubleData, sizeof(_doubleData) );
	} else {
		return MS::kFailure;
	}
	return in.fail() ? MS::kFailure : MS::kSuccess;
}
				
MStatus CVData::writeBinary ( ostream& out )  
{
	out.write( (char*) &_intData, sizeof(_intData) );
	if ( !out.fail() ) {
		out.write( (char*) &_doubleData, sizeof(_doubleData) );
	} else {
		return MS::kFailure;
	}
	return out.fail() ? MS::kFailure : MS::kSuccess;
}


//////////////////////////////////////////////////////////////////////////////
//
// Command class implementation
//
//////////////////////////////////////////////////////////////////////////////

void* blindComplexDataCmd::creator()
{
	return new blindComplexDataCmd;
}

blindComplexDataCmd::~blindComplexDataCmd()
{
}

blindComplexDataCmd::blindComplexDataCmd()
	: MPxCommand()
{
}

MStatus blindComplexDataCmd::doIt( const MArgList& )
{
	MStatus stat;

	// Create a selection list iterator
	//
    MSelectionList list;
    MGlobal::getActiveSelectionList( list );
	iter = new MItSelectionList( list,	MFn::kInvalid, &stat	);
	if ( MS::kSuccess == stat )
		stat = redoIt();

	return stat;
}

MStatus blindComplexDataCmd::redoIt()
{
	MStatus     stat;				// Status code
	MObject 	dependNode;		// Selected dependency node

	// Iterate over all selected dependency nodes
	//
	for ( ; !iter->isDone(); iter->next() ) 
	{
		// Get the selected dependency node and create
		// a function set for it
		//
		if ( MS::kSuccess != iter->getDependNode( dependNode ) ) {
			cerr << "Error getting the dependency node" << endl;
			continue;
		}
		
		MFnDependencyNode fnDN( dependNode, &stat );
		if ( MS::kSuccess != stat ) {
			cerr << "Error creating MFnDependencyNode" << endl;
			continue;
		}

		// Create a new attribute for our blind data
		//
		// cout << "Creating attr..." << endl;
		
		MFnTypedAttribute fnAttr;
		const MString fullName( "blindComplexData" );
		const MString briefName( "BCD" );
		MObject newAttr = fnAttr.create( fullName, briefName,
										 blindComplexData::id );

		// Now add the new attribute to the current dependency node
		//
		// cout << "Adding attr..." << endl;
		fnDN.addAttribute( newAttr, MFnDependencyNode::kLocalDynamicAttr );
		
		//
		// now we will demonstrate setting the value by using a plug.
		MPlug plug( dependNode, newAttr );  

		//
		// create an instance of the blind data with an initial array size of
		// 5.
		blindComplexData * newData = new blindComplexData( 5 );

		//
		// initialized 
		// cout << "setting data values..." << endl;
		unsigned int i;
		for ( i= 0; i < newData->length(); i++ ) {
			(*newData)[i]._intData = 10 + i;
			(*newData)[i]._doubleData = 20.02 + i;
		}

		// 
		// setting the value for the plug.
		stat = plug.setValue( newData );

		//
		// The following code demonstrates the retrieving of data from the 
		// plug.
		MObject sData;
		stat = plug.getValue( sData );

		if ( stat != MS::kSuccess ) {
			cerr << "error getting value off plug" << endl;
			continue;
		}

		// 
		// Convert the data from an MObject back to a pointer to MPxData, then
		// cast it back to a pointer to blindComplexData.
		MFnPluginData pdFn( sData ); 
		blindComplexData* data =
							( blindComplexData* ) pdFn.constData( &stat );
		//
		// read the data, and set the result to the values set.
		
		clearResult();

		if ( NULL != data ) {
			// cout << "retrieving data values..." << endl;
			for ( i = 0; i < data->length(); i++ ) {
				// cout << "rec #" << i << ": " << (*data)[i]._intData << ", "; 
				// cout << (*data)[i]._doubleData << endl;
				appendToResult((double) ((*data)[i]._intData));
				appendToResult((*data)[i]._doubleData);
			}
		} else {
			// cout << "Null data" << endl;
		}

	}
	return MS::kSuccess;
}

MStatus blindComplexDataCmd::undoIt()
{
	return MS::kSuccess;
}

bool blindComplexDataCmd::isUndoable() const
{
	return true;
}


//////////////////////////////////////////////////////////////////////////////
//
// The following routines are used to register/unregister
// the command we are creating within Maya
//
//////////////////////////////////////////////////////////////////////////////
MStatus initializePlugin( MObject obj )
{ 
	MStatus	  status;
	MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any" );

	status = plugin.registerData( "blindComplexData",
						  blindComplexData::id,
						  blindComplexData::creator );
	if (!status) {
		status.perror("registerData");
		return status;
	}

	status = plugin.registerCommand(  "blindComplexData",
							 blindComplexDataCmd::creator );
	if (!status) {
		plugin.deregisterData( blindComplexData::id );
		status.perror("registerCommand");
		return status;
	}

	return status;
}

MStatus uninitializePlugin( MObject obj)
{
	MStatus	  status1, status2;
	MFnPlugin plugin( obj );

	status1 = plugin.deregisterCommand( "blindComplexData" );
	if (!status1) {
		status1.perror("deregisterCommand");
	}
	status2 = plugin.deregisterData( blindComplexData::id );
	if (!status2) {
		status2.perror("deregisterData");
	}

	if ( !status1 )
		return status1;
	return status2;
}
