/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) Telecom SudParis
 * See COPYING in top-level directory.
 */

#ifndef __EZTRACE_OTF2_H__
#define __EZTRACE_OTF2_H__

#include <time.h>
#include <otf2/otf2.h>
#include <otf2/OTF2_AttributeValue.h>
#include <otf2/OTF2_AttributeList.h>
#include "eztrace-lib/eztrace_internals.h"
#include "eztrace-lib/eztrace_mpi.h"

int ezt_otf2_register_function(const char* function_name);
int ezt_otf2_register_string(const char* string);
int ezt_otf2_register_attribute(char* name, OTF2_Type type);
int ezt_otf2_register_thread(int thread_rank);
int ezt_otf2_register_gpu(int gpu_id);
int ezt_otf2_register_thread_team(char *name, int nb_threads);
int ezt_otf2_register_thread_team_member(int team_id, int my_rank, int nb_threads);
int ezt_otf2_register_mpi_comm(int comm_size, uint64_t *members);

void ezt_otf2_init();
int ezt_otf2_finalize();
int ezt_otf2_initialize_thread(int thread_rank);
void ezt_otf2_set_mpi_rank(int rank, int comm_size);

#define EZT_OTF2_CHECK( function ) do {					\
    OTF2_ErrorCode _status_ = function;					\
    if(_status_ != OTF2_SUCCESS) {					\
      eztrace_warn("OTF2 error: %s: %s\n", OTF2_Error_GetName(_status_), \
		   OTF2_Error_GetDescription(_status_));		\
    }									\
  } while(0)


#define OTF2_TYPE_AUTO(var) sizeof(var)==1 ? OTF2_TYPE_UINT8 :	\
    sizeof(var)==2 ? OTF2_TYPE_UINT16:				\
    sizeof(var)==4 ? OTF2_TYPE_UINT32: OTF2_TYPE_UINT64

#define REGISTER_ARG_ENTRY(I, arg) REGISTER_ARG(I, arg, entry_attr_id)
#define REGISTER_ARG_EXIT(I, arg) REGISTER_ARG(I, arg, exit_attr_id)

#define REGISTER_ARG(I, arg, attr_array) do {				\
    enum OTF2_Type_enum type = OTF2_TYPE_AUTO(arg);			\
    attr_array[I-1] = ezt_otf2_register_attribute(#arg, OTF2_TYPE_AUTO(arg)); \
  } while(0);

#define ADD_ATTR_ENTRY(I, arg) ADD_ATTR(I, arg, entry_attr_id)
#define ADD_ATTR_EXIT(I, arg) ADD_ATTR(I, arg, exit_attr_id)

static void OTF2_AttributeList_AddAttribute_uint8(OTF2_AttributeList * 	attribute_list,
						  OTF2_AttributeRef 	attribute,
						  uint8_t value)  __attribute__((unused));

static void OTF2_AttributeList_AddAttribute_uint16(OTF2_AttributeList *attribute_list,
						   OTF2_AttributeRef 	attribute,
						   uint16_t value)  __attribute__((unused));

static void OTF2_AttributeList_AddAttribute_uint32(OTF2_AttributeList *attribute_list,
						   OTF2_AttributeRef 	attribute,
						   uint32_t value)  __attribute__((unused));

static void OTF2_AttributeList_AddAttribute_uint64(OTF2_AttributeList *attribute_list,
						   OTF2_AttributeRef 	attribute,
						   uint64_t value)  __attribute__((unused));

static void OTF2_AttributeList_AddAttribute_string(OTF2_AttributeList *attribute_list,
						   OTF2_AttributeRef 	attribute,
						   const char* value)  __attribute__((unused));

static void OTF2_AttributeList_AddAttribute_uint8(OTF2_AttributeList * 	attribute_list,
						  OTF2_AttributeRef 	attribute,
						  uint8_t value) {
  OTF2_AttributeValue attr_value;
  attr_value.uint8  = value;
  OTF2_AttributeList_AddAttribute( attribute_list, attribute, OTF2_TYPE_UINT8, attr_value );
}

static void OTF2_AttributeList_AddAttribute_uint16(OTF2_AttributeList *attribute_list,
						   OTF2_AttributeRef 	attribute,
						   uint16_t value) {
  OTF2_AttributeValue attr_value;
  attr_value.uint16  = value;
  OTF2_AttributeList_AddAttribute( attribute_list, attribute, OTF2_TYPE_UINT16, attr_value );
}
static void OTF2_AttributeList_AddAttribute_uint32(OTF2_AttributeList *attribute_list,
						  OTF2_AttributeRef 	attribute,
						  uint32_t value) {
  OTF2_AttributeValue attr_value;
  attr_value.uint32  = value;
  OTF2_AttributeList_AddAttribute( attribute_list, attribute, OTF2_TYPE_UINT32, attr_value );
}

static void OTF2_AttributeList_AddAttribute_uint64(OTF2_AttributeList *attribute_list,
						   OTF2_AttributeRef 	attribute,
						   uint64_t value) {
  OTF2_AttributeValue attr_value;
  attr_value.uint64  = value;
  OTF2_AttributeList_AddAttribute( attribute_list, attribute, OTF2_TYPE_UINT64, attr_value );
}

static void OTF2_AttributeList_AddAttribute_string(OTF2_AttributeList *attribute_list,
						  OTF2_AttributeRef 	attribute,
						  const char* value) {
  OTF2_AttributeValue attr_value;
  attr_value.stringRef  = ezt_otf2_register_string(value?value:"");
  OTF2_AttributeList_AddAttribute( attribute_list, attribute, OTF2_TYPE_STRING, attr_value );
}

#define ADD_ATTRIBUTE(attribute_list, attribute_id, value) _Generic((value), \
								 uint8_t:    OTF2_AttributeList_AddAttribute_uint8 (attribute_list, attribute_id, (uint8_t)     (uint64_t)value), \
								 uint16_t:   OTF2_AttributeList_AddAttribute_uint16(attribute_list, attribute_id, (uint16_t)    (uint64_t)value), \
								 uint32_t:   OTF2_AttributeList_AddAttribute_uint32(attribute_list, attribute_id, (uint32_t)    (uint64_t)value), \
								 char*:      OTF2_AttributeList_AddAttribute_string(attribute_list, attribute_id, (const char*) (uint64_t)value), \
								 const char*:OTF2_AttributeList_AddAttribute_string(attribute_list, attribute_id, (const char*) (uint64_t)value), \
								 default:    OTF2_AttributeList_AddAttribute_uint64(attribute_list, attribute_id, (uint64_t)    (uint64_t)value));

#define ADD_ATTR(I, arg, attr_array) ADD_ATTRIBUTE(attribute_list, attr_array[I-1], arg)


/* Define how timestamps are collected */

// use clock_gettime(CLOCK_MONOTONIC)
#define TS_SOURCE_CLOCK_MONOTONIC (1<<1)
// use rdtsc
#define TS_SOURCE_RDTSC           (1<<2)
/* leave room for other timestamp sources */

// timestamps are relative to the application start
#define TS_RELATIVE               (1<<8)
// timestamps are absolute (and can be compared with external clocks)
#define TS_ABSOLUTE               (1<<9)

extern OTF2_TimeStamp first_timestamp;
extern int* _ezt_timestamp_config;
static inline OTF2_TimeStamp ezt_get_timestamp( ) {
  uint64_t retval = 0;

  if(EZT_MPI_Wtime) {
    /* If MPI is enabled, use MPI_Wtime as a clock */
    retval = EZT_MPI_Wtime() * 1e9;
    goto out;
  }

  if((!_ezt_timestamp_config) ||
     (*_ezt_timestamp_config) & TS_SOURCE_CLOCK_MONOTONIC) {
    struct timespec t;
    clock_gettime(CLOCK_MONOTONIC, &t);
    retval = t.tv_sec*1e9 + t.tv_nsec;
    goto out;
  }

  if((*_ezt_timestamp_config) & TS_SOURCE_RDTSC) {
#ifdef __i386
    __asm__ volatile ("rdtsc" : "=A" (retval));
    goto out;
#elif defined __amd64
    uint64_t a, d;
    __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
    retval = (d<<32) | a;
    goto out;
#endif

    fprintf(stderr, "RDTSC timestamps not available\n");
    abort();
  }

 out:

  if(first_timestamp == 0 &&
     (*_ezt_timestamp_config) & TS_RELATIVE) {
    first_timestamp = retval;
  }

  return retval - first_timestamp;
}

extern int otf2_chunk_size;
static int MPI2OTF(int mpi_rank) __attribute__((unused));
static int MPI2OTF(int mpi_rank) {
  return mpi_rank*otf2_chunk_size;
}

#endif /* __EZTRACE_OTF2_H__ */
