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

//#define DEBUG

#define LOOP 100        // Number of different message sizes
#define STRIDE 10000    // Difference between the message sizes
#define REPEAT 7        // Number of times to repeat aREPEAT measurement

const int data_size = LOOP * STRIDE;

/*
 * The master task initiates the transport of data and waits for it to come
 * back. The time between sending and receiving is measured and printed.
 */
void master(int tasks) {
    double *received = malloc(data_size * sizeof(double)),
           *sent = malloc(data_size * sizeof(double)),
           avg_time, min_time, start_time, time;
    MPI_Status status;
    int i, size, measurement, failures;

    // generate data to send
    for( i = 0; i < data_size; i++ )
        sent[i] = (double)i;

    #ifdef DEBUG
    printf("Using a ring of %d nodes, time precision is %fs, unit size is %ld "
           "bytes.\n", tasks, MPI_Wtick(), sizeof(double));
    #endif

    // Increase the message length with a fixed STRIDE
    for( size = STRIDE; size <= data_size; size += STRIDE ) {
        // Repeat the measurement and average the results
        avg_time = .0;
        min_time = INFINITY;

        for( measurement = 1; measurement <= REPEAT; measurement++ ) {
            #ifdef DEBUG
            printf("Measurement %d: ", measurement);
            #endif
            start_time = MPI_Wtime();

            MPI_Send(sent, size, MPI_DOUBLE, 1, 0, MPI_COMM_WORLD);
            MPI_Recv(received, size, MPI_DOUBLE, tasks - 1, MPI_ANY_TAG,
                    MPI_COMM_WORLD, &status);

            time = MPI_Wtime() - start_time;

            for( i = failures = 0; i < size; i++ )
                if( received[i] != sent[i] )
                    failures++;

            #ifdef DEBUG
            printf("%fs (%d failures)\n", time, failures);
            #endif
            avg_time += time;

            if( time < min_time )
                min_time = time;
        }

        // Divide by node count now instead of inside the loop to preserve
        // maximum accuracy
        avg_time /= REPEAT * tasks;
        min_time /= tasks;

        #ifdef DEBUG
        printf("Average and minimum time for size %d: %fs, %fs\n", size,
               avg_time, min_time);
        #else
        printf("%d,%f\n", size, min_time);
        #endif
    }

    free(received);
    free(sent);
}

/*
 * A slave task passes the data to the next task in the ring.
 */
void slave(int tasks, int rank) {
    double *data = malloc(data_size * sizeof(double));
    MPI_Status status;
    int size, measurement,
        source = (rank - 1 + tasks) % tasks, target = (rank + 1) % tasks;

    // Increase the message length with a fixed STRIDE
    for( size = STRIDE; size <= data_size; size += STRIDE ) {
        // Repeat the measurement
        for( measurement = 1; measurement <= REPEAT; measurement++ ) {
            MPI_Recv(data, size, MPI_DOUBLE, source, MPI_ANY_TAG,
                    MPI_COMM_WORLD, &status);
            MPI_Send(data, size, MPI_DOUBLE, target, 0, MPI_COMM_WORLD);
        }
    }

    free(data);
}

int main(int argc, char **argv) {
    int tasks, rank, error;

    // Initialize adnd setup MPI
    if( (error = MPI_Init(&argc, &argv)) != MPI_SUCCESS ) {
        printf("MPI_init failed (error %d)\n", error);
        MPI_Abort(MPI_COMM_WORLD, error);
        return EXIT_FAILURE;
    }

    MPI_Comm_size(MPI_COMM_WORLD, &tasks);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    // Execute the measurements
    if( !rank )
        master(tasks);
    else
        slave(tasks, rank);

    // Finalize mPI
    MPI_Finalize();

    return EXIT_SUCCESS;
}
