//-
// ==========================================================================
// 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 "meshMapUtils.h"
#include "meshReorderCmd.h"

#include <maya/MArgList.h>
#include <maya/MDagPathArray.h>
#include <maya/MFnMesh.h>
#include <maya/MFnMeshData.h>
#include <maya/MIOStream.h>
#include <maya/MItSelectionList.h>
#include <maya/MItMeshVertex.h>
#include <maya/MPlug.h>
#include <maya/MString.h>

// CONSTRUCTOR DEFINITION:
meshReorderCommand::meshReorderCommand()
{
	fClampedArray = NULL;
	fRepArray = NULL;
	fColorArrays = NULL;
	fVertexColorArrays = NULL;
	fColorIdsArrays = NULL;
	fUArrays = NULL;
	fVArrays = NULL;
	fUVIdsArrays = NULL;
}


// DESTRUCTOR DEFINITION:
meshReorderCommand::~meshReorderCommand()
{
	resetColorsUVsMemory();
}


// METHOD FOR CREATING AN INSTANCE OF THIS COMMAND:
void* meshReorderCommand::creator()
{
	return new meshReorderCommand;
}


MStatus meshReorderCommand::parseArgs(const MArgList& args)
{
	MStatus stat;
	MString err;


	if( args.length() != 3 )
	{
		displayError("3 vertices must be specified");
		return MS::kFailure;
	}

	MObjectArray selectedComponent(3);
	MDagPathArray selectedPath;

	selectedPath.setLength(3);

	int argIdx = 0;
	for (unsigned int j = 0; j < 3; j++)
	{
		MString arg;

		if( ( stat = args.get( argIdx, arg )) != MStatus::kSuccess )
		{
			displayError( "Can't parse arg");
			return stat;
		}

		MSelectionList list;
		if (! list.add( arg ) )
		{
			err = arg + ": no such component";
			displayError(err);
			return MS::kFailure; // no such component
		}

		MItSelectionList selectionIt (list, MFn::kComponent);
		if (selectionIt.isDone ())
		{
			err = arg + ": not a component";
			displayError (err);
			return MS::kFailure;
		}

		if( selectionIt.getDagPath (selectedPath[j], selectedComponent[j]) != MStatus::kSuccess )
		{
			displayError( "Can't get path for");
			return stat;
		}


		if (!selectedPath[j].node().hasFn(MFn::kMesh) && !(selectedPath[j].node().hasFn(MFn::kTransform) && selectedPath[j].hasFn(MFn::kMesh)))
		{
			err = arg + ": Invalid type!  Only a mesh or its transform can be specified!";
			displayError (err);
			return MStatus::kFailure;
		}

		argIdx++;
	}

	if( ( stat = meshMapUtils::validateFaceSelection( selectedPath, selectedComponent, &fFaceIdxSrc, &fFaceVtxSrc ) ) != MStatus::kSuccess )
	{
		displayError("Selected vertices don't define a unique face on source mesh");
		return stat;
	}

	fDagPathSrc = selectedPath[0];

	return stat;
}

// FIRST INVOKED WHEN COMMAND IS CALLED, PARSING THE COMMAND ARGUMENTS, INITIALIZING DEFAULT PARAMETERS, THEN CALLING redoIt():
MStatus meshReorderCommand::doIt(const MArgList& args)
{
	MStatus  stat = MStatus::kSuccess;

	if ( ( stat = parseArgs( args ) ) != MStatus::kSuccess )
	{
		displayError ("Error parsing arguments");
		return stat;
	}

	return redoIt();
}

MStatus meshReorderCommand::redoIt()
{
	MStatus  stat = MStatus::kSuccess;

	MIntArray 			newPolygonCounts;
	MIntArray 			newPolygonConnects;
	MFloatPointArray 	origVertices;
	MFloatPointArray 	newVertices;

	unsigned i;

	MFnMesh theMesh( fDagPathSrc, &stat );
	if( stat != MStatus::kSuccess )
	{
		displayError(" MFnMesh creation");
		return stat;
	}

	//	The mesh cannot have history or this won't work.
	MPlug	historyPlug = theMesh.findPlug("inMesh", true);

	if (historyPlug.isDestination()) {
		displayError("The mesh has history. Its geometry cannot be modified.");
		return MS::kInvalidParameter;
	}

	stat = theMesh.getPoints (origVertices, MSpace::kObject );
	if( stat != MStatus::kSuccess )
	{
		displayError(" MFnMesh getPoints");
		return stat;
	}

	// Initialize the traversal flags and CV mappings for this shape 
	MIntArray 			faceTraversal( theMesh.numPolygons(), false );
	MIntArray cvMapping(theMesh.numVertices(), -1);
	MIntArray cvMappingInverse(theMesh.numVertices(), -1);

	//
	//  Starting with the user selected face, recursively rebuild the entire mesh
	//
	stat = meshMapUtils::traverseFace( fDagPathSrc, fFaceIdxSrc, fFaceVtxSrc[0], fFaceVtxSrc[1], faceTraversal,
					cvMapping, cvMappingInverse, 
					newPolygonCounts, newPolygonConnects, 
					origVertices, newVertices );

	if ( stat != MStatus::kSuccess )
	{
		displayError(" could not process all the mesh faces.");
		return stat;
	}

	// Store mesh vertices and connectivity information for undo. Must collect the information here before it is 
	// modified by createInPlace() call.
	fVertices.copy(origVertices);
	MItMeshPolygon polyIter(fDagPathSrc.node());
	while(!polyIter.isDone()) 
	{
		fPolygonCounts.append(polyIter.polygonVertexCount());
		for(i = 0; i < polyIter.polygonVertexCount(); i++)
		{
			fPolygonConnects.append(polyIter.vertexIndex(i));
		}
		polyIter.next();
	}

	fColorSetNames.clear();
	theMesh.getColorSetNames(fColorSetNames);
	int numColorSets = fColorSetNames.length();
	fColorArrays = new MColorArray[numColorSets];
	fColorIdsArrays = new MIntArray[numColorSets];

	collectColorsUVs(theMesh, false);

	stat = theMesh.createInPlace( newVertices.length(), newPolygonCounts.length(), newVertices, newPolygonCounts, newPolygonConnects );

	if ( stat != MStatus::kSuccess )
	{
		displayError(" MFnMesh::createInPlace failed.");

		fCVMapping.clear();
		fCVMappingInverse.clear();
		fVertices.clear();
		fPolygonCounts.clear();
		fPolygonConnects.clear();
		delete [] fColorArrays;  fColorArrays = NULL;
		delete [] fColorIdsArrays;fColorIdsArrays = NULL;

		// Free memeory allocated in collectColorsUVs()
		resetColorsUVsMemory();

		return stat;
	}

	assignColorsUVs(theMesh, cvMapping, cvMappingInverse, false);

	// Store fCVMapping and fCVMappingInverse for undo
	fCVMapping = cvMapping;
	fCVMappingInverse = cvMappingInverse;

	return stat;
}

MStatus meshReorderCommand::undoIt()
{
	MStatus  stat = MStatus::kSuccess;
	MFnMesh theMesh( fDagPathSrc );

	fColorSetNames.clear();
	theMesh.getColorSetNames(fColorSetNames);

	collectColorsUVs(theMesh, true);

	stat = theMesh.createInPlace( fVertices.length(), fPolygonCounts.length(), fVertices, fPolygonCounts, fPolygonConnects );

	assignColorsUVs(theMesh, fCVMappingInverse, fCVMapping, true);

	fCVMapping.clear();
	fCVMappingInverse.clear();
	fVertices.clear();
	fPolygonCounts.clear();
	fPolygonConnects.clear();
	delete [] fColorArrays;  fColorArrays = NULL;
	delete [] fColorIdsArrays;fColorIdsArrays = NULL;

	return stat;
}

void meshReorderCommand::collectColorsUVs(MFnMesh &theMesh, bool isUndo)
{
	int i;
	// Store Colors
	int numColorSets = fColorSetNames.length();
	fClampedArray = new bool[numColorSets];
	fRepArray = new MFnMesh::MColorRepresentation[numColorSets];
	fVertexColorArrays = new MColorArray[numColorSets];

	for (i = 0; i < numColorSets; i++)
	{
		fClampedArray[i] = theMesh.isColorClamped(fColorSetNames[i]);
		fRepArray[i] = theMesh.getColorRepresentation(fColorSetNames[i]);
		// We need to use two different approaches to set colors because unfortunately 
		// setVertexColors() doesn't work in the case of undo and setColors() will
		// require a huge effort to construct the colorIds in the non undo case.
		theMesh.getVertexColors(fVertexColorArrays[i], &(fColorSetNames[i]));

		if(!isUndo) {
			theMesh.getColors(fColorArrays[i], &(fColorSetNames[i]));
			unsigned nth = 0;
			fColorIdsArrays[i].setLength(theMesh.numColors(fColorSetNames[i]));
			MItMeshPolygon polyIter(fDagPathSrc.node());
			while(!polyIter.isDone()) 
			{
				for(unsigned j = 0; j < polyIter.polygonVertexCount(); j++)
				{
					unsigned polygonIdx = polyIter.index();
					int colorId;
					theMesh.getColorIndex(polygonIdx, j, colorId, &(fColorSetNames[i]));
					fColorIdsArrays[i][nth] = colorId;
					nth++;
				}
				polyIter.next();
			}
		}
		theMesh.deleteColorSet(fColorSetNames[i]);
	}

	// Store UVs 
	fUVSetNames.clear();
	theMesh.getUVSetNames(fUVSetNames);
	int numUVSets = fUVSetNames.length();
	fUArrays = new MFloatArray[numUVSets];
	fVArrays = new MFloatArray[numUVSets];
	fUVIdsArrays = new MIntArray[numUVSets];

	for (i = 0; i < numUVSets; i++) 
	{
		MIntArray uvCounts;
		MIntArray uvIds;
		theMesh.getAssignedUVs(uvCounts, uvIds, &(fUVSetNames[i]));

		unsigned nth = 0;
		fUVIdsArrays[i].setLength(fVertices.length());
		MItMeshPolygon polyIter(fDagPathSrc.node());
		while(!polyIter.isDone()) 
		{
			for(unsigned j = 0; j < polyIter.polygonVertexCount(); j++)
			{
				unsigned vertexIdx = polyIter.vertexIndex(j);
				fUVIdsArrays[i][vertexIdx] = uvIds[nth];
				nth++;
			}
			polyIter.next();
		}

		theMesh.getUVs(fUArrays[i], fVArrays[i], &(fUVSetNames[i]));
		theMesh.deleteUVSet(fUVSetNames[i]);
	}
}

void meshReorderCommand::assignColorsUVs(MFnMesh &theMesh, MIntArray &colorMapping, MIntArray &uvMapping, bool isUndo)
{
	int i;

	// Copy Colors 
	MString defaultColorSetName;
	theMesh.getCurrentColorSetName(defaultColorSetName);
	int numColorSets = fColorSetNames.length();
	for (i = 0; i < numColorSets; i++) 
	{
		// Not to duplicate default color set
		if (fColorSetNames[i] != defaultColorSetName)
		{
			theMesh.createColorSet(fColorSetNames[i], NULL, fClampedArray[i], fRepArray[i]);
		}

		if (fVertexColorArrays[i].length() > 0 && fVertexColorArrays[i].length() == fVertices.length())
		{
			// We need to use two different approaches to set colors because unfortunately 
			// setVertexColors() doesn't work in the case of undo and setColors() will
			// require a huge effort to construct the colorIds in the non undo case.
			if(!isUndo) 
			{
				theMesh.setVertexColors(fVertexColorArrays[i], colorMapping, NULL, fRepArray[i]);
			} else {
				theMesh.setColors(fColorArrays[i], NULL, fRepArray[i]);
				theMesh.assignColors(fColorIdsArrays[i], &(fColorSetNames[i]));
			}
		}
	}

	// Copy UVs
	MString defaultUVSetName;
	theMesh.getCurrentUVSetName(defaultUVSetName);
	int numUVSets = fUVSetNames.length();
	for (i = 0; i < numUVSets; i++) 
	{
		// Not to duplicate default uv set
		if (fUVSetNames[i] != defaultUVSetName)
		{
			theMesh.createUVSet(fUVSetNames[i]);
		}

		if (fUArrays[i].length() > 0 && fUArrays[i].length() == fVArrays[i].length())
		{
			theMesh.setUVs(fUArrays[i], fVArrays[i], &(fUVSetNames[i]));
			MItMeshPolygon polyIter(fDagPathSrc.node());
			while(!polyIter.isDone()) 
			{
				for(unsigned j = 0; j < polyIter.polygonVertexCount(); j++)
				{
					unsigned polygonIdx = polyIter.index();
					unsigned vertexIdx = polyIter.vertexIndex(j);
					int uvId = fUVIdsArrays[i][uvMapping[vertexIdx]];
					theMesh.assignUV(polygonIdx, j, uvId);
				}
				polyIter.next();
			}
		}
	}

	resetColorsUVsMemory();
}

void meshReorderCommand::resetColorsUVsMemory()
{
	if (fClampedArray != NULL) 
	{
		delete [] fClampedArray; 
		fClampedArray = NULL;
	}
	if (fRepArray != NULL)
	{
		delete [] fRepArray;
		fRepArray = NULL;
	}
	if (fVertexColorArrays != NULL)
	{
		delete [] fVertexColorArrays;
		fVertexColorArrays = NULL;
	}
	if (fUArrays != NULL)
	{
		delete [] fUArrays;
		fUArrays = NULL;
	}
	if (fVArrays != NULL)
	{
		delete [] fVArrays;
		fVArrays = NULL;
	}
	if (fUVIdsArrays != NULL)
	{
		delete [] fUVIdsArrays;
		fUVIdsArrays = NULL;
	}
	fColorSetNames.clear();
	fUVSetNames.clear();
}
