/*************************************************************************
 * This file is part of the Distributed Computation feature
 *
 * Project created in conformity with the requirements for the Degree of
 * Master of Engineering in Software Engineering, Computing and Software
 * Department, McMaster University 2012
 *
 * Author:	David Kirby
 * Supervisor: Dr. Ryan Leduc
 ************************************************************************/
#include "CommHandler.h"

#include <QDialog>
#include <stdio.h>
#include <iostream>
#include <string>
#include "CommonDefinitions.h"
#include <QMessageBox>

#ifdef __ENABLE_DIST__

namespace DESpot
{
	HostList		CommHandler::hosts;
	bool			CommHandler::initialized		= false;
	pthread_mutex_t CommHandler::CommHandlerMutex	= PTHREAD_MUTEX_INITIALIZER;

//______________________________________________________________________________________________

	CommHandler::CommHandler(bool isslave, int maxhosts)
	{
		isSlave = isslave;		

		if (isslave)
		{
			checkInit();
			MPI_Comm_get_parent(&parent);
		}
		else
		{
			if (!initialized)
			{
				pthread_mutex_lock(&CommHandlerMutex);
			
				// ensure still not initialized
				if (!initialized)
				{	
					checkInit();					
				
					hosts = HostList(maxhosts);
					initialized = true;
				}

				pthread_mutex_unlock(&CommHandlerMutex);
			}
		}
	}

//______________________________________________________________________________________________

	void CommHandler::terminateSlaves()
	{
		if (!isSlave)
		{
			checkInit();

			// Kill all slave processes
			int numHosts = hosts.count();
			std::stringstream ss;
			ss << "null " << KillProcess;
			std::string data = ss.str();

			int strlen = data.length();

			char* str = (char*)(malloc(sizeof(char)*(strlen + 1)));
			if (str == null)
				throw new EX("Out of memory error");
			strcpy(str, data.c_str());
			str[strlen] = '\0';

			for (int i = 0; i < numHosts; i++)
			{
				Host* myHost = hosts.getHost();
				MPI_Comm* target = myHost->getHost(); 
		
				// send size of string first	
				pthread_mutex_lock(&CommHandlerMutex);			
				MPI_Send(&strlen, 1, MPI_INT, 0, 0, *target);
				pthread_mutex_unlock(&CommHandlerMutex);

				// send the data string
				pthread_mutex_lock(&CommHandlerMutex);
				MPI_Send(str, data.length(), MPI_CHAR, 0, 0, *target);				
				pthread_mutex_unlock(&CommHandlerMutex);
			}

			free(str);

			hosts.clearHostList();
			initialized = false;
		}
	}

//______________________________________________________________________________________________

	void CommHandler::closeConnection()
	{
		int flag = 0;
		MPI_Initialized(&flag);
		if (flag)
			MPI_Finalize();
	}

//______________________________________________________________________________________________

	// Ensures that MPI has been initialized, initializing it if it hasn't been
	void CommHandler::checkInit()
	{
		int flag = 0;
		MPI_Initialized(&flag);
		if (!flag)
			MPI_Init(NULL,NULL);
	}

//______________________________________________________________________________________________

	remoteCheck* CommHandler::sendRecv(std::string data)
	{
		// get a free host
		Host* myHost = hosts.getHost();

		MPI_Comm* target = myHost->getHost(); 

		// send the size of the string first
		int strlen = data.length();
		pthread_mutex_lock(&CommHandlerMutex);
		MPI_Send(&strlen, 1, MPI_INT, 0, 0, *target);
		pthread_mutex_unlock(&CommHandlerMutex);

		// send the data string
		char* str = (char*)(malloc(sizeof(char)*(strlen + 1)));
		if (str == null)
		{
			throw new EX("Out of memory error");
		}
		strcpy(str, data.c_str());
		str[strlen] = '\0';
		pthread_mutex_lock(&CommHandlerMutex);
		MPI_Send(str, strlen, MPI_CHAR, 0, 0, *target);
		pthread_mutex_unlock(&CommHandlerMutex);

		free(str);

		remoteCheck* cr = new remoteCheck();
		cr->errList = DesAlgo::ErrorList();

		MPI_Request request;

		// receive the result as an int. 0 = success, < 0 = error, > 0 BDD Check All result
		// a nonblocking receive is used here because, otherwise, the receive would hold the
		// lock until the slave process finished and passed back the result. Not so concurrent.
		pthread_mutex_lock(&CommHandlerMutex);	
		MPI_Irecv(&cr->result, 1, MPI_INT, 0, 0, *target, &request); // nonblocking MPI receive
		pthread_mutex_unlock(&CommHandlerMutex);

		// loop until the receive is finished
		int flag;
		do
		{
			pthread_mutex_lock(&CommHandlerMutex);
			MPI_Test(&request, &flag, MPI_STATUS_IGNORE);	// test to see if the receive is finished
			pthread_mutex_unlock(&CommHandlerMutex);
		} while (!flag);

		// get number of strings
		int numerrors;
		pthread_mutex_lock(&CommHandlerMutex);
		MPI_Recv(&numerrors, 1, MPI_INT, 0, 0, *target, MPI_STATUS_IGNORE);
		pthread_mutex_unlock(&CommHandlerMutex);

		// loop through to receive each error string and add to error list
		char* errstr;			

		for (int i = 0; i < numerrors; i++)
		{
			// get string length
			pthread_mutex_lock(&CommHandlerMutex);
			MPI_Recv(&strlen, 1, MPI_INT, 0, 0, *target, MPI_STATUS_IGNORE);
			pthread_mutex_unlock(&CommHandlerMutex);

			// allocate memory for the error string
			errstr = (char*)(malloc(sizeof(char)*(strlen+1)));

			if (errstr == null)
				throw new EX("Out of memory error");

			// receive string. nonblocking receive used so that exceptionally long strings
			// do not hold up the computations
			pthread_mutex_lock(&CommHandlerMutex);
			MPI_Irecv(errstr, strlen, MPI_CHAR, 0, 0, *target, &request);
			pthread_mutex_unlock(&CommHandlerMutex);

			do
			{
				pthread_mutex_lock(&CommHandlerMutex);
				MPI_Test(&request, &flag, MPI_STATUS_IGNORE);
				pthread_mutex_unlock(&CommHandlerMutex);
			} while (!flag);

			errstr[strlen] = '\0';

			// add error string to error list
			cr->errList.push_back(std::wstring(errstr, errstr + strlen));

			// deallocate memory from the error string
			free(errstr);
		}

		// free the host
		hosts.freeHost(myHost);

		return cr;
	}

//______________________________________________________________________________________________

	void CommHandler::send(int data)
	{
		// send the data
		MPI_Send(&data, 1, MPI_INT, 0, 0, parent);
	}

//______________________________________________________________________________________________

	void CommHandler::send(std::string data)
	{
		// send size of string first
		int strlen = data.length();
		MPI_Send(&strlen, 1, MPI_INT, 0, 0, parent);

		// send the data string
		char* str = (char*)(malloc(sizeof(char)*(strlen + 1)));
		if (str == null)
			throw new EX("Out of memory error");
		strcpy(str, data.c_str());
		str[strlen] = '\0';

		MPI_Send(str, strlen, MPI_CHAR, 0, 0, parent);

		free(str);
	}

//______________________________________________________________________________________________

	int CommHandler::recvInt()
	{
		int result;
		pthread_mutex_lock(&CommHandlerMutex);
		MPI_Recv(&result, 1, MPI_INT, 0, 0, parent, MPI_STATUS_IGNORE);
		pthread_mutex_unlock(&CommHandlerMutex);

		return result;
	}

//______________________________________________________________________________________________

	std::string CommHandler::recvString()
	{
		// get string length
		int strlen;
		MPI_Recv(&strlen, 1, MPI_INT, 0, 0, parent, MPI_STATUS_IGNORE);

		// allocate memory for the string
		char* c_str = (char*)(malloc(sizeof(char)*(strlen + 1)));

		if (c_str == null)
		{
			throw new EX("Out of memory error");
		}
		// receive string
		MPI_Recv(c_str, strlen, MPI_CHAR, 0, 0, parent, MPI_STATUS_IGNORE);
		c_str[strlen] = '\0';

		// convert char* to std::string
		std::string str = std::string(c_str, c_str + strlen);

		// deallocate memory from the string
		free(c_str);

		return str;
	}
//______________________________________________________________________________________________

	int CommHandler::getSlaveCount()
	{
		return hosts.count();
	}

}

#endif
