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

#include "configuration.h"
#include "box.h"

// --------------------------------------------- Helper function declarations
long parse_long(const char *str);

void set_ncells_per_proc(conf_t *c, double ncells_per_proc);
void set_ncells_total(conf_t *c, double ncells_total);
void set_global_domain(conf_t *c, int cube_edge_length);

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

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

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

void conf_init(conf_t *c)
{
   // Default number of cells (mesh elements) per proces: 1 * 2^14 = 16k
   double ncells_per_proc_avg = 0x1p14;

   c->verbosity_level = INFO;
   c->debug_rank = -1;
#if MPI_VERSION >= 3
   c->transfer_mode = SPARSE_COLLECTIVE;
#else
#pragma message("MPI_VERSION < 3 => Default transfer mode for halo exchange will be collective.")
   c->transfer_mode = COLLECTIVE;
#endif
   c->halo_width = 1;
   c->n_iterations = 100;

   // Default values:
   set_ncells_per_proc(c, ncells_per_proc_avg);
}

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

      {"ncells-per-proc", required_argument, NULL, 'n'},
      {"ncells", required_argument, NULL, 'N'},
      {"edge-length", required_argument, NULL, 'e'},
      {"halo-width", required_argument, NULL, 'w'},

      {"graph",      no_argument, &c->transfer_mode, SPARSE_COLLECTIVE},
      {"collective", no_argument, &c->transfer_mode, COLLECTIVE},
      {"p2p",        no_argument, &c->transfer_mode, P2P_DEFAULT},
      {"p2p-sync",   no_argument, &c->transfer_mode, P2P_SYNCHRONOUS},
      {"p2p-ready",  no_argument, &c->transfer_mode, P2P_READY},

      {"iterations", required_argument, NULL, 'i'},
      {NULL, 0, NULL, 0}
   };

   int option;
   while((option = getopt_long(argc, argv, "v:g:n:N:e:w:i:", long_options, NULL)) >= 0) {
      switch(option) {
         case 'v': c->verbosity_level = atoi(optarg); break;
         case 'g': c->debug_rank = atoi(optarg); break;
         case 'n': set_ncells_per_proc(c, parse_long(optarg)); break;
         case 'N': set_ncells_total(c, parse_long(optarg)); break;
         case 'e': set_global_domain(c, atoi(optarg)); break;
         case 'w': c->halo_width = atoi(optarg); break;
         case 'i': c->n_iterations = parse_long(optarg); break;
      }
   }
   return NULL;
}

void conf_print(const conf_t *c, FILE *f)
{
   char *transfer_mode_strs[] = {
      "Sparse collective - MPI_Neighbor_alltoallw",
      "Collective - MPI_Alltoallw",
      "Point-to-point - MPI_Isend / MPI_Irecv",
      "Point-to-point; synchronous mode - MPI_Issend / MPI_Irecv",
      "Point-to-point; ready mode - MPI_Irsend / MPI_Irecv"
   };
   char buf[128];
   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, " * Mesh domain (cube):      %s\n", box_to_string(&c->global_domain, buf, 128));
   fprintf(f, " * Halo width:              %d cell%s\n", c->halo_width, c->halo_width == 1 ? "" : "s");
   fprintf(f, " * Halo transfer mode:      %s\n", transfer_mode_strs[c->transfer_mode]);
   fprintf(f, " * Number of iterations:    %d\n", c->n_iterations);
   fprintf(f, "\n");
}

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

void set_ncells_per_proc(conf_t *c, double ncells_per_proc)
{
   int nprocs;
   MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
   set_ncells_total(c, ncells_per_proc * nprocs);   
}

void set_ncells_total(conf_t *c, double ncells_total)
{
   double edge_length_exact = cbrt(ncells_total);
   set_global_domain(c, (int)round(edge_length_exact));    
}

void set_global_domain(conf_t *c, int cube_edge_length)
{
   box_t *cube = &c->global_domain;
   int i;
   for(i = 0; i < N_DIMS; i++) {
      cube->coords[i] = 0;
      cube->extents[i] = cube_edge_length;
   }
   box_set_volume(cube);
}

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;
}

