/*
 * 02-sections.c  —  Task parallelism with #pragma omp sections
 * Session 10 · 2311CSC501J Parallel Processing · Unit IV — OpenMP in Depth
 *
 * WHAT THIS SHOWS
 *   #pragma omp for is DATA parallelism: the same loop body, split over data.
 *   #pragma omp sections is TASK parallelism: several DIFFERENT blocks of code
 *   that are independent, run at the same time — one thread per section.
 *
 *   Think of a kitchen: one cook chops vegetables, another boils rice, another
 *   fries the paneer — three different jobs happening at once. That is exactly
 *   what sections express. Here we pretend to (a) load data, (b) render a
 *   report, and (c) send an email — three unrelated tasks that can overlap.
 *
 *   Structure:
 *       #pragma omp parallel      -> fork a team of threads
 *       {
 *           #pragma omp sections  -> the team splits up the sections below
 *           {
 *               #pragma omp section   { taskA }   // one thread runs this
 *               #pragma omp section   { taskB }   // another thread runs this
 *               #pragma omp section   { taskC }   // another thread runs this
 *           }   // implicit barrier: all sections finish before we continue
 *       }
 *
 *   Notes worth remembering:
 *     - Each section runs on exactly ONE thread. If you have more sections than
 *       threads, some threads run more than one section, one after another.
 *     - There is an implicit barrier at the end of the sections block, so code
 *       after it only runs once all three tasks are done. Add nowait to remove
 *       that barrier if you do not need to wait.
 *     - #pragma omp single / master run a block on just one thread — handy for
 *       one-off setup inside a parallel region (shown at the end).
 *
 * HOW TO COMPILE & RUN
 *       gcc -fopenmp 02-sections.c -o sections
 *       OMP_NUM_THREADS=3 ./sections
 *
 *   macOS (Apple clang): clang -Xpreprocessor -fopenmp -lomp 02-sections.c -o sections
 *   Or paste into OnlineGDB / godbolt.org (add -fopenmp) — no install needed.
 */

#include <stdio.h>
#include <omp.h>

/* A stand-in for "real" work: spin briefly so each task takes a moment. */
static void do_work(const char *name, int spins) {
    double t0 = omp_get_wtime();
    volatile long acc = 0;                 /* volatile so it is not optimized out */
    for (long k = 0; k < (long)spins * 20000000L; k++) acc += k & 3;
    double t1 = omp_get_wtime();
    printf("  [%s] ran on thread %d for %.3f s\n",
           name, omp_get_thread_num(), t1 - t0);
}

int main(void) {
    printf("Three DIFFERENT tasks, run in parallel with omp sections.\n");
    printf("(Give the team at least 3 threads: OMP_NUM_THREADS=3)\n\n");

    double start = omp_get_wtime();

    #pragma omp parallel
    {
        #pragma omp sections
        {
            #pragma omp section
            {
                do_work("load-data ", 3);   /* Task A */
            }

            #pragma omp section
            {
                do_work("render-rpt", 2);   /* Task B */
            }

            #pragma omp section
            {
                do_work("send-email", 2);   /* Task C */
            }
        }   /* <-- implicit barrier: we wait here for all three to finish */

        /* single: exactly one thread prints this, once, after the barrier. */
        #pragma omp single
        {
            printf("\n  [single] all three tasks complete — thread %d reports in.\n",
                   omp_get_thread_num());
        }
    }

    double end = omp_get_wtime();

    printf("\nWall-clock time for all three tasks: %.3f s\n", end - start);
    printf("Run serially they would have summed their times; in parallel the\n");
    printf("total is close to the LONGEST single task — that is the task-parallel win.\n");
    return 0;
}
