#include <createMetadataCmd.h>
#include <metadataPluginStrings.h>
#include <maya/MObject.h>
#include <maya/MSyntax.h>
#include <maya/MString.h>
#include <maya/MStringResource.h>
#include <maya/MFnMesh.h>
#include <maya/MFileObject.h>
#include <maya/MSelectionList.h>
#include <maya/adskDataStream.h>
#include <maya/adskDataChannel.h>
#include <maya/adskDataAssociations.h>
#include <float.h>
#include <string>

using namespace adsk::Data;

#define F_RAND float((4000000.0 * ((float)rand()/(float)RAND_MAX)) - 2000000.0)
#define D_RAND ((4000000000.0 * ((double)rand()/(double)RAND_MAX)) - 2000000000.0)

// Command flag names
static const char* flagChannelName		( "-cn" );
static const char* flagChannelNameLong	( "-channelName" );
static const char* flagStreamName		( "-sn" );
static const char* flagStreamNameLong	( "-streamName" );
static const char* flagStructure		( "-s" );
static const char* flagStructureLong	( "-structure" );

//============================================================================
//
// Get the syntax information. Initializes the shared flags. Derived commands
// can add on their own flags after calling this routine.
//
MSyntax createMetadataCmd::cmdSyntax()
{
	MSyntax syntax;
    syntax.addFlag(flagChannelName,	flagChannelNameLong, MSyntax::kString);
    syntax.addFlag(flagStreamName,	flagStreamNameLong,	 MSyntax::kString);
    syntax.addFlag(flagStructure,	flagStructureLong,	 MSyntax::kString);
	
	syntax.useSelectionAsDefault(true);
    syntax.setObjectType(MSyntax::kSelectionList, 1);

	// No query or edit.
    syntax.enableQuery( false );
    syntax.enableEdit( false );

	// Seed the random numbers for later
	srand( 123 );

    return syntax;
}

//======================================================================
//
// Creator function: returns a new command object
//
void* createMetadataCmd::creator()
{
	return new createMetadataCmd;
}

//======================================================================
//
// Returns the name of this command
//
const char* createMetadataCmd::name()
{
	return "createMetadata";
}

//======================================================================
//
// Default command constructor
//
createMetadataCmd::createMetadataCmd()
: fNodes()
, fDGModifier()
, fIndexList()
, fChannelName()
, fStreamName()
, fStructure( (adsk::Data::Structure*) 0 )
{
}

//======================================================================
//
// Destructor, does nothing
//
createMetadataCmd::~createMetadataCmd()
{
}

//======================================================================
//
// This command is undoable
//
bool
createMetadataCmd::isUndoable() const
{
	return true;
}

//======================================================================
//
// Check the parsed arguments and do/undo/redo the command as appropriate
//
MStatus createMetadataCmd::checkArgs(MArgDatabase& argsDb)
{
	MStatus status = MS::kSuccess;

	//----------------------------------------
	// -structure flag
	//
	fStructureFlag.parse(argsDb, flagStructure);
	if( fStructureFlag.isSet() )
	{
		if( ! fStructureFlag.isArgValid() )
		{
			MString errMsg( MStringResource::getString(kInvalidString, status) );
			displayError( errMsg );
			return MS::kFailure;
		}
		MString structureName( fStructureFlag.arg() );
		fStructure = adsk::Data::Structure::structureByName( structureName.asChar() );
		if( ! fStructure )
		{
			MString fmt( MStringResource::getString(kCreateMetadataStructureNotFound, status) );
			MString msg;
			msg.format( fmt, structureName );
			displayError(msg);
			return MS::kFailure;
		}
	}
	else
	{
		MString err( MStringResource::getString(kCreateMetadataNoStructureName,status) );
		displayError( err );
		return MS::kFailure;
	}

	//----------------------------------------
	// -streamName flag
	//
	fStreamNameFlag.parse(argsDb, flagStreamName);
	if( fStreamNameFlag.isSet() )
	{
		if( ! fStreamNameFlag.isArgValid() )
		{
			MString errMsg( MStringResource::getString(kInvalidString, status) );
			displayError( errMsg );
			return MS::kFailure;
		}
		fStreamName = fStreamNameFlag.arg();
	}
	else
	{
		MString err( MStringResource::getString(kCreateMetadataNoStreamName,status) );
		displayError( err );
		return MS::kFailure;
	}

	//----------------------------------------
	// -channelName flag
	//
	fChannelNameFlag.parse(argsDb, flagChannelName);
	if( fChannelNameFlag.isSet() )
	{
		if( ! fChannelNameFlag.isArgValid() )
		{
			MString errMsg( MStringResource::getString(kInvalidString, status) );
			displayError( errMsg );
			return MS::kFailure;
		}
		fChannelName = fChannelNameFlag.arg().asChar();
	}
	else
	{
		MString err( MStringResource::getString(kCreateMetadataNoChannelName,status) );
		displayError( err );
		return MS::kFailure;
	}

	//----------------------------------------
	// (selection list)
	//
	// Commands need at least one node on which to operate so gather up
	// the list of nodes specified and/or selected.
	//

	// Empty out the list of nodes on which to operate so that it can be
	// populated from the selection or specified lists.
	fNodes.clear();

	MSelectionList objects;
	status = argsDb.getObjects(objects);
	MStatError(status, "argsDb.getObjects()");
	for (unsigned int i = 0; i<objects.length(); ++i)
	{
		MObject	node;
		status = objects.getDependNode(i, node);
		MStatError(status, "objects.getDependNode()");	
		fNodes.append( node );
	}

	if( fNodes.length() == 0 )
	{
		MString msg = MStringResource::getString(kObjectNotFoundError, status);
		displayError(msg);
		return MStatus::kFailure;
	}

	return status;
}

//======================================================================
//
// Do the metadata creation. The metadata will be randomly initialized
// based on the channel type and the structure specified. For recognized
// components the number of metadata elements will correspond to the count
// of components in the selected mesh, otherwise a random number of metadata
// elements between 1 and 100 will be created (at consecutive indices).
//
// The previously existing metadata is preserved for later undo.
//
MStatus createMetadataCmd::doIt(const MArgList& args)
{
	MStatus status;

	MArgDatabase argsDb(syntax(), args, &status);
	if (MS::kSuccess != status) return status;

    status = checkArgs(argsDb);
	if( MS::kSuccess != status )
	{
		return status;
	}

	clearResult();

	for( unsigned int i=0; i<fNodes.length(); ++i )
	{
		MFnDependencyNode node( fNodes[i] );
		// Get the current metadata (empty if none yet)
		adsk::Data::Associations newMetadata( node.metadata() );
		adsk::Data::Channel newChannel = newMetadata.channel( fChannelName );

		// Check to see if the requested stream name already exists
		adsk::Data::Stream* oldStream = newChannel.dataStream( fStreamName.asChar() );
		if( oldStream )
		{
			MString fmt = MStringResource::getString(kCreateMetadataHasStream, status);
			MString msg;
			msg.format( fmt, fStreamName );
			displayError( msg );
			status = MS::kFailure;
			continue;
		}

		adsk::Data::Stream newStream( *fStructure, fStreamName.asChar() );

		unsigned int indexCount = 0;

		// Treat the channel type initializations different for meshes
		if( fNodes[i].hasFn(MFn::kMesh) )
		{
			MFnMesh mesh( fNodes[i], &status );
			// Get mesh-specific channel type parameters
			if( fChannelName == "face" )
			{
				indexCount = mesh.numPolygons();
			}
			else if( fChannelName == "edge" )
			{
				indexCount = mesh.numEdges();
			}
			else if( fChannelName == "vertex" )
			{
				indexCount = mesh.numVertices();
			}
			else if( fChannelName == "vertexFace" )
			{
				indexCount = mesh.numFaceVertices();
			}
			else
			{
				indexCount = rand() % 100 + 1;
			}
		}
		else
		{
			// Create generic channel type information
			indexCount = rand() % 100 + 1;
		}

		// Fill specified stream ranges with random data
		unsigned int structureMemberCount = fStructure->memberCount();
		for( unsigned int m=0; m<indexCount; ++m )
		{
			// Walk each structure member and fill with random data
			// tailored to the member data type.
			adsk::Data::Handle handle( *fStructure );
			for( unsigned int i=0; i<structureMemberCount; ++i )
			{
				handle.setPositionByMemberIndex( i );
				for( unsigned int d=0; d<handle.dataLength(); ++d )
				{
					switch( handle.dataType() )
					{
						case adsk::Data::Member::kBoolean:
						{
							bool* data = handle.asBoolean();
							data[d] = (rand() % 2 == 1);
							break;
						}
						case adsk::Data::Member::kDouble:
						{
							double* data = handle.asDouble();
							data[d] = D_RAND;
							break;
						}
						case adsk::Data::Member::kDoubleMatrix4x4:
						{
							double* data = handle.asDoubleMatrix4x4();
							data[d*16+0] = D_RAND;
							data[d*16+1] = D_RAND;
							data[d*16+2] = D_RAND;
							data[d*16+3] = D_RAND;
							data[d*16+4] = D_RAND;
							data[d*16+5] = D_RAND;
							data[d*16+6] = D_RAND;
							data[d*16+7] = D_RAND;
							data[d*16+8] = D_RAND;
							data[d*16+9] = D_RAND;
							data[d*16+10] = D_RAND;
							data[d*16+11] = D_RAND;
							data[d*16+12] = D_RAND;
							data[d*16+13] = D_RAND;
							data[d*16+14] = D_RAND;
							data[d*16+15] = D_RAND;
							break;
						}
						case adsk::Data::Member::kFloat:
						{
							float* data = handle.asFloat();
							data[d] = F_RAND;
							break;
						}
						case adsk::Data::Member::kFloatMatrix4x4:
						{
							float* data = handle.asFloatMatrix4x4();
							data[d*16+0] = F_RAND;
							data[d*16+1] = F_RAND;
							data[d*16+2] = F_RAND;
							data[d*16+3] = F_RAND;
							data[d*16+4] = F_RAND;
							data[d*16+5] = F_RAND;
							data[d*16+6] = F_RAND;
							data[d*16+7] = F_RAND;
							data[d*16+8] = F_RAND;
							data[d*16+9] = F_RAND;
							data[d*16+10] = F_RAND;
							data[d*16+11] = F_RAND;
							data[d*16+12] = F_RAND;
							data[d*16+13] = F_RAND;
							data[d*16+14] = F_RAND;
							data[d*16+15] = F_RAND;
							break;
						}
						case adsk::Data::Member::kInt8:
						{
							signed char* data = handle.asInt8();
							data[d] = rand() % 255 - 127;
							break;
						}
						case adsk::Data::Member::kInt16:
						{
							short* data = handle.asInt16();
							data[d] = rand() % 65535 - 32767;
							break;
						}
						case adsk::Data::Member::kInt32:
						{
							int* data = handle.asInt32();
							data[d] = (rand()/2) * (rand() % 2 == 1 ? 1 : -1);
							break;
						}
						case adsk::Data::Member::kInt64:
						{
							int64_t* data = handle.asInt64();
							data[d] = rand() * 10 * (rand() % 2 == 1 ? 1 : -1);
							break;
						}
						case adsk::Data::Member::kUInt8:
						{
							unsigned char* data = handle.asUInt8();
							data[d] = rand() % 255;
							break;
						}
						case adsk::Data::Member::kUInt16:
						{
							unsigned short* data = handle.asUInt16();
							data[d] = rand() % 65535;
							break;
						}
						case adsk::Data::Member::kUInt32:
						{
							unsigned int* data = handle.asUInt32();
							data[d] = rand();
							break;
						}
						case adsk::Data::Member::kUInt64:
						{
							uint64_t* data = handle.asUInt64();
							data[d] = rand() * 10;
							break;
						}
						case adsk::Data::Member::kString:
						{
							char** data = handle.asString();
							data[d] = (char*) malloc( sizeof(char) * 9);
							for( unsigned int s=0; s<8; ++s )
							{
								data[d][s] = rand() % 26 + 'a';
							}
							data[d][8] = '\0';
							break;
						}

						default:
						{
							// Shouldn't ever happen
							assert( false );
							break;
						}
					}
				}
			}
			newStream.setElement( m, handle );
		}
		newChannel.setDataStream( newStream );
		newMetadata.setChannel( newChannel );

		fDGModifier.setMetadata( fNodes[i], newMetadata );
		status = fDGModifier.doIt();
		if( MS::kSuccess == status )
		{
			// Set the result to the number of actual metadata values set as a
			// triple value:
			//	 	(# nodes, # metadata elements, # members per element)
			//
			MIntArray theResult;
			theResult.append( (int) fNodes.length() );
			theResult.append( (int) indexCount );
			theResult.append( (int) structureMemberCount );
			setResult( theResult );
		}
		else
		{
			MString fmt = MStringResource::getString(kCreateMetadataCreateFailed, status);
			MString msg;
			msg.format( fmt, node.name() );
			displayError( msg );
		}
	}
    return status;
}

//======================================================================
//
// Redo the import, restoring the originally imported metadata onto the
// mesh(es).
//
MStatus createMetadataCmd::redoIt()
{
    return fDGModifier.doIt();
}

//======================================================================
//
// Undo the import, restoring the prior metadata to the mesh(es).
//
MStatus createMetadataCmd::undoIt()
{
    return fDGModifier.undoIt();
}

//-
// ==================================================================
// Copyright 2012 Autodesk, Inc.  All rights reserved.
// 
// This computer source code  and related  instructions and comments are
// the unpublished confidential and proprietary information of Autodesk,
// Inc. and are  protected  under applicable  copyright and trade secret
// law. They may not  be disclosed to, copied or used by any third party
// without the prior written consent of Autodesk, Inc.
// ==================================================================
//+
