/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) CNRS, INRIA, Universite Bordeaux 1, Telecom SudParis
 * See COPYING in top-level directory.
 */

#define _REENTRANT

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <pthread.h>
#include <papi.h>
#include "eztrace_sampling.h"

#include "papi_ev_codes.h"
#include "eztrace.h"

/* set to 1 when all the hooks are set.
 * This is usefull in order to avoid recursive calls
 */
static int __papi_initialized = 0;

//#define VERBOSE 1
/* We don't use the FUNCTION_NAME macro (defined in src/core/eztrace.h)
 * to avoid infinite loops (since it calls record_counters).
 */
#ifdef FUNCTION_NAME
#undef FUNCTION_NAME
#endif

#ifdef VERBOSE
#define FUNCTION_NAME fprintf(stderr, "Calling [%s]\n", __FUNCTION__)
#else
#define FUNCTION_NAME (void) 0
#endif

#define TIME_DIFF(t1, t2) \
        ((t2.tv_sec - t1.tv_sec) * 1000000 + (t2.tv_usec - t1.tv_usec))

static pthread_key_t papi_thread_info_key;

struct __papi_thread_info {
  int Events;
  long_long *values;
  struct timeval last_tick;
};

static int nb_events;
static int Events;
static int* Event_codes;
static char** Event_names;
static char** units_names;

struct __papi_thread_info * papi_init_hw_counter();

#define handle_error(n) \
  fprintf(stderr, "%s: PAPI error %d: %s\n",__FUNCTION__, n,PAPI_strerror(n))

int papi_record(struct ezt_sampling_callback_instance *instance) {
    if (!__papi_initialized || !nb_events)
        return 0;

    struct __papi_thread_info *ptr = NULL;
    struct timeval cur_tick;
    gettimeofday(&cur_tick, NULL );

    /* retrieve the thread-specific structure */
    if(! instance->plugin_data) {
      ptr = papi_init_hw_counter(instance);
      if (!ptr) {
	return -1;
      }
    }
    ptr = instance->plugin_data;

    /* read hardware counter */
    // this region needs to be protected in order to disable infinite recursion due to the intercepting of functions.
    // Because, PAPI functions can possibly be intercepted too.
    EZTRACE_PROTECT {
        EZTRACE_PROTECT_ON();
        int ret;
	/* get the values of counters */
        if ((ret = PAPI_stop(ptr->Events, ptr->values)) != PAPI_OK) {
	  handle_error(ret);
	  abort();
        }

	/* restart counters */
	ret = PAPI_start(ptr->Events);
	if(ret != PAPI_OK) {
	  handle_error(ret);
	  abort();
	}

        int duration = TIME_DIFF(instance->last_call, cur_tick); /* duration in usec */
	int i;

        for (i = 0; i < nb_events; i++) {
            // As this region is protected, the unprotected function is called
            EZTRACE_EVENT3_PACKED_UNPROTECTED(EZTRACE_PAPI_MEASUREMENT, i, ptr->values[i], duration);
	}

        /* reset the timer */
        instance->last_call.tv_usec = cur_tick.tv_usec;
        instance->last_call.tv_sec  = cur_tick.tv_sec;

        EZTRACE_PROTECT_OFF();
	return 0;
    }

    return 1;
}

/* Initialize the PAPI counter interface for a thread */
struct __papi_thread_info * papi_init_hw_counter(struct ezt_sampling_callback_instance *instance) {

    if (!nb_events)
      /* no counter were selected. We don't need to do anything */
        return NULL;

    struct __papi_thread_info *ptr = NULL;
    // this region needs to be protected in order to disable infinite recursion due to the intercepting of functions.
    // Because, PAPI functions can possibly be intercepted too.
    EZTRACE_PROTECT {
        EZTRACE_PROTECT_ON();

        int ret;
        /* initialize the thread-specific structure */
        if (instance->plugin_data == NULL ) {

  	  /* This thread has not been initialized yet */
	  PAPI_register_thread();

	  ptr = malloc(sizeof(struct __papi_thread_info));
	  ptr->values = malloc(sizeof(long_long) * nb_events);
	  int i;

	  /* Create a new EventSet */
	  ptr->Events = PAPI_NULL;
	  ret = PAPI_create_eventset(&ptr->Events);
	  if(ret != PAPI_OK) {
	    handle_error(ret);
	    abort();
	  }

	  /* copy Events to the EventSet */
	  for (i = 0; i < nb_events; i++) {
	    ret = PAPI_add_event( ptr->Events, Event_codes[i] );
	    if(ret != PAPI_OK) {
	      handle_error(ret);
	      abort();
	    }
	  }

	  /* Start the counter */
	  if ((ret = PAPI_start(ptr->Events)) != PAPI_OK) {
	    if (ret == PAPI_ECNFLCT) {
	      fprintf(stderr, "PAPI error %d (%s): It is likely that you selected too many counters to monitor\n",
		      ret, PAPI_strerror(ret));
	      exit(-1);
	    }
	  }

	  /* set the structure Thread-specific */
	  instance->plugin_data = ptr;

	  /* first measurement */
	  gettimeofday(&instance->last_call, NULL );
	  int retval = PAPI_stop( ptr->Events, ptr->values);
	  if (retval != PAPI_OK) {
	    fprintf(stderr, "PAPI_stop() failed\n");
	  }

	  retval = PAPI_start(ptr->Events);
	  if (retval != PAPI_OK) {
	    fprintf(stderr,"PAPI_start() failed\n");
	    exit(1);
	  }

        }
        EZTRACE_PROTECT_OFF();
    }

    return ptr;
}

static int __papi_add_counter(char* counter_name) {

  nb_events++;
  Event_codes = realloc(Event_codes, sizeof(int) * nb_events);
  Event_names = realloc(Event_names, sizeof(char*) * nb_events);
  units_names = realloc(units_names, sizeof(char*) * nb_events);

  int code;
  int retval;
  if ( (retval = PAPI_event_name_to_code( counter_name, &code )) == PAPI_OK ) {
    retval = PAPI_add_event( Events, code );
    if (retval != PAPI_OK) {
      handle_error(retval);
      nb_events--;
      return retval;
    } else {
#if PAPI_VERSION > PAPI_VERSION_NUMBER(5,0,0,0)
      PAPI_event_info_t info;
      retval = PAPI_get_event_info(code, &info);
      if (retval != PAPI_OK) {
	/* PAPI_get_event_info failed. Let's assume there is no unit */
	handle_error(retval);
	asprintf(&units_names[nb_events-1], " ");
      } else {
	asprintf(&units_names[nb_events-1], "%s", info.units);
      }

#else
      asprintf(&units_names[nb_events-1], " ");
#endif
      Event_codes[nb_events-1] = code;
      asprintf(&Event_names[nb_events-1], "%s", counter_name);
      return PAPI_OK;
    }
  }

  handle_error(retval);
  nb_events--;
  return retval;
}


/* select the counters to monitor */
static void __papi_select_counters() {
    int invalid_counters = 0;
    char* env1 = getenv("EZTRACE_PAPI_COUNTERS");

    nb_events = 0;
    Events = PAPI_NULL;

    if ( PAPI_create_eventset( &Events ) != PAPI_OK )
      {
	abort();
      }
    if (env1) {
        /* EZTRACE_PAPI_COUNTERS is defined */

        /* strtok may modify env, so let's make a copy of it */
        char* env;
        asprintf(&env, "%s", env1);
        char *token = strtok(env, " ");

        /* for each token, get the code id and fill the Events array */
        while (token) {
	  if(__papi_add_counter(token) != PAPI_OK) {
	    invalid_counters = 1;
	  }
	  token = strtok(NULL, " ");
        }

    } else {
        /* EZTRACE_PAPI_COUNTERS is not defined, use the default values */
	__papi_add_counter("PAPI_TOT_INS");
    }

    if (invalid_counters) {
        fprintf(stderr, "Available counters:\n");
        __papi_print_available_counters();
    }
}

/* In some cases (eg. using -O3 with gcc), the compiler may optimize too agressively and remove the function array symbol.
 * This results in pptrace being unable to load the module (since the symbol is not found).
 * This can be "solved" by declaring 2 callbacks to avoid the compiler optimization
 */
void (*libfoo)(void*);
void (*libfoo2)(void*);
START_INTERCEPT_MODULE(papi)
INTERCEPT2("foo", libfoo)
INTERCEPT2("foo2", libfoo2)
END_INTERCEPT_MODULE(papi)


static void __papi_init(void) __attribute__ ((constructor));
static void __papi_init(void) {
    DYNAMIC_INTERCEPT_ALL_MODULE(papi);

    int ret;
    pthread_key_create(&papi_thread_info_key, NULL );

    /* initialize PAPI */
    ret = PAPI_library_init(PAPI_VER_CURRENT);
    if (ret != PAPI_VER_CURRENT && ret > 0) {
        fprintf(stderr, "PAPI library version mismatch!\n");
        exit(1);
    }

    /* There may be several threads, so let's tell this to PAPI */
    if ((ret = PAPI_thread_init(pthread_self)) != PAPI_OK)
        handle_error(ret);

    __papi_init_counter_ids();
    __papi_select_counters();

    char* env=getenv("EZTRACE_PAPI_SAMPLE_INTERVAL");
    int sample_interval=100; 	/* by default, check counter every 100 µs */
    if(env) {
      sample_interval = atoi(env);
      if(sample_interval <= 0) {
	fprintf(stderr, "[EZTrace] please enter a value > 0 when setting EZTRACE_PAPI_SAMPLE_INTERVAL\n");
	sample_interval=100;
      }
    }
#ifdef EZTRACE_AUTOSTART
    eztrace_start ();
#endif

    __papi_initialized = 1;

    /* Tell how many counters (and which counters) are monitored */
    EZTRACE_EVENT_PACKED_1(EZTRACE_PAPI_INIT, nb_events);
    int i;
    for (i = 0; i < nb_events; i++) {
      EZTRACE_EVENT_PACKED_2(EZTRACE_PAPI_INIT_COUNTERS, i, Event_codes[i]);

      litl_write_probe_raw(__ezt_trace.litl_trace,
			   EZTRACE_PAPI_INIT_COUNTERS,
			   strlen(Event_names[i]),
			   (litl_data_t*) Event_names[i]);
      litl_write_probe_raw(__ezt_trace.litl_trace,
			   EZTRACE_PAPI_INIT_COUNTERS,
			   strlen(units_names[i]),
			   (litl_data_t*)units_names[i]);
      /* todo: add event description */
    }

    ezt_sampling_register_callback(papi_record, sample_interval);
}

static void __papi_conclude(void) __attribute__ ((destructor));
static void __papi_conclude(void) {
    __papi_initialized = 0;
    eztrace_stop();
}
