#ifndef EZTRACE_MPI_H
#define EZTRACE_MPI_H

#include "eztrace-lib/eztrace_otf2.h"
#include <eztrace-core/eztrace_htable.h>
#include <eztrace-core/eztrace_types.h>

#if USE_MPI
#include "mpi.h"

/* in some cases (eg on some architectures, or with some MPI implementations), some MPI datatypes
 * are not defined :/
 */
#ifdef MPI_INTEGER1
#define _EZT_MPI_INTEGER1 MPI_INTEGER1
#else
#define _EZT_MPI_INTEGER1 MPI_DATATYPE_NULL
#endif

#ifdef MPI_INTEGER2
#define _EZT_MPI_INTEGER2 MPI_INTEGER2
#else
#define _EZT_MPI_INTEGER2 MPI_DATATYPE_NULL
#endif

#ifdef MPI_INTEGER4
#define _EZT_MPI_INTEGER4 MPI_INTEGER4
#else
#define _EZT_MPI_INTEGER4 MPI_DATATYPE_NULL
#endif

#ifdef MPI_INTEGER8
#define _EZT_MPI_INTEGER8 MPI_INTEGER8
#else
#define _EZT_MPI_INTEGER8 MPI_DATATYPE_NULL
#endif

#ifdef MPI_INTEGER16
#define _EZT_MPI_INTEGER16 MPI_INTEGER16
#else
#define _EZT_MPI_INTEGER16 MPI_DATATYPE_NULL
#endif

#ifdef MPI_REAL4
#define _EZT_MPI_REAL4 MPI_REAL4
#else
#define _EZT_MPI_REAL4 MPI_DATATYPE_NULL
#endif

#ifdef MPI_REAL8
#define _EZT_MPI_REAL8 MPI_REAL8
#else
#define _EZT_MPI_REAL8 MPI_DATATYPE_NULL
#endif

#ifdef MPI_REAL16
#define _EZT_MPI_REAL16 MPI_REAL16
#else
#define _EZT_MPI_REAL16 MPI_DATATYPE_NULL
#endif

#ifdef MPI_COMPLEX8
#define _EZT_MPI_COMPLEX8 MPI_COMPLEX8
#else
#define _EZT_MPI_COMPLEX8 MPI_DATATYPE_NULL
#endif

#ifdef MPI_COMPLEX16
#define _EZT_MPI_COMPLEX16 MPI_COMPLEX16
#else
#define _EZT_MPI_COMPLEX16 MPI_DATATYPE_NULL
#endif

#ifdef MPI_COMPLEX32
#define _EZT_MPI_COMPLEX32 MPI_COMPLEX32
#else
#define _EZT_MPI_COMPLEX32 MPI_DATATYPE_NULL
#endif

#define EZT_DATATYPE_TO_MPI(ezt_datatype)				\
  ezt_datatype == EZT_MPI_DATATYPE_NULL ? MPI_DATATYPE_NULL:		\
    ezt_datatype == EZT_MPI_CHAR ? MPI_CHAR :				\
    ezt_datatype == EZT_MPI_UNSIGNED_CHAR ? MPI_UNSIGNED_CHAR :		\
    ezt_datatype == EZT_MPI_SHORT ? MPI_SHORT :				\
    ezt_datatype == EZT_MPI_UNSIGNED_SHORT ? MPI_UNSIGNED_SHORT :	\
    ezt_datatype == EZT_MPI_INT ? MPI_INT :				\
    ezt_datatype == EZT_MPI_UNSIGNED ? MPI_UNSIGNED :			\
    ezt_datatype == EZT_MPI_LONG ? MPI_LONG :				\
    ezt_datatype == EZT_MPI_UNSIGNED_LONG ? MPI_UNSIGNED_LONG :		\
    ezt_datatype == EZT_MPI_LONG_LONG_INT ? MPI_LONG_LONG_INT :		\
    ezt_datatype == EZT_MPI_LONG_LONG ? MPI_LONG_LONG :			\
    ezt_datatype == EZT_MPI_FLOAT ? MPI_FLOAT :				\
    ezt_datatype == EZT_MPI_DOUBLE ? MPI_DOUBLE :			\
    ezt_datatype == EZT_MPI_LONG_DOUBLE ? MPI_LONG_DOUBLE :		\
    ezt_datatype == EZT_MPI_BYTE ? MPI_BYTE :				\
    ezt_datatype == EZT_MPI_WCHAR ? MPI_WCHAR :				\
    ezt_datatype == EZT_MPI_PACKED ? MPI_PACKED :			\
    ezt_datatype == EZT_MPI_C_COMPLEX ? MPI_C_COMPLEX :			\
    ezt_datatype == EZT_MPI_C_FLOAT_COMPLEX ? MPI_C_FLOAT_COMPLEX :	\
    ezt_datatype == EZT_MPI_C_DOUBLE_COMPLEX ? MPI_C_DOUBLE_COMPLEX :	\
    ezt_datatype == EZT_MPI_C_LONG_DOUBLE_COMPLEX ? MPI_C_LONG_DOUBLE_COMPLEX : \
    ezt_datatype == EZT_MPI_2INT ? MPI_2INT :				\
    ezt_datatype == EZT_MPI_C_BOOL ? MPI_C_BOOL :			\
    ezt_datatype == EZT_MPI_SIGNED_CHAR ? MPI_SIGNED_CHAR :		\
    ezt_datatype == EZT_MPI_UNSIGNED_LONG_LONG ? MPI_UNSIGNED_LONG_LONG : \
    ezt_datatype == EZT_MPI_CHARACTER ? MPI_CHARACTER :			\
    ezt_datatype == EZT_MPI_INTEGER ? MPI_INTEGER :			\
    ezt_datatype == EZT_MPI_REAL ? MPI_REAL :				\
    ezt_datatype == EZT_MPI_LOGICAL ? MPI_LOGICAL :			\
    ezt_datatype == EZT_MPI_COMPLEX ? MPI_COMPLEX :			\
    ezt_datatype == EZT_MPI_DOUBLE_PRECISION ? MPI_DOUBLE_PRECISION :	\
    ezt_datatype == EZT_MPI_2INTEGER ? MPI_2INTEGER :			\
    ezt_datatype == EZT_MPI_2REAL ? MPI_2REAL :				\
    ezt_datatype == EZT_MPI_DOUBLE_COMPLEX ? MPI_DOUBLE_COMPLEX :	\
    ezt_datatype == EZT_MPI_2DOUBLE_PRECISION ? MPI_2DOUBLE_PRECISION : \
    ezt_datatype == EZT_MPI_REAL4 ? _EZT_MPI_REAL4 :			\
    ezt_datatype == EZT_MPI_COMPLEX8 ? _EZT_MPI_COMPLEX8 :		\
    ezt_datatype == EZT_MPI_REAL8 ? _EZT_MPI_REAL8 :			\
    ezt_datatype == EZT_MPI_COMPLEX16 ? _EZT_MPI_COMPLEX16 :		\
    ezt_datatype == EZT_MPI_REAL16 ? _EZT_MPI_REAL16 :			\
    ezt_datatype == EZT_MPI_COMPLEX32 ? _EZT_MPI_COMPLEX32 :		\
    ezt_datatype == EZT_MPI_INTEGER1 ? _EZT_MPI_INTEGER1 :		\
    ezt_datatype == EZT_MPI_INTEGER2 ? _EZT_MPI_INTEGER2 :		\
    ezt_datatype == EZT_MPI_INTEGER4 ? _EZT_MPI_INTEGER4 :		\
    ezt_datatype == EZT_MPI_INTEGER8 ? _EZT_MPI_INTEGER8 :		\
    ezt_datatype == EZT_MPI_INTEGER16 ? _EZT_MPI_INTEGER16 :		\
    ezt_datatype == EZT_MPI_INT8_T ? MPI_INT8_T :			\
    ezt_datatype == EZT_MPI_INT16_T ? MPI_INT16_T :			\
    ezt_datatype == EZT_MPI_INT32_T ? MPI_INT32_T :			\
    ezt_datatype == EZT_MPI_INT64_T ? MPI_INT64_T :			\
    ezt_datatype == EZT_MPI_UINT8_T ? MPI_UINT8_T :			\
    ezt_datatype == EZT_MPI_UINT16_T ? MPI_UINT16_T :			\
    ezt_datatype == EZT_MPI_UINT32_T ? MPI_UINT32_T :			\
    ezt_datatype == EZT_MPI_UINT64_T ? MPI_UINT64_T :			\
    ezt_datatype == EZT_MPI_AINT ? MPI_AINT :				\
    ezt_datatype == EZT_MPI_OFFSET ? MPI_OFFSET :			\
    ezt_datatype == EZT_MPI_FLOAT_INT ? MPI_FLOAT_INT :			\
    ezt_datatype == EZT_MPI_DOUBLE_INT ? MPI_DOUBLE_INT :		\
    ezt_datatype == EZT_MPI_LONG_INT ? MPI_LONG_INT :			\
    ezt_datatype == EZT_MPI_SHORT_INT ? MPI_SHORT_INT :			\
    ezt_datatype == EZT_MPI_LONG_DOUBLE_INT ? MPI_LONG_DOUBLE_INT :	\
    EZT_MPI_DATATYPE_NULL


#define EZT_OP_TO_MPI(ezt_op)			\
  ezt_op == EZT_MPI_MAX ? MPI_MAX :		\
    ezt_op == EZT_MPI_MIN ? MPI_MIN :		\
    ezt_op == EZT_MPI_SUM ? MPI_SUM :		\
    ezt_op == EZT_MPI_PROD ? MPI_PROD :		\
    ezt_op == EZT_MPI_LAND ? MPI_LAND :		\
    ezt_op == EZT_MPI_BAND ? MPI_BAND :		\
    ezt_op == EZT_MPI_LOR ? MPI_LOR :		\
    ezt_op == EZT_MPI_BOR ? MPI_BOR :		\
    ezt_op == EZT_MPI_LXOR ? MPI_LXOR :		\
    ezt_op == EZT_MPI_BXOR ? MPI_BXOR :		\
    ezt_op == EZT_MPI_MAXLOC ? MPI_MAXLOC :	\
    MPI_MINLOC


// if a datatype is MPI_DATATYPE_NULL, calling MPI_Type_size is invalid and some MPI
// implementations (eg OpenMPI) generate an error
#define _EZT_MPI_Type_size(_type, _size) do {	\
    if((void*)_size != NULL) *(_size) = 0;	\
    if( (_type) != MPI_DATATYPE_NULL )		\
      MPI_Type_size(_type, _size);		\
  } while(0)

#if MPI_VERSION >= 3
/* Use MPI 3 */

/* In MPI3, the prototype of some MPI functions have change.
 * For instance, in MPI2.X, the prototype of MPI_Send was:
 * int MPI_Send(void* buffer, [...]);
 * In MPI3, MPI_Send is defined as:
 * int MPI_Send(const void* buffer, [...]);
 * In order to fix this prototype issue, let's define a CONST macro
 */
#define CONST const

#ifndef USE_MPI3
#define USE_MPI3
#endif

#else
/* This is MPI 2.X */

#define CONST

#ifdef USE_MPI3
#undef USE_MPI3
#endif

#endif

static inline hashkey_t _ezt_hash_mpi_comm(MPI_Comm c) {
  uint64_t key = (uint64_t)c;
  return hash_function_int64(key);
}

#endif	/* USE_MPI */

enum EZT_MPI_Datatype {
  EZT_MPI_DATATYPE_NULL          ,
  EZT_MPI_CHAR                   ,
  EZT_MPI_UNSIGNED_CHAR          ,
  EZT_MPI_SHORT                  ,
  EZT_MPI_UNSIGNED_SHORT         ,
  EZT_MPI_INT                    ,
  EZT_MPI_UNSIGNED               ,
  EZT_MPI_LONG                   ,
  EZT_MPI_UNSIGNED_LONG          ,
  EZT_MPI_LONG_LONG_INT          ,
  EZT_MPI_LONG_LONG              ,
  EZT_MPI_FLOAT                  ,
  EZT_MPI_DOUBLE                 ,
  EZT_MPI_LONG_DOUBLE            ,
  EZT_MPI_BYTE                   ,
  EZT_MPI_WCHAR                  ,
  EZT_MPI_PACKED                 ,
  EZT_MPI_LB                     ,
  EZT_MPI_UB                     ,
  EZT_MPI_C_COMPLEX              ,
  EZT_MPI_C_FLOAT_COMPLEX        ,
  EZT_MPI_C_DOUBLE_COMPLEX       ,
  EZT_MPI_C_LONG_DOUBLE_COMPLEX  ,
  EZT_MPI_2INT                   ,
  EZT_MPI_C_BOOL                 ,
  EZT_MPI_SIGNED_CHAR            ,
  EZT_MPI_UNSIGNED_LONG_LONG     ,
  EZT_MPI_CHARACTER              ,
  EZT_MPI_INTEGER                ,
  EZT_MPI_REAL                   ,
  EZT_MPI_LOGICAL                ,
  EZT_MPI_COMPLEX                ,
  EZT_MPI_DOUBLE_PRECISION       ,
  EZT_MPI_2INTEGER               ,
  EZT_MPI_2REAL                  ,
  EZT_MPI_DOUBLE_COMPLEX         ,
  EZT_MPI_2DOUBLE_PRECISION      ,
  EZT_MPI_REAL4                  ,
  EZT_MPI_COMPLEX8               ,
  EZT_MPI_REAL8                  ,
  EZT_MPI_COMPLEX16              ,
  EZT_MPI_REAL16                 ,
  EZT_MPI_COMPLEX32              ,
  EZT_MPI_INTEGER1               ,
  EZT_MPI_INTEGER2               ,
  EZT_MPI_INTEGER4               ,
  EZT_MPI_INTEGER8               ,
  EZT_MPI_INTEGER16              ,
  EZT_MPI_INT8_T                 ,
  EZT_MPI_INT16_T                ,
  EZT_MPI_INT32_T                ,
  EZT_MPI_INT64_T                ,
  EZT_MPI_UINT8_T                ,
  EZT_MPI_UINT16_T               ,
  EZT_MPI_UINT32_T               ,
  EZT_MPI_UINT64_T               ,
  EZT_MPI_AINT                   ,
  EZT_MPI_OFFSET                 ,
  EZT_MPI_FLOAT_INT              ,
  EZT_MPI_DOUBLE_INT             ,
  EZT_MPI_LONG_INT               ,
  EZT_MPI_SHORT_INT              ,
  EZT_MPI_LONG_DOUBLE_INT        
};

enum EZT_MPI_Op {
    EZT_MPI_MAX,
    EZT_MPI_MIN,
    EZT_MPI_SUM,
    EZT_MPI_PROD,
    EZT_MPI_LAND,
    EZT_MPI_BAND,
    EZT_MPI_LOR,
    EZT_MPI_BOR,
    EZT_MPI_LXOR,
    EZT_MPI_BXOR,
    EZT_MPI_MAXLOC,
    EZT_MPI_MINLOC,
};


struct _ezt_mpi_info {
  int rank;
  int size;
  int mpi_any_source;
  int mpi_any_tag;
  app_ptr mpi_request_null;
  int mpi_proc_null;
  app_ptr mpi_comm_world;
  app_ptr mpi_comm_self;

  struct ezt_hashtable mpi_communicators; // match MPI communicators with OTF2 communicator IDs
  struct ezt_hashtable ezt_mpi_requests;
  struct ezt_hashtable ezt_mpi_persistent_requests;

  /* Process identifier.
   * It corresponds to the global MPI rank unless the process was spawned.
   * In that case, the identifier is the concatenation of the parent process id
   * and the global rank.
   * For example process id 0_1_3 has a global rank of 3 and is has been spawned by process 0_1
   * Process 0_1 has a global rank of 1 and was spawned by process 0
   */
  char* proc_id;
};

extern struct _ezt_mpi_info mpi_infos;

extern int (*EZT_MPI_Recv)(void* buffer, size_t size, int src, int tag);
extern int (*EZT_MPI_Send)(void* buffer, size_t size, int dest, int tag);
extern int (*EZT_MPI_Reduce)(const void *sendbuf, void *recvbuf, int count,
			     enum EZT_MPI_Datatype datatype,  enum EZT_MPI_Op op, int root);
extern int (*EZT_MPI_SetMPICollectiveCallbacks)(OTF2_Archive *archive);
extern int (*EZT_MPI_Barrier)();
extern double (*EZT_MPI_Wtime)();

#if USE_MPI
extern void (*EZT_New_MPI_Comm)(MPI_Comm comm);
extern void (*EZT_Delete_MPI_Comm)(MPI_Comm c);
#endif

#endif	/* EZTRACE_MPI_H */
