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

#include "configuration.h"

const char *verbosity_levels[] = {"OFF", "INFO", "DEBUG", "TRACE"};

const char *transmission_modes[] = {
   "Dynamic - MPI_Issend / MPI_Iprobe / MPI_Recv / MPI_Ibarrier",
   "Collective - MPI_Alltoall / MPI_Alltoallw",
   "Remote Memory Access (RMA) - MPI_Accumulate / MPI_Probe / MPI_Recv"
};

// --------------------------------------------- Helper function declarations

void conf_set_or_validate_ncells(conf_t *c);

long parse_long(const char *str);

int log_enabled(const conf_t *c, int lvl);

// ==========================================================================

void conf_init_default(conf_t *c)
{
   c->verbosity_level = INFO;
   c->ncells[0] = 0;
   c->ncells[1] = 0;
   c->debug_rank = -1;
   c->use_cartesian_topology = 0;
   c->transmission_mode = RMA;

   // Default values:
   c->n_iterations = 100;
   c->particles_per_cell_initial = 1<<14; // 16k particles
   c->delta_t = 1.0;
   c->min_mass = 1.0;
   c->max_mass = 1.0;
   c->max_force = 0.0;
   c->max_velocity_initial = 1.0; // 1 cell width per iteration
}

void conf_init_from_args(conf_t *c, int argc, char* argv[])
{
   int nprocs;
   struct option long_options[] = {
      {"verbose", required_argument, NULL, 'v'},
      {"debug-rank", required_argument, NULL, 'g'},

      {"use-cart-topo", no_argument, &c->use_cartesian_topology, 1},
      {"collective", no_argument, &c->transmission_mode, COLLECTIVE},
      {"dynamic", no_argument, &c->transmission_mode, DYNAMIC},
      {"rma", no_argument, &c->transmission_mode, RMA},

      {"ncells-x", required_argument, NULL, 'x'},
      {"ncells-y", required_argument, NULL, 'y'},

      {"iterations", required_argument, NULL, 'i'},
      {"particles-per-cell", required_argument, NULL, 'n'},
      {"particles-total", required_argument, NULL, 'N'},
      {"delta-t", required_argument, NULL, 't'},
      {"min-mass", required_argument, NULL, 'm'},
      {"max-mass", required_argument, NULL, 'M'},
      {"max-force", required_argument, NULL, 'F'},
      {"max-velocity", required_argument, NULL, 'V'},
      {NULL, 0, NULL, 0}
   };

   MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
   conf_init_default(c);

   int option;
   while((option = getopt_long(argc, argv, "v:g:x:y:i:n:N:t:m:M:F:V:", long_options, NULL)) >= 0) {
      switch(option) {
         case 'v': c->verbosity_level = atoi(optarg); break;
         case 'g': c->debug_rank = atoi(optarg); break;
         case 'x': c->ncells[0] = atoi(optarg); break;
         case 'y': c->ncells[1] = atoi(optarg); break;
         case 'i': c->n_iterations = parse_long(optarg); break;
         case 'n': c->particles_per_cell_initial = parse_long(optarg); break;
         case 'N': c->particles_per_cell_initial = parse_long(optarg) / nprocs; break;
         case 't': c->delta_t = atof(optarg); break;
         case 'm': c->min_mass = atof(optarg); break;
         case 'M': c->max_mass = atof(optarg); break;
         case 'F': c->max_force = atof(optarg); break;
         case 'V': c->max_velocity_initial = atof(optarg); break;
      }
   }

   conf_set_or_validate_ncells(c);
}

void conf_print(const conf_t *c, FILE *f)
{
   int i = c->verbosity_level;
   if(i < 0) i = 0; else if(i > 3) i = 3;

   fprintf(f, "Configuration:\n");
   fprintf(f, " * Verbosity level:         %s (%d)\n", verbosity_levels[i], c->verbosity_level);
   fprintf(f, " * Use MPI Cart. Topology:  %s\n", c->use_cartesian_topology ? "YES" : "NO");
   fprintf(f, " * Transmission mode:       %s\n", transmission_modes[c->transmission_mode]);
   fprintf(f, " * Number of cells:         %d x %d\n", c->ncells[0], c->ncells[1]);
   fprintf(f, " * Particles per cell:      %d\n", c->particles_per_cell_initial);
   fprintf(f, " * Number of iterations:    %d\n", c->n_iterations);
   fprintf(f, " * Init. Velocity max:      %f\n", c->max_velocity_initial);
   fprintf(f, " * Timestep:                %f\n", c->delta_t);
   fprintf(f, " * Particle mass (min-max): %f - %f\n", c->min_mass, c->max_mass);
   fprintf(f, " * Max. random force:       %f\n", c->max_force);
   fprintf(f, "\n");
}

void conf_set_or_validate_ncells(conf_t *c)
{
   int nprocs, rc;

   MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
   rc = MPI_Dims_create(nprocs, 2, c->ncells);

   if(rc != MPI_SUCCESS) {
      fprintf(stderr, "Cannot create 2D Cartesian grid with specified number of processes in x or y direction.\n");
      MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
   }
}

int log_enabled(const conf_t *c, int lvl)
{
   static int rank = -1;
   if(rank < 0) {
      MPI_Comm_rank(MPI_COMM_WORLD, &rank);
   }

   return c->verbosity_level >= lvl && (rank == 0 || rank == c->debug_rank);
}

int info_enabled(const conf_t *c)
{
   return log_enabled(c, INFO);
}

int debug_enabled(const conf_t *c)
{
   return log_enabled(c, DEBUG);
}

int trace_enabled(const conf_t *c)
{
   return log_enabled(c, TRACE);
}

// --------------------------------------------------------- Helper functions

long parse_long(const char *str)
{
   char* p;
   long result = strtol(str, &p, 10);
   if(*p == 'k') {
      result <<= 10;
   } else if(*p == 'M') {
      result <<= 20;
   }
   return result;
}

