//-
// ==========================================================================
// 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.
// ==========================================================================
//+

//
// File: cleanPerFaceAssignmentCmd.cpp
// MEL Command: cleanPerFaceAssignment
// Author: Hiroyuki Haga
// 

#include "cleanPerFaceAssignmentCmd.h"

// Include Maya libary: General
#include <maya/MGlobal.h>
#include <maya/MDagPath.h>
#include <maya/MPlug.h>
#include <maya/MObjectArray.h>
#include <maya/MIntArray.h>
#include <maya/MStringArray.h>

// Include Maya libary: Selection
#include <maya/MSelectionList.h>
#include <maya/MItSelectionList.h>

// Include Maya libary: Nodes
#include <maya/MFnDependencyNode.h>

// Include Maya libary: Polygons
#include <maya/MFnMesh.h>
#include <maya/MItMeshPolygon.h>

// Include Maya libary: Sets
#include <maya/MFnSet.h>

#ifdef DEBUG
#include <MIOStream.h>
#endif

MStatus cleanPerFaceAssignment::doIt( const MArgList& )
//
//	Description:
//		cleanPerfaceAssignment does the followings
//			1. It traces material connections
//			2. If multiple conections to one material is found, it combines them.
//			3. It changes the assignment order
//			4. it returns a string array that can be executed as a MEL command
//
//	Arguments:
//		NONE
//
//	Return Value:
//		It always returns MS:kSuccess as there's no error handling
//
{
	MStatus stat = MS::kSuccess;


	//  Get the selection list and using the iterator to trace them
	MSelectionList list;
	MGlobal::getActiveSelectionList( list );
	MItSelectionList listIt( list);

	for ( listIt.reset(); !listIt.isDone() ; listIt.next() ){

		// Get the DAG path of each element and extend the path to its shape node
		MDagPath path;
		listIt.getDagPath( path );
		path.extendToShape();
	
		// if a DAG Path is MFn::kMesh, it tries to obtain its material

		if ( path.apiType() == MFn::kMesh ){

			// Create a function set for poly geometries, find faces that are assigned as a set
			MFnMesh meshFn( path );
			MObjectArray sets, comps;

			unsigned int instanceNumber = path.instanceNumber();
			meshFn.getConnectedSetsAndMembers( instanceNumber, sets, comps, 1 );

#ifdef _DEBUG
			// Debug output
			for ( unsigned int i = 0; i < sets.length() ; i++ ){
				MFnSet setFn ( sets[i]);
				MItMeshPolygon faceIt( path, comps[i] );

				cout << "-------------->" << endl;
				cout << setFn.name() << endl;
				cout << "FaceCount:" << faceIt.count() << endl;
				for ( faceIt.reset() ; !faceIt.isDone() ; faceIt.next() ){
					cout << faceIt.index() << " ";
				}
				cout << endl;
				cout << "<--------------" << endl;
				
			}
#endif // _DEBUG


			// Variable declaration 
			MStringArray SGNames;		// Stores Shading Group name
			MIntArray sameConnectionFlag;	// a flag to check if Shading Group has same names
			MIntArray sameSGFaceCount;	// storoes the number of faces included in each shading group
			MStringArray memberFaceNames;	// stores names of faces included in each shading group

			sameConnectionFlag.clear();
			sameSGFaceCount.clear();

			// ------------------------------------------------
			// 1	Initialization
			// ------------------------------------------------

			// initialization of variables
			for ( unsigned int i = 0; i < sets.length() ; i++ ){

				// SGName Initialization
				//	Append Shading Group name
				MFnSet setFn ( sets[i] );
				SGNames.append( setFn.name() );

				// sameConnectionFlag Initialization
				//	set -1@
				sameConnectionFlag.append( -1 );
				
				
				// sameSGFaceCount Initialization
				//	Set the number of faces included in a Shading Group
				MItMeshPolygon tempFaceIt ( path, comps[i] );				
				sameSGFaceCount.append( tempFaceIt.count() );


				// memberFaceNames Initialization
				//	Stores names of faces included in a Shading Group in a way MEL can use
				MString aMemberFaceName;

				MString pathName = path.fullPathName();
				bool optimizeList = true; 
				if (optimizeList)
				{
					int lastIndices[2] = { -1, -1 };
					int currentIndex = -1;
					bool haveAFace = false;
					for ( ;!tempFaceIt.isDone() ; tempFaceIt.next() )
					{
						if (lastIndices[0] == -1)
						{
							lastIndices[0] = tempFaceIt.index();
							lastIndices[1] = tempFaceIt.index();
						}
						else
						{
							currentIndex = tempFaceIt.index();

							// Hit non-contiguous face #. split out a new string
							if (currentIndex > lastIndices[1] + 1)
							{
								aMemberFaceName += (pathName + ".f[" + lastIndices[0] + ":" + lastIndices[1] + "] ");
								lastIndices[0] = currentIndex;
								lastIndices[1] = currentIndex;
							}
							else
								lastIndices[1] = currentIndex;
						}
						haveAFace = true;
					}
					// Only one member. Add it.
					if (haveAFace)
					{
						aMemberFaceName += (pathName + ".f[" + lastIndices[0] + ":" + lastIndices[1] + "] ");
					}
				}
				else
				{
					for ( ;!tempFaceIt.isDone() ; tempFaceIt.next() ){
						aMemberFaceName += (path.fullPathName() + ".f[" + tempFaceIt.index() + "] ");
					}
				}
				memberFaceNames.append( aMemberFaceName );

			}
			
	
			// ------------------------------------------------
			// 2	Finding redundant connections
			// ------------------------------------------------

			// Scan for multiple connections to a shading group and if it exists
			//	combine them
			for ( unsigned int i = 0; i < sets.length() ; i++ ){
				MFnSet setFn ( sets[i]);
				if ( sameConnectionFlag[i] == -1 ){

					for ( unsigned int j = 0; j < sets.length() ; j++ ){
						if ( i != j && sameConnectionFlag[j] == -1 ) {
							MFnSet tempSetFn ( sets[j] );

							if ( setFn.name() == tempSetFn.name() ){
								sameConnectionFlag[j] = i;
								sameSGFaceCount[i] += sameSGFaceCount[j];
								sameSGFaceCount[j] = 0;
								memberFaceNames[i] += memberFaceNames[j];
								memberFaceNames[j] = "";
							}
						}

					}
					sameConnectionFlag[i] = i;
				}
			}

			// delete empties
			unsigned int tail = 0;
			while( tail < sameConnectionFlag.length() ){
				if ( sameSGFaceCount[tail] == 0 ){
					SGNames.remove( tail );
					memberFaceNames.remove( tail );
					sameConnectionFlag.remove( tail );
					sameSGFaceCount.remove( tail );

				} else {
					tail++;
				}
			}


			// ------------------------------------------------
			// 3	Sorting array
			// ------------------------------------------------

			// Sorting preprocess
			//		Simply assign ID from the top
			MIntArray sortMapper;
			for ( unsigned int i = 0; i < sameSGFaceCount.length() ; i++ ){
				sortMapper.append( i );
			}
			bool needToSort = true;
	        
			// Begin sorting
			//		in the order of number of faces 
			while ( needToSort ){
				unsigned int sortCount = 0;
				if ( sameSGFaceCount.length() > 1 ){
					for( unsigned int i = 0; i < ( sameSGFaceCount.length() -1 ) ; i++ ){
						if ( sameSGFaceCount[sortMapper[i]] < sameSGFaceCount[sortMapper[i+1]] ){
							int tmp = sortMapper[i];
							sortMapper[i] = sortMapper[i+1];
							sortMapper[i+1] = tmp;
							sortCount++;
						} else {

						}

					}
				}else {
					break;
				}

				
				// Check if sorting is completed
				if ( sortCount > 0 ){
					needToSort = true; //another sorting required
				} else {
					needToSort = false;//no sorting required and exit loop
				}


			}

			// store the sorted arrary

			//	Variable declaration
			MStringArray sortedSGNames;
			MStringArray sortedMemberFaceNames;
			MIntArray	sortedSGFaceCount;

			//	Store
			for ( unsigned int i = 0; i < sortMapper.length(); i++ ){
				sortedSGNames.append( SGNames[sortMapper[i]] );
				sortedSGFaceCount.append( sameSGFaceCount[sortMapper[i]] );
				sortedMemberFaceNames.append( memberFaceNames[sortMapper[i]]);
			}

			

#ifdef _DEBUG

			// Debug information output
			for ( unsigned int i = 0; i < sortedSGFaceCount.length() ; i++ ){
				cout << sortedSGNames[i] << "  " << sortedSGFaceCount[i] << endl;
				cout << sortedMemberFaceNames[i] << endl;
			}
			
			for ( unsigned int i = 0; i < sortMapper.length() ; i++ ){
				cout << i << " -- " << sortMapper[i] << endl;
			}
#endif // _DEBUG
			
			// ------------------------------------------------
			// 4	Output
			// ------------------------------------------------
			//	This outputs a MEL executable string array
			// 	This is to enable step by step excution when it can't be solved to normally 
			
			bool executeImmediate = true;
			for ( unsigned int i = 0; i < sortedSGFaceCount.length() ; i++ ){
				
				MString resultString;
				if ( i == 0 ){

					//@Add object base selection MEL command 
					resultString = ("select -r " + path.fullPathName()  +";" );
				} else {
					//@Add component base selection MEL command 
					resultString = ("select -r " + sortedMemberFaceNames[i] + ";");
				}

				if (executeImmediate )
					MGlobal::executeCommand( resultString );

				// Add a meterial assignemnet MEL comand 
				if (executeImmediate)
				{
					resultString = ("sets -e -forceElement " + sortedSGNames[i] + ";");
					MGlobal::executeCommand( resultString );
				}
				else
					resultString += ("sets -e -forceElement " + sortedSGNames[i] + ";");

				// Append to the result
				if (!executeImmediate)
					appendToResult( resultString );
			}

		}

	}
	return stat; // TODO: Error handling/Additional ASSERT requiredBCurrently it always returns MS::kSuccess
}


void* cleanPerFaceAssignment::creator()
//
//	Description:
//		this method exists to give Maya a way to create new objects
//      of this type. 
//
//	Return Value:
//		a new object of this type
//
{
	return new cleanPerFaceAssignment();
}

cleanPerFaceAssignment::cleanPerFaceAssignment()
//
//	Description:
//		cleanPerFaceAssignment constructor
//
{}

cleanPerFaceAssignment::~cleanPerFaceAssignment()
//
//	Description:
//		cleanPerFaceAssignment destructor
//
{
}


