/* Matrix matrix multiplication using manager-worker pattern */

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

#pragma TASS input 
int L_BOUND;
#pragma TASS input 
int M_BOUND;
#pragma TASS input 
int N_BOUND;
#pragma TASS input {L>=0 && L<=L_BOUND} 
int L; 
#pragma TASS input {M>=0 && M<=M_BOUND} 
int M; 
#pragma TASS input {N>=0 && N<=N_BOUND} 
int N;
#pragma TASS input 
double a[N][L]; 
#pragma TASS input 
double b[L][M]; 
#pragma TASS output 
double c[N][M];

int nprocs;
int myrank;

void vecmat(double vector[], double matrix[][], double result[]) {
	int j;
	int k;
	
	for (j = 0; j < M; j++) {
		result[j] = 0.0;
		for (k = 0; k < L; k++)
			result[j] += vector[k]*matrix[k][j];
	}
}

void manager() {
	int count;
	int i;
	int j;
	double tmp[M];
	MPI_Status status;
	
	for (count = 0; count < nprocs-1 && count < N; count++)
		MPI_Send(&a[count][0], L, MPI_DOUBLE, count + 1, count + 1, MPI_COMM_WORLD);
	for (i = 0; i < N; i++) {
		MPI_Recv(&tmp[0], M, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
		for (j = 0; j < M; j++)
			c[status.MPI_TAG - 1][j] = tmp[j];
		if (count < N) {
			MPI_Send(&a[count][0], L, MPI_DOUBLE, status.MPI_SOURCE, count + 1, MPI_COMM_WORLD);
			count++;
		}
	}
	for (i = 1; i < nprocs; i++)
		MPI_Send(NULL, 0, MPI_DOUBLE, i, 0, MPI_COMM_WORLD); // term signal
}

void worker() {
	double in[L];
	double out[M];
	MPI_Status status;
	
	MPI_Recv(&in[0], L, MPI_DOUBLE, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
	while (status.MPI_TAG != 0) {
		vecmat(in, b, out);
		MPI_Send(&out[0], M, MPI_DOUBLE, 0, status.MPI_TAG, MPI_COMM_WORLD);
		MPI_Recv(&in[0], L, MPI_DOUBLE, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
	}
}

void main() {
	int argc;
	char **argv;
	
	MPI_Init(&argc,&argv);
	MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
	MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
	if (myrank == 0)
		manager();
	else
		worker();
	MPI_Finalize();
}
