// picalc-cs-mpi.cs
//

using System ;
using System.Runtime.InteropServices ;
using System.ComponentModel ;
using MPI ;

namespace picalc_cs_mpi
{
//
//
internal class HiPerfTimer
{
	[DllImport("Kernel32.dll")]
	private static extern bool QueryPerformanceCounter(out long lpPerformanceCount) ;

	[DllImport("Kernel32.dll")]
	private static extern bool QueryPerformanceFrequency(out long lpFrequency) ;

	private long startTime, stopTime, freq ;

	public HiPerfTimer()
	{
		startTime = 0 ;
		stopTime  = 0 ;

		if (QueryPerformanceFrequency(out freq) == false)
		{
			// High-performance counter not supported
			//
			throw new Win32Exception() ;
		}
	}

	// Start the timer
	//
	public void Start()
	{
		QueryPerformanceCounter(out startTime) ;
	}

	// Stop the timer
	//
	public void Stop()
	{
		QueryPerformanceCounter(out stopTime) ;
	}

	// Returns the duration of the timer (in seconds)

	public double Duration
	{
		get
		{
			return (double)(stopTime - startTime) / (double) freq ;
		}
	}
}

//
//
class Program
{
//
//
const long PICALC_MAX = (Int64.MaxValue - 9) / 10 ; // = 922337203685477579
const char PICALC_0_C = '0' ;
const char PICALC_9_C = '9' ;

// Usage.
//
private static void Usage()
{
	Console.WriteLine("picalc-cs-mpi.exe is a C# MPI implementation of an algorithm which approximates Pi.") ;
	Console.WriteLine("Usage:\nmpiexec -n numprocesses picalc-cs-mpi numintervals") ;
	Console.WriteLine("This example:\nmpiexec -n 4 picalc-cs-mpi 1E9\napproximates Pi over 1 billion intervals using MPI with 4 processes.") ;
	Console.WriteLine() ;
	return ;
}

// Parse input string number into a long.
//
private static long ParseNumIterations(string strNumIterations)
{
	if (null == strNumIterations)
	{
		return 0 ;
	}

	int iLen = strNumIterations.Length ;
	byte ucPos = 0 ;
	long i64NumIterations = 0 ;
	long i64Ten = 10 ;
	while (ucPos < Byte.MaxValue && ucPos < iLen && strNumIterations[ucPos] >= PICALC_0_C && strNumIterations[ucPos] <= PICALC_9_C)
	{
		i64NumIterations = i64Ten * i64NumIterations + (strNumIterations[ucPos] - PICALC_0_C) ;
		if (i64NumIterations > PICALC_MAX)
		{
			i64NumIterations = PICALC_MAX ;
			break ;
		}
		ucPos++ ;
	}

	if (ucPos < Byte.MaxValue && ucPos < iLen && (strNumIterations[ucPos] == 'e' || strNumIterations[ucPos] == 'E'))
	{
		ucPos++ ;
		long i64Exp = 0 ;
		while (ucPos < Byte.MaxValue && ucPos < iLen && strNumIterations[ucPos] >= PICALC_0_C && strNumIterations[ucPos] <= PICALC_9_C)
		{
			i64Exp = i64Ten * i64Exp + (strNumIterations[ucPos] - PICALC_0_C) ;
			ucPos++ ;
		}

		while (i64Exp > 0)
		{
			i64NumIterations *= i64Ten ;
			if (i64NumIterations > PICALC_MAX)
			{
				i64NumIterations = PICALC_MAX ;
				break ;
			}
			i64Exp-- ;
		}
	}

	return i64NumIterations ;
}

//
//
private static double CalcPiCSMPI(Intracommunicator comm,int iMPIRank,int iMPIWorld,long i64NumIterations)
{
	double		dX = 0.0 ;					// Width
	double		dY = 0.0 ;					// Height
	double		dXMid = 0.0 ;				// X mid-point
	long			i64Iteration = 0 ;	// Counter
	double[]	dChunk = {-1.0} ;		// MPI reduction send buffer for local Pi chunk
	double[]	dPi = {-1.0} ;			// MPI reduction return buffer for sum of Pi chunks = approximation of Pi

	// Pi = 3.141592653589793238462643...

	// From Rank = 0, send the number of iterations to run to all other processors.
	comm.Broadcast<long>(ref i64NumIterations,0) ;

	if (i64NumIterations > 0)
	{
		// Approximate the value of Pi.
		dX = 1.0 / (double)i64NumIterations ;

		for (i64Iteration = iMPIRank + 1; i64Iteration <= i64NumIterations; i64Iteration += iMPIWorld)
		{
			dXMid = dX * ((double)i64Iteration - 0.5) ;
			dY += (4.0 / (dXMid * dXMid + 1.0)) ;
		}

		dChunk[0] = dX * dY ;

		// Sum reduce all processors' dChunk values into dPi at Rank == 0.
		comm.Reduce<double>(dChunk,Operation<double>.Add,0,ref dPi) ;
	}

	return dPi[0] ;
}

//
//
public static int Main(string[] args)
{
	if (null == args || 1 != args.Length)
	{
		Usage() ;
		return 1 ;
	}

	// Initialize our MPI app.
	//
	using (new MPI.Environment(ref args))
	{
	// Start a line of communication.
	//
	Intracommunicator comm = Communicator.world ;
	comm.Barrier() ;

	// This process's rank.
	// Who are we in MPI land?
	//
	int iMPIRank = comm.Rank ;

	// The size of the world.
	//
	int iMPIWorld = comm.Size ;

	long i64NumIterations = 0 ;
	HiPerfTimer hpt = null ;

	if (iMPIRank == 0)
	{
		hpt = new HiPerfTimer() ;

		i64NumIterations = ParseNumIterations(args[0]) ;

		if (hpt != null)
		{
			hpt.Start() ;
		}
	}

	// Go
	//
	double dPi = CalcPiCSMPI(comm,iMPIRank,iMPIWorld,i64NumIterations) ;

	if (iMPIRank == 0)
	{
		if (hpt != null)
		{
			hpt.Stop() ;
		}
		Console.WriteLine("Pi={0:F15} ",dPi) ;
		Console.WriteLine("picalc-cs-mpi,NumMPIProcesses={0:d},NumIntervals={1:d},Duration={2:F6}",iMPIWorld,i64NumIterations,hpt.Duration) ;
	}

	// Stop the MPI
	//
	} // using (new MPI.Environment(ref args))

	return 0 ;
}

} // class Program
} // namespace picalc_cs_mpi

