/* 
 *  Generic example of master/worker program.  "Tasks" just consist of sleeping
 *  for a specified time; times to wait are generatedly randomly from 0 to a
 *  specified maximum.
 *
 *  Command-line arguments:  total tasks, maximum task time,
 *    (optional) "--verbose" for verbose output
 *
 *  Parallel version using OpenMP and static assignment of tasks to workers.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <omp.h>

/* a short function to print a message and exit */
void error_exit(char msg[]) {
    fprintf(stderr, "%s", msg);
    exit(EXIT_FAILURE);
}

/* function to generate a random integer in [start, end] */
int random_in_range(int start, int end) {
    /* use method described in "rand" man page */
    return start + ((double) (end-start+1) * rand()/(RAND_MAX + 1.0));
}

/* ---- main program ---- */

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

  int num_tasks;
  int max_task_time;

  double start_time, end_time;

  int i;
  int task_time;
  int total_task_time = 0;
  int max_thread_total_task_time;
  int min_thread_total_task_time;

  int verbose = 0;

  int nthreads;

  if (argc < 3) {
      error_exit("usage:  master-worker num_tasks max_task_time [--verbose]\n");
  }
  num_tasks = atoi(argv[1]);
  max_task_time = atoi(argv[2]);
  if (argc > 3)
      verbose = 1;
  max_thread_total_task_time = 0;
  min_thread_total_task_time = max_task_time * num_tasks;

  start_time = omp_get_wtime();

  #pragma omp parallel private(task_time)
  {
      int thread_total_task_time = 0;
      int thread_num_tasks = 0;
      int thread_ID = omp_get_thread_num();

      #pragma omp single
      {
          nthreads = omp_get_num_threads();
      }
      #pragma omp for schedule(static)
      for (i = 0; i < num_tasks; ++i) {

          #pragma omp critical
          {
              task_time = random_in_range(1, max_task_time);
          }
          thread_total_task_time += task_time;
          ++thread_num_tasks;
          if (verbose)
              printf("(thread %d) task time = %d\n", thread_ID, task_time);
          usleep(task_time * 1000);
      }
      printf("thread %d number of tasks = %d, total time = %d\n", thread_ID,
              thread_num_tasks, thread_total_task_time);
      #pragma omp critical
      {
          if (thread_total_task_time > max_thread_total_task_time)
              max_thread_total_task_time = thread_total_task_time;
          if (thread_total_task_time < min_thread_total_task_time)
              min_thread_total_task_time = thread_total_task_time;
          total_task_time += thread_total_task_time;
      }
  }

  end_time = omp_get_wtime();

  printf("\nOpenMP parallel version with %d threads\n", nthreads);
  printf("number of tasks = %d\n", num_tasks);
  printf("max task time = %d\n", max_task_time);
  printf("total task time = %d\n", total_task_time);
  printf("total task time in threads ranges from %d to %d\n",
          min_thread_total_task_time, max_thread_total_task_time);
  printf("running time = %g\n", end_time - start_time);

  return EXIT_SUCCESS;
}