#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <stdlib.h>

#include "cdipio.h"
#include "error.h"
#include "pio_util.h"

static int
cdiPioCSRSelectRange(MPI_Comm commSuper, int IOMode,
                     int clientRangeStart, int clientRangeEnd,
                     int specialRank);

int
cdiPioCSRLastN(MPI_Comm commSuper, int IOMode, int nProcsIO)
{
  int commSize;
  xmpi(MPI_Comm_size(commSuper, &commSize));
  return cdiPioCSRSelectRange(commSuper, IOMode, 0, commSize - nProcsIO - 1,
                              commSize - 1);
}

int
cdiPioCSRFirstN(MPI_Comm commSuper, int IOMode, int nProcsIO)
{
  int commSize;
  xmpi(MPI_Comm_size(commSuper, &commSize));
  return cdiPioCSRSelectRange(commSuper, IOMode, nProcsIO, commSize - 1,
                              nProcsIO - 1);
}

static int
cdiPioCSRSelectRange(MPI_Comm commSuper, int IOMode,
                     int clientRangeStart, int clientRangeEnd,
                     int specialRank)
{
  int commRank, commSize;
  xmpi(MPI_Comm_size(commSuper, &commSize));
  xmpi(MPI_Comm_rank(commSuper, &commRank));
  int role;
  switch (IOMode)
  {
  case PIO_NONE:
    xassert(commRank == 0 && commSize == 1);
    role = PIO_NONE;
    break;
  case PIO_MPI:
  case PIO_MPI_FW_ORDERED:
  case PIO_MPI_FW_AT_ALL:
  case PIO_MPI_FW_AT_REBLOCK:
    role = (commRank >= clientRangeStart && commRank <= clientRangeEnd)
      ? PIO_ROLE_CLIENT : PIO_ROLE_WRITER_COLLECTOR;
    break;
  case PIO_WRITER:
  case PIO_ASYNCH:
    role = (commRank >= clientRangeStart && commRank <= clientRangeEnd)
      ? PIO_ROLE_CLIENT : ((commRank == specialRank)
                           ? PIO_ROLE_WRITER
                           : PIO_ROLE_COLLECTOR);
    break;
  case PIO_FPGUARD:
    role = (commRank >= clientRangeStart && commRank <= clientRangeEnd)
      ? PIO_ROLE_CLIENT : ((commRank == specialRank)
                           ? PIO_ROLE_FPGUARD
                           : PIO_ROLE_WRITER_COLLECTOR);
    break;
  default:
    xabort("Invalid mode requested %d", IOMode);
  }
  return role;
}

int
cdiPioCSRBalanced(MPI_Comm commSuper, int IOMode, int nProcsIO)
{
  int commRank, commSize, numClients, clientsPerCollectorMin,
    clientsPerCollectorMax, rest;
  xmpi(MPI_Comm_size(commSuper, &commSize));
  xmpi(MPI_Comm_rank(commSuper, &commRank));
  numClients = commSize - nProcsIO;
  int role, specialRole = PIO_ROLE_FPGUARD,
    collType = PIO_ROLE_WRITER_COLLECTOR;
  switch (IOMode)
  {
  case PIO_NONE:
    xassert(commRank == 0 && commSize == 1);
    role = PIO_NONE;
    break;
  case PIO_MPI:
  case PIO_MPI_FW_ORDERED:
  case PIO_MPI_FW_AT_ALL:
  case PIO_MPI_FW_AT_REBLOCK:
    clientsPerCollectorMin = numClients / nProcsIO;
    rest = numClients % nProcsIO;
    clientsPerCollectorMax = clientsPerCollectorMin + (rest != 0);
    if (commRank == commSize - 1)
      role = PIO_ROLE_WRITER_COLLECTOR;
    else if (commRank <= (clientsPerCollectorMax + 1) * (nProcsIO - rest))
      role = ((commRank + 1) % (clientsPerCollectorMax + 1)) != 0
        ? PIO_ROLE_CLIENT : PIO_ROLE_WRITER_COLLECTOR;
    else
      role = ((commRank - clientsPerCollectorMax * (nProcsIO - rest) + 1)
              % (clientsPerCollectorMin + 1)) != 0
        ? PIO_ROLE_CLIENT : PIO_ROLE_WRITER_COLLECTOR;
    break;
  case PIO_WRITER:
  case PIO_ASYNCH:
    specialRole = PIO_ROLE_WRITER;
    collType = PIO_ROLE_COLLECTOR;
    /* fallthrough intentional */
  case PIO_FPGUARD:
    clientsPerCollectorMin = numClients / (nProcsIO - 1);
    rest = numClients % nProcsIO;
    clientsPerCollectorMax = clientsPerCollectorMin + (rest != 0);
    if (commRank == commSize - 1)
      role = specialRole;
    else if (commRank <= clientsPerCollectorMax * (nProcsIO - 1 - rest))
      role = ((commRank + 1) % (clientsPerCollectorMax + 1)) != 0
        ? PIO_ROLE_CLIENT : collType;
    else
      role = ((commRank - clientsPerCollectorMax * (nProcsIO - 1 - rest) + 1)
              % (clientsPerCollectorMin + 1)) != 0
        ? PIO_ROLE_CLIENT : collType;
    break;
  default:
    xabort("Invalid mode requested %d", IOMode);
  }
  return role;
}

