//-
// ==========================================================================
// 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 <math.h>

#include <maya/MIOStream.h>
#include <maya/MSimple.h>
#include <maya/MTimer.h>
#include <maya/MGlobal.h>
#include <maya/MThreadPool.h>

DeclareSimpleCommand( threadTestCmd, PLUGIN_COMPANY, "2008");

typedef struct _threadDataTag
{
	int threadNo;
	long primesFound;
	long start, end;

} threadData;

typedef struct _taskDataTag
{
	long start, end, totalPrimes;

} taskData;

#define NUM_TASKS	16

// No global information used in function
static bool TestForPrime(int val)
{
	int limit, factor = 3;
	limit = (long)(sqrtf((float)val)+0.5f);
	while( (factor <= limit) && (val % factor))
		factor ++;

	return (factor > limit);
}


// Primes finder. This function is called from multiple threads
MThreadRetVal Primes(void *data)
{
	threadData *myData = (threadData *)data;
	for( int i = myData->start + myData->threadNo*2; i <= myData->end; i += 2*NUM_TASKS )
	{
		if( TestForPrime(i) )
			myData->primesFound++;
	}
	return (MThreadRetVal)0;
}

// Function to create thread tasks
void DecomposePrimes(void *data, MThreadRootTask *root)
{
	taskData *taskD = (taskData *)data;
	
	threadData tdata[NUM_TASKS];

	for( int i = 0; i < NUM_TASKS; ++i )
	{
		tdata[i].threadNo    = i;
		tdata[i].primesFound = 0;
		tdata[i].start       = taskD->start;
		tdata[i].end         = taskD->end;

		MThreadPool::createTask(Primes, (void *)&tdata[i], root);
	}

	MThreadPool::executeAndJoin(root);

	for( int i = 0; i < NUM_TASKS; ++i )
	{
		taskD->totalPrimes += tdata[i].primesFound;
	}
}

// Single threaded calculation
int SerialPrimes(int start, int end)
{
	int primesFound = 0;
	for( int i = start; i <= end; i+=2)
	{
		if( TestForPrime(i) )
			primesFound++;
	}
	return primesFound;
}

// Set up and tear down parallel tasks
int ParallelPrimes(int start, int end)
{
	MStatus stat = MThreadPool::init();
	if( MStatus::kSuccess != stat ) {
		MString str = MString("Error creating threadpool");
		MGlobal::displayError(str);
		return 0;
	}

	taskData tdata;
	tdata.totalPrimes = 0;
	tdata.start       = start;
	tdata.end         = end;
	MThreadPool::newParallelRegion(DecomposePrimes, (void *)&tdata);

	// pool is reference counted. Release reference to current thread instance
	MThreadPool::release();

	// release reference to whole pool which deletes all threads
	MThreadPool::release();

	return tdata.totalPrimes;
}

// MSimple command that invokes the serial and parallel thread calculations
MStatus threadTestCmd::doIt( const MArgList& args )
{
	MString introStr = MString("Computation of primes using the Maya API");
	MGlobal::displayInfo(introStr);

	if(args.length() != 2) {
		MString str = MString("Invalid number of arguments, usage: threadTestCmd 1 10000");
		MGlobal::displayError(str);
		return MStatus::kFailure;
	}

	MStatus stat;

	int start = args.asInt( 0, &stat );
	if ( MS::kSuccess != stat ) {
		MString str = MString("Invalid argument 1, usage: threadTestCmd 1 10000");
		MGlobal::displayError(str);
		return MStatus::kFailure;
	}

	int end = args.asInt( 1, &stat );
	if ( MS::kSuccess != stat ) {
		MString str = MString("Invalid argument 2, usage: threadTestCmd 1 10000");
		MGlobal::displayError(str);
		return MStatus::kFailure;
	}

	// start search on an odd number
	if((start % 2) == 0 ) start++;

	// run single threaded
	MTimer timer;
	timer.beginTimer();
	int serialPrimes = SerialPrimes(start, end);
	timer.endTimer();
	double serialTime = timer.elapsedTime();

	// run multithreaded
	timer.beginTimer();
	int parallelPrimes = ParallelPrimes(start, end);
	timer.endTimer();
	double parallelTime = timer.elapsedTime();

	// check for correctness
	if ( serialPrimes != parallelPrimes ) {
		MString str("Error: Computations inconsistent");
		MGlobal::displayError(str);
		return MStatus::kFailure;
	}

	// print results
	if(parallelTime>0.0) {
	  double ratio = serialTime/parallelTime;
	  MString str = MString("\nElapsed time for serial computation: ") + serialTime + MString("s\n");
	  str += MString("Elapsed time for parallel computation: ") + parallelTime + MString("s\n");
	  str += MString("Speedup: ") + ratio + MString("x\n");
	  MGlobal::displayInfo(str);
	} else {
	  MString str = MString("\nParallel time zero, no scaling measurement possible\n");
	  MGlobal::displayInfo(str);
	}
	return MStatus::kSuccess;
}
