#include "iostream"
#include "stdlib.h"
#include <mpi.h>

//	this example demonstrates creating new communicators, groups and communicators with cartesian topology

//	processes are divided into 2 teams
//	they have cart coordinates like [0, n] and [1, n]
//	runners [m, 0] starts, m = 0, 1
//	runners [m, n-1] finishes, m = 0, 1

void run_S()
{
	double v = 0, S = 0, fullS = 1, dt = 0.1;
	while (S < fullS)
	{
		v = (double)(rand())/RAND_MAX;
		S = S + v * dt;
	}
}

int main(int argc, char ** argv)
{
	int myRank = 0, refereeRank = 0, size = 0, message = 0 /* it's a baton */, startMessage = 0;
	int coords[2];
	MPI_Group group, newGroup;					//	new groups, are used for creating new communicators
	MPI_Comm newComm, decartComm;	//	new communicators

	MPI_Init(&argc, &argv);

	MPI_Comm_size(MPI_COMM_WORLD, &size);
	MPI_Comm_rank(MPI_COMM_WORLD, &myRank);
	MPI_Comm_group(MPI_COMM_WORLD, &group);	//	creating group from MPI_COMM_WORLD communicator (this group includes all processes)

	MPI_Group_excl(group, 1, &refereeRank, &newGroup);	// excluding referee process from the group
	MPI_Comm_create(MPI_COMM_WORLD, newGroup, &newComm);	// creating new communicator without referee process
	int n = int((size - 1) / 2);	//	number of runners in every team
	int dims[2];
	int periods[2];
	dims[0] = 2; dims[1] = n;
	periods[0] = periods[1] = 0;
	if (myRank != refereeRank)
		MPI_Cart_create(newComm, 2, dims, periods, false, &decartComm);	// creating cartesian communicator

	if (myRank == refereeRank) {		//	referee's tasks
		MPI_Bcast(&startMessage, 1, MPI_INT, refereeRank, MPI_COMM_WORLD);	// referee sends start message to all processes in MPI_COMM_WORLD
		for (int  i = 0; i < 2; i++)
		{
			MPI_Recv(&coords, 2, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);	// referee registrates "finish" of every team
			std::cout << "REFEREE: player[" << coords[0] << ", " << coords[1] << "] has finished! " << std::endl;
		}
	}
	else
	{
		int myCartRank;
		int destinationCartRank;
		int sourceCartRank;
		int destinationCoords[2], sourceCoords[2];
		MPI_Comm_rank(decartComm, &myCartRank);	//	rank of the process in cartesian communicator
		MPI_Cart_coords(decartComm, myCartRank, 2, coords);	// cartesian coordinates of current process (runner)
		destinationCoords[0] = sourceCoords[0] = coords[0];	// cartesian coordinates of next runner and previous runner
		destinationCoords[1] = coords[1] + 1;
		sourceCoords[1] = coords[1] - 1;
		MPI_Bcast(&startMessage, 1, MPI_INT, refereeRank, MPI_COMM_WORLD);	// receives start message from referee
		if  (coords[1] == 0)	//	first runner
		{
			run_S();
			MPI_Cart_rank(decartComm, destinationCoords, &destinationCartRank);	// get rank with a cartesian coordinates (for send function)
			MPI_Send(&message, 1, MPI_INT, destinationCartRank, 0, decartComm);
			std::cout << "Player[" << coords[0] << ", " << coords[1] <<"] gave baton." << std::endl;
		}
		else
		if (coords[1] == n - 1)	//	last runner
		{
			MPI_Cart_rank(decartComm, sourceCoords, &sourceCartRank);
			MPI_Recv(&message, 1, MPI_INT, sourceCartRank, 0, decartComm, MPI_STATUS_IGNORE);
			run_S();
			MPI_Send(&coords, 2, MPI_INT, refereeRank, myRank, MPI_COMM_WORLD);	// send message to referee 
			std::cout << "Player[" << coords[0] << ", " << coords[1] << "] has finished! " << std::endl;	// finishes and writes it
		}
		else		//	all runners, excluding first and last
		{
			MPI_Cart_rank(decartComm, sourceCoords, &sourceCartRank);
			MPI_Recv(&message, 1, MPI_INT, sourceCartRank, 0, decartComm, MPI_STATUS_IGNORE);
			run_S();
			MPI_Cart_rank(decartComm, destinationCoords, &destinationCartRank);
			MPI_Send(&message, 1, MPI_INT, destinationCartRank, 0, decartComm);
			std::cout << "Player[" << coords[0] << ", " << coords[1] <<"] gave baton." << std::endl;
		}
	}

	MPI_Finalize();
	return 0;
}

