#include <malloc.h>
#include <stdio.h>
#include "mpi.h"

#define MAXSIZE 100

#define VECTORTAG 1
#define MATRIXTAG 2
#define DIETAG    0
#define RESULTTAG 3



//the manager process
static void manager(int dim)
{
	int output[MAXSIZE][2];//output vector is stored here
	int size,rank;// group size and individual rank
	int k=0; 
	int i,j;
	MPI_Comm_size(MPI_COMM_WORLD,&size);//determine total no of processes
	int matrix[100][100];//input matrix filled from file
	int vector[100];//input vector filled from file
	int result;//stores individual output vector item
 	MPI_Status status;
	int flag;

	int ind;
	FILE *in;
	int rowSentLast;// keep track which row should be sent next
	


	if ( (in=fopen("vector100.dat","r"))==NULL)
	{
		printf("Can not open vector input file\n");
		exit(0);
	}
	ind=0;
	//read vector from file
	while(!feof(in))
	{	
		fscanf(in,"%d",&vector[ind]);
		ind++;
	}		
	fclose(in);
	printf("Manager:The Vector:\n");
	for(i=0;i<dim;i++)
	{
		printf("%d\t",vector[i]);
	}
	printf("\n");

	if ((in=fopen("matrix100.dat","r"))==NULL)
	{
		printf("Can not open matrix input file");
		exit(0);
	}
	i=0;
	j=0;
	printf("\n");
	//read matrix from file to matrix[][] array
	while(!feof(in))
	{
		fscanf(in,"%d",&matrix[i][j]);		
		j++;
		if (j==dim)
		{
			i=i+1;
			j=0;
			printf("\n");
		}
	}
	fclose(in);

	printf("\nManager: The matrix:\n");
	i=0;
	j=0;
	for(i=0;i<dim;i++)		
	{
		for(j=0;j<dim;j++)
		{
			printf("%d\t",matrix[i][j]);
		}
		printf("\n");
	}

	//send the vector to all worker
	//send one row serially to the workers
	for(i=1;i<size;i++)
	{
		flag=MPI_Send(&vector,dim,MPI_INT,i,VECTORTAG,MPI_COMM_WORLD);
		if (flag==MPI_SUCCESS) printf ("Manager:Vector sent to %d\n",i);
		else printf ("Manager:Vector sent to %d is failed\n",i);
		flag=MPI_Send(&matrix[i-1],dim,MPI_INT,i,MATRIXTAG,MPI_COMM_WORLD);
		if (flag==MPI_SUCCESS)
		{
		 printf ("Manager:%d TH row sent to %d \n",i-1,i);
		 output[k][0]=i;
		 output[k][1]=0;
		 k++;
		}
		else printf ("Manager:%d th row sent to %d is failed\n",i-1,i);

	}
	rowSentLast=i-2;
	//wait upto a worker finishes, collect result,send the next row of matrix to that worker 
	while(rowSentLast<dim-1)
	{
																flag=MPI_Recv(&result,1,MPI_INT,MPI_ANY_SOURCE,RESULTTAG,MPI_COMM_WORLD,	
		&status);
		if (flag==MPI_SUCCESS){
			 printf("Manager:result from %d\t=%d\n",status.MPI_SOURCE,result);
			int l;
			for(l=0;l<dim;l++)
			{
				if ((output[l][0]==status.MPI_SOURCE) && (output[l][1]==0))
				{
					output[l][1]=result;
					
				}

			}

			
		}
		else 
		{
			printf("Manager:Problem in receiving result from %d \n",status.MPI_SOURCE);
		}
		flag=MPI_Send(&matrix[++rowSentLast],dim,MPI_INT,status.MPI_SOURCE,
		MATRIXTAG,MPI_COMM_WORLD);
		if (flag==MPI_SUCCESS)
		{
			printf("Manager:On request %d th row is sent to %d\n",rowSentLast,status.MPI_SOURCE);
			output[k][0]=status.MPI_SOURCE;
		 	output[k][1]=0;
		 	k++;
		}
	}
	//receiving the last cycle of result
	printf("Manager:receiving finishing results\n");
	for(i=1;i<size;i++)
	{
		
		flag=MPI_Recv(&result,1,MPI_INT,MPI_ANY_SOURCE,RESULTTAG,MPI_COMM_WORLD,	
		&status);			
		if (flag==MPI_SUCCESS) 
		{
			printf("Manager: result received from %d=%d\n",status.MPI_SOURCE,result);
			int l;
			for(l=0;l<dim;l++)
			{
				if ((output[l][0]==status.MPI_SOURCE) && (output[l][1]==0))
				{
					output[l][1]=result;
					
				}

			}

		}
		else{
			printf("Manager:Error receiving finishing result\n");
		}
	}
	//sending die tags to the workers
	printf("Manager:Sending Die Tag\n");
	for(i=1;i<size;i++)
	{
		MPI_Send(0,0,MPI_INT,i,DIETAG,MPI_COMM_WORLD);	
	}
	printf("Manager:Finished sending rows\n");
	
	int kk;
	
	//print output vector
	for(kk=0;kk<dim;kk++)
	{
		if (kk==0)
			printf("Output Vector\n");
		printf("%d\n",output[kk][1]);

	}


}

//the worker processes
static void worker(int dim)
{
	int result=0,i;
	int vector[MAXSIZE];
	int lr[MAXSIZE];//to store row sent by manager
	MPI_Status status;
	int flag;
	int rank;
	MPI_Comm_rank(MPI_COMM_WORLD,&rank);
	//receive vector from manager
	flag =MPI_Recv(&vector,dim,MPI_INT,0,VECTORTAG,MPI_COMM_WORLD,&status);
	if (flag==MPI_SUCCESS)
	{
		printf("worker%d: received vector\n",rank);
	}

	//receive a row from manager, calculate result, send result to manager
	for(;;)
	{
		flag=MPI_Recv(&lr,dim,MPI_INT,0,MPI_ANY_TAG,MPI_COMM_WORLD,&status);
		if (flag==MPI_SUCCESS)
		{
			printf("worker%d: received a row:\n ",rank);
			for(i=0;i<dim;i++) printf("%d\t",lr[i]); printf("\n");
		}
		if (status.MPI_TAG==DIETAG)
		{
			printf("worker%d:died\n",rank);
			return;
		}
		result=0;
		for(i=0;i<dim;i++)
		{
			result+=vector[i]*lr[i];

		}
		flag=MPI_Send(&result,1,MPI_INT,0,RESULTTAG,MPI_COMM_WORLD);
		if (flag==MPI_SUCCESS)
		{
			printf("worker%d: sends a result %d\n",rank,result);
		}
		else
		{
			printf("worker%d:result send failed",rank);
		}
	}
}


int main(int argc, char **argv)
{
	
	int dim=0;
	int size,rank;
	MPI_Init(&argc,&argv);
	MPI_Comm_size(MPI_COMM_WORLD,&size);//get total no of processes in size
	MPI_Comm_rank(MPI_COMM_WORLD,&rank);//get processor rank individually in rank
	dim =atoi(argv[1]);
	if (rank==0)
	{
		
		
		manager(dim); //manager process
	}
	else{
		
		worker(dim);//worker processes
	}

	MPI_Finalize();

}
