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

#include <ctype.h>

#include <yaxt.h>

#include "cdi.h"
#include "cdi_int.h"
#include "dmemory.h"
#include "namespace.h"
#include "taxis.h"

#include "cdipio.h"
#include "pio.h"
#include "pio_client.h"
#include "pio_comm.h"
#include "pio_interface.h"
#include "pio_rpc.h"
#include "pio_util.h"
#include "pio_serialize.h"

static int
cdiPioClientStreamOpen(const char *filename, char filemode,
                       int filetype, stream_t *streamptr,
                       int recordBufIsToBeCreated)
{
  (void)streamptr; (void)recordBufIsToBeCreated;
  int fileID;
  if ( filemode == 'w' )
    {
      MPI_Comm comm = cdiPioInqInterComm();
      int clientRank = commInqRankModel(),
        numClients = cdiPioCommInqSizeClients(),
        numColl = commInqSizeColl(),
        collRank = cdiPioCollRank(clientRank, numClients, numColl);
      int streamID = streamptr->self;
      if (clientRank
          == cdiPioClientRangeStart(collRank, numClients, numColl))
        {
          reshSetStatus(streamID, &streamOps,
                        reshGetStatus(streamID, &streamOps)
                        & ~RESH_SYNC_BIT);
          char *msgBuffer;
          int msgSize = 0;
          int msgBufPos = reshPackBufferCreate(&msgBuffer, &msgSize, &comm);
          int size;
          size_t filename_len = strlen(filename);
          xassert(filename_len < (size_t)(INT_MAX - msgBufPos));
          int soHdr[3] = { streamptr->self, filetype, (int)filename_len };
          xmpi(MPI_Pack_size(3, MPI_INT, comm, &size));
          msgSize += size;
          xmpi(MPI_Pack_size(1, MPI_CHAR, comm, &size));
          msgSize += size;
          xmpi(MPI_Pack_size((int)filename_len, MPI_CHAR, comm, &size));
          msgSize += size;
          /* optimize to pos + size */
          msgBuffer = (char *)Realloc(msgBuffer, (size_t)msgSize);
          xmpi(MPI_Pack(soHdr, 3, MPI_INT,
                        msgBuffer, msgSize, &msgBufPos, comm));
          xmpi(MPI_Pack(&filemode, 1, MPI_CHAR,
                        msgBuffer, msgSize, &msgBufPos, comm));
          xmpi(MPI_Pack((void *)filename, (int)filename_len, MPI_CHAR,
                        msgBuffer, msgSize, &msgBufPos, comm));
          xmpi(MPI_Sendrecv(msgBuffer, msgSize, MPI_PACKED, collRank,
                            STREAMOPEN,
                            &fileID, 1, MPI_INT, collRank, STREAMOPEN,
                            comm, MPI_STATUS_IGNORE));
          Free(msgBuffer);
        }
      else
        xmpi(MPI_Recv(&fileID, 1, MPI_INT, collRank, STREAMOPEN,
                      comm, MPI_STATUS_IGNORE));
      if (fileID >= 0)
        {
          streamptr->filetype = filetype;
          cdiPioClientStreamWinInit(streamID);
        }
    }
  else
    xabort("cdiPIO read support not implemented");
  return fileID;
}

static void
cdiPioClientStreamDefVlist_(int streamID, int vlistID)
{
  cdiStreamDefVlist_(streamID, vlistID);
  int clientRank = commInqRankModel(),
    numClients = cdiPioCommInqSizeClients(),
    numColl = commInqSizeColl(),
    collRank = cdiPioCollRank(clientRank, numClients, numColl);
  int sendRPCData
    = (clientRank
       == cdiPioClientRangeStart(collRank, numClients, numColl));
  if (sendRPCData)
    {
      MPI_Comm comm = cdiPioInqInterComm();
      reshSetStatus(streamID, &streamOps,
                    reshGetStatus(streamID, &streamOps) & ~RESH_SYNC_BIT);
      char *msgBuffer;
      int msgSize = 0;
      int msgBufPos = reshPackBufferCreate(&msgBuffer, &msgSize, &comm);
      {
        int size;
        xmpi(MPI_Pack_size(defVlistNInts, MPI_INT, comm, &size));
        msgSize += size;
      }
      /* optimize: pos + size */
      msgBuffer = Realloc(msgBuffer, (size_t)msgSize);
      int msgData[defVlistNInts] = { streamID, streamInqVlist(streamID) };
      xmpi(MPI_Pack(&msgData, defVlistNInts, MPI_INT,
                    msgBuffer, msgSize, &msgBufPos, comm));
      xmpi(MPI_Send(msgBuffer, msgBufPos, MPI_PACKED, collRank,
                    STREAMDEFVLIST, comm));
      Free(msgBuffer);
    }
  struct collSpec cspec = { .numClients = numClients,
                            .numServers = numColl,
                            .sendRPCData = sendRPCData };
  cdiPioClientStreamWinCreate(streamID, &cspec);
}

/* If we're not using GNU C, elide __attribute__ */
#if ! defined __GNUC__ && ! defined __attribute__
#  define  __attribute__(x)  /*NOTHING*/
#endif

static void
cdiPioClientStreamWriteVar_(int streamID, int varID, int memtype,
                            const void *data, size_t nmiss)
  __attribute__((noreturn));

static void
cdiPioClientStreamWriteVar_(int streamID, int varID, int memtype,
                            const void *data, size_t nmiss)
{
  (void)streamID; (void)varID; (void)memtype; (void)data; (void)nmiss;
  xabort("parallel writing requires explicit partition information,"
         " use streamWriteVarPart!");
}

static void
cdiPioClientStreamWriteVarChunk_(int streamID, int varID, int memtype,
                                 const int rect[][2],
                                 const void *data, size_t nmiss)
{
  /* todo: handle transmission of float data */
  if (memtype != MEMTYPE_DOUBLE)
    Error("Writing of non-double type data not implemented!");
  int vlistID = streamInqVlist(streamID);
  int size = vlistInqVarSize(vlistID, varID),
    varShape[3];
  unsigned ndims = (unsigned)cdiPioQueryVarDims(varShape, vlistID, varID);
  Xt_int varShapeXt[3], origin[3] = { 0, 0, 0 };
  int chunkShape[3] = { 1, 1, 1 };
  /* FIXME: verify xt_int ranges are good enough */
  for (unsigned i = 0; i < 3; ++i)
    varShapeXt[i] = varShape[i];
  for (unsigned i = 0; i < ndims; ++i)
    chunkShape[i] = rect[i][1] - rect[i][0] + 1;
  int varSize = varShape[0] * varShape[1] * varShape[2];
  xassert(varSize == size);
  Xt_idxlist chunkDesc
    = xt_idxsection_new(0, (int)ndims, varShapeXt, chunkShape, origin);
  pioBufferPartData(streamID, varID, data, nmiss, chunkDesc);
  xt_idxlist_delete(chunkDesc);
}

static void
cdiPioClientStreamWriteVarPart(int streamID, int varID, const void *data,
                               size_t nmiss, Xt_idxlist partDesc)
{
  pioBufferPartData(streamID, varID, data, nmiss, partDesc);
}

static void
cdiPioClientStreamWriteScatteredVarPart(int streamID, int varID,
                                        const void *data,
                                        int numBlocks, const int blocklengths[],
                                        const int displacements[],
                                        size_t nmiss, Xt_idxlist partDesc)
{
  cdiPioBufferPartDataGather(streamID, varID, data, numBlocks,
                             blocklengths, displacements, nmiss, partDesc);
}

#if defined HAVE_LIBNETCDF
static void
cdiPioCdfDefTimestepNOP(stream_t *streamptr, int tsID)
{
  (void)streamptr; (void)tsID;
}
#endif

static void
cdiPioClientStreamNOP(stream_t *streamptr)
{
  (void)streamptr;
}


static void
cdiPioClientStreamClose(stream_t *streamptr, int recordBufIsToBeDeleted)
{
  (void)recordBufIsToBeDeleted;
  int streamID = streamptr->self;
  int clientRank = commInqRankModel(),
    numClients = cdiPioCommInqSizeClients(),
    numColl = commInqSizeColl(),
    collRank = cdiPioCollRank(clientRank, numClients, numColl);
  if (clientRank
      == cdiPioClientRangeStart(collRank, numClients, numColl))
    {
      MPI_Comm comm = cdiPioInqInterComm();
      reshSetStatus(streamID, &streamOps,
                    reshGetStatus(streamID, &streamOps) & ~RESH_SYNC_BIT);
      char *msgBuffer;
      int msgSize = 0;
      int msgBufPos = reshPackBufferCreate(&msgBuffer, &msgSize, &comm);
      {
        int size;
        xmpi(MPI_Pack_size(1, MPI_INT, comm, &size));
        msgSize += size;
      }
      /* optimize: pos + size */
      msgBuffer = Realloc(msgBuffer, (size_t)msgSize);
      xmpi(MPI_Pack(&streamptr->self, 1, MPI_INT,
                    msgBuffer, msgSize, &msgBufPos, comm));
      xmpi(MPI_Send(msgBuffer, msgBufPos, MPI_PACKED, collRank,
                    STREAMCLOSE, comm));
      Free(msgBuffer);
    }
  cdiPioClientStreamWinDestroy(streamID);
}

static void
cdiPioTaxisPackWrap(void *data, void *buf, int size, int *pos,
                    void *context)
{
  int taxisID = (int)(intptr_t)data;
  reshPackResource(taxisID, &taxisOps, buf, size, pos, context);
}

static int
cdiPioClientStreamDefTimestep_(stream_t *streamptr, int tsID)
{
  int taxisID = vlistInqTaxis(streamptr->vlistID);
  struct winHeaderEntry header = (struct winHeaderEntry){
    .id = STREAMDEFTIMESTEP,
    .specific.funcArgs.streamNewTimestep = { streamptr->self, tsID } };
  xassert(sizeof (void *) >= sizeof (int));
  pioBufferFuncCall(streamptr->self, header,
                    (void *)(intptr_t)taxisID, cdiPioTaxisPackWrap);
  return cdiStreamDefTimestep_(streamptr, tsID);
}

void
cdiPioClientSetup(int *pioNamespace_, int *pioNamespace)
{
  *pioNamespace_ = *pioNamespace = namespaceNew();
  int callerCDINamespace = namespaceGetActive();
  namespaceSetActive(*pioNamespace_);
  cdiPioSerializeSetMPI();
  namespaceSwitchSet(NSSWITCH_ABORT, NSSW_FUNC(cdiAbortC_MPI));
  namespaceSwitchSet(NSSWITCH_WARNING, NSSW_FUNC(cdiPioWarning));
  namespaceSwitchSet(NSSWITCH_STREAM_OPEN_BACKEND,
                     NSSW_FUNC(cdiPioClientStreamOpen));
  namespaceSwitchSet(NSSWITCH_STREAM_DEF_VLIST_,
                     NSSW_FUNC(cdiPioClientStreamDefVlist_));
  namespaceSwitchSet(NSSWITCH_STREAM_WRITE_VAR_,
                     NSSW_FUNC(cdiPioClientStreamWriteVar_));
  namespaceSwitchSet(NSSWITCH_STREAM_WRITE_VAR_CHUNK_,
                     NSSW_FUNC(cdiPioClientStreamWriteVarChunk_));
  namespaceSwitchSet(NSSWITCH_STREAM_WRITE_VAR_PART_,
                     NSSW_FUNC(cdiPioClientStreamWriteVarPart));
  namespaceSwitchSet(NSSWITCH_STREAM_WRITE_SCATTERED_VAR_PART_,
                     NSSW_FUNC(cdiPioClientStreamWriteScatteredVarPart));
  namespaceSwitchSet(NSSWITCH_STREAM_CLOSE_BACKEND,
                     NSSW_FUNC(cdiPioClientStreamClose));
  namespaceSwitchSet(NSSWITCH_STREAM_DEF_TIMESTEP_,
                     NSSW_FUNC(cdiPioClientStreamDefTimestep_));
  namespaceSwitchSet(NSSWITCH_STREAM_SYNC,
                     NSSW_FUNC(cdiPioClientStreamNOP));
#ifdef HAVE_LIBNETCDF
  namespaceSwitchSet(NSSWITCH_CDF_DEF_TIMESTEP,
                     NSSW_FUNC(cdiPioCdfDefTimestepNOP));
  namespaceSwitchSet(NSSWITCH_CDF_STREAM_SETUP,
                     NSSW_FUNC(cdiPioClientStreamNOP));
#endif
  namespaceSetActive(callerCDINamespace);
}


/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
