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

#define USE_DERIVED_DATATYPE 1

void PrintMPIError(int err, int my_rank) {
	int eclass, len;
	char estring[MPI_MAX_ERROR_STRING];

	MPI_Error_class(err, &eclass);
	MPI_Error_string(err, estring, &len);
	printf("P%d Error %d: %s\n", my_rank, eclass, estring);fflush(stdout);
}

void MakePacket(int *buffer1, double *buffer2, int elems, int my_rank, MPI_Datatype *new_type) {
	int k, err, lblq[2];
	MPI_Aint dspl[2];
	MPI_Datatype type[2];
		
	type[0] = MPI_INT   ; lblq[0] = elems ; MPI_Get_address(buffer1, &dspl[0]);
	type[1] = MPI_DOUBLE; lblq[1] = elems ; MPI_Get_address(buffer2, &dspl[1]);
	for (k=1; k>=0; k--) dspl[k] -= dspl[0]; 
	// Creation of the packet
	err = MPI_Type_create_struct (2, lblq, dspl, type, new_type);
	if (err != MPI_SUCCESS) PrintMPIError(err, my_rank);
	err = MPI_Type_commit(new_type);
	if (err != MPI_SUCCESS) PrintMPIError(err, my_rank);
}

int main(int argc, char* argv[])
{
    MPI_Init(&argc, &argv);

    // Size of the default communicator
    int size, my_rank;
    int tag, elems, use_derived, err;
    MPI_Datatype new_type;
    tag = 210;
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    char processor_name[MPI_MAX_PROCESSOR_NAME];
    int name_len;
    MPI_Get_processor_name(processor_name, &name_len);

    use_derived = USE_DERIVED_DATATYPE;
    // Get my rank and do the corresponding job
    switch(my_rank)
    {
        case 0:
        { 
	    printf("P%d at node %s\n", my_rank, processor_name);
	    elems = 752;
            int *buffer1 = (int*) malloc(elems * sizeof(int));
            double *buffer2 = (double*) malloc(elems * sizeof(double));

	    if(use_derived)
	    {
              MakePacket(buffer1, buffer2, elems, my_rank, &new_type);
              err = MPI_Send(buffer1, 1, new_type, 2, tag, MPI_COMM_WORLD);
	      if (err != MPI_SUCCESS) PrintMPIError(err, my_rank);
	      sleep(1);
              err = MPI_Send(buffer1, 1, new_type, 1, tag, MPI_COMM_WORLD);
	      if (err != MPI_SUCCESS) PrintMPIError(err, my_rank);
	      err = MPI_Type_free(&new_type);
	      if (err != MPI_SUCCESS) PrintMPIError(err, my_rank);
	    } else 
	    {
              MPI_Send(buffer1, elems, MPI_INT, 2, tag, MPI_COMM_WORLD);
	      sleep(1);
              MPI_Send(buffer1, elems, MPI_INT, 1, tag, MPI_COMM_WORLD);
	    }
            break;
        }
        case 1: // This rank is in a different machine than rank 0
	    sleep(2);
        case 2: // This rank is in the same machine than rank 0
        {
            printf("P%d at node %s\n", my_rank, processor_name);

            // Retrieve information about the incoming message
            MPI_Status status;
            err = MPI_Probe(0, tag, MPI_COMM_WORLD, &status);
	    if (err != MPI_SUCCESS) PrintMPIError(err, my_rank);

            // Get the number of elements in the message probed
            int count, tam_int, tam_double;
	    if(use_derived)
	    {
	      err = MPI_Get_count(&status, MPI_BYTE, &count);
	      if (err != MPI_SUCCESS) PrintMPIError(err, my_rank);
	      err = MPI_Type_size(MPI_INT, &tam_int);
	      if (err != MPI_SUCCESS) PrintMPIError(err, my_rank);
	      err = MPI_Type_size(MPI_DOUBLE, &tam_double);
	      if (err != MPI_SUCCESS) PrintMPIError(err, my_rank);
	      elems = count / (tam_int + tam_double);
//	      elems = 752;
	    }
	    else
	    {
	      MPI_Get_count(&status, MPI_INT, &count);
	      elems=count;
	    }

	    printf("P%d: Total expected counts=%d elems=%d\n", my_rank, count, elems);
            // Allocate the buffer now that we know how many elements there are
            int* buffer1 = (int*) malloc(elems * sizeof(int));
            double *buffer2 = (double*) malloc(elems * sizeof(double));

	    // Receive the message
	    if(use_derived)
	    {
              MakePacket(buffer1, buffer2, elems, my_rank, &new_type);
              err = MPI_Recv(buffer1, 1, new_type, 0, tag, MPI_COMM_WORLD, &status);
	      if (err != MPI_SUCCESS) PrintMPIError(err, my_rank);
	      err = MPI_Type_free(&new_type);
	      if (err != MPI_SUCCESS) PrintMPIError(err, my_rank);
	    }
	    else
	    { 
              MPI_Recv(buffer1, elems, MPI_INT, 0, tag, MPI_COMM_WORLD, &status);
	    }
            printf("P%d: received message with %d elements\n", my_rank, elems);
            break;
        }
	default:
	{
	    break;
	}
    }

    MPI_Finalize();
    return EXIT_SUCCESS;
}
