/*
 * 02-send-recv.c  —  Point-to-point: bouncing a number between two processes
 * Session 11 · 2311CSC501J Parallel Processing
 *
 * WHAT THIS SHOWS
 *   The two most fundamental MPI calls: MPI_Send and MPI_Recv. One process hands
 *   a value directly to another. Here rank 0 and rank 1 play PING-PONG: rank 0
 *   sends a counter to rank 1, rank 1 adds one and sends it back, and so on. The
 *   number bounces between them a few times.
 *
 *   This is the distributed-systems version of one service calling another (an
 *   RPC / a message on a queue). The sender BLOCKS conceptually until the message
 *   is handed off; the receiver BLOCKS until a matching message arrives.
 *
 * THE DEADLOCK TRAP (read this!)
 *   MPI_Send and MPI_Recv are BLOCKING. If BOTH processes call MPI_Recv first,
 *   each waits for a message that the other hasn't sent yet — they wait forever.
 *   That is a DEADLOCK. The rule of thumb: for every Send there must be a matching
 *   Recv, and you must order them so somebody sends before everybody receives.
 *   (Non-blocking MPI_Isend / MPI_Irecv, which return immediately and let you
 *   MPI_Wait later, are the usual way to avoid this — see the comment at the end.)
 *
 * HOW TO COMPILE & RUN  (needs EXACTLY 2 processes)
 *       mpicc 02-send-recv.c -o pingpong
 *       mpirun -np 2 ./pingpong
 *   See README.md for online / no-install options. Cannot run in a browser.
 */

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

#define ROUNDS 5          /* how many times the number bounces back and forth */
#define TAG    0          /* a message "label"; sender and receiver must agree */

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

    int rank, size;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    /* This example is written for precisely two players. Bail out politely
     * otherwise so nobody blocks forever waiting for a partner. */
    if (size != 2) {
        if (rank == 0)
            printf("Please run with exactly 2 processes: mpirun -np 2 ./pingpong\n");
        MPI_Finalize();
        return 0;
    }

    int partner = (rank == 0) ? 1 : 0;   /* the other process is my partner */
    int number  = 0;

    for (int round = 0; round < ROUNDS; round++) {
        if (rank == 0) {
            /* Rank 0 sends first, then waits for the reply. Order: Send, Recv. */
            number++;                                        /* our contribution */
            MPI_Send(&number, 1, MPI_INT, partner, TAG, MPI_COMM_WORLD);
            printf("[rank 0] sent %d to rank 1\n", number);

            MPI_Recv(&number, 1, MPI_INT, partner, TAG, MPI_COMM_WORLD,
                     MPI_STATUS_IGNORE);                     /* we don't inspect status */
            printf("[rank 0] got  %d back from rank 1\n", number);
        } else {
            /* Rank 1 receives first, then replies. Order: Recv, Send.
             * This opposite ordering is what prevents deadlock. */
            MPI_Recv(&number, 1, MPI_INT, partner, TAG, MPI_COMM_WORLD,
                     MPI_STATUS_IGNORE);
            printf("        [rank 1] got %d, adding 1 and sending back\n", number);
            number++;
            MPI_Send(&number, 1, MPI_INT, partner, TAG, MPI_COMM_WORLD);
        }
    }

    if (rank == 0)
        printf("[rank 0] final value after %d rounds = %d\n", ROUNDS, number);

    /*
     * Arguments of MPI_Send / MPI_Recv, for reference:
     *   MPI_Send(buffer, count, datatype, DEST_rank, tag, communicator)
     *   MPI_Recv(buffer, count, datatype, SOURCE_rank, tag, communicator, status)
     *
     * Non-blocking version (returns immediately, you MPI_Wait on the request):
     *   MPI_Request req;
     *   MPI_Isend(&number, 1, MPI_INT, partner, TAG, MPI_COMM_WORLD, &req);
     *   ... do other work ...
     *   MPI_Wait(&req, MPI_STATUS_IGNORE);
     * Non-blocking calls are how you overlap communication with computation and
     * sidestep the deadlock trap described above.
     */

    MPI_Finalize();
    return 0;
}
