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

#include "eztrace-lib/eztrace.h"
#include "eztrace-lib/eztrace_mpi.h"
#include "eztrace-lib/eztrace_module.h"
#include "eztrace-lib/eztrace_internals.h"
#include "eztrace-core/eztrace_attributes.h"
#include "eztrace-core/eztrace_config.h"
#include "eztrace-core/eztrace_utils.h"

#include <assert.h>
#include <errno.h>
#include <execinfo.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <otf2/otf2.h>

#if HAVE_LIBBACKTRACE
#include <libbacktrace/backtrace-supported.h>
#include <libbacktrace/backtrace.h>
#endif

int eztrace_should_trace=0;
int eztrace_can_trace = 0;

static void _eztrace_run_atexit_functions();

static pthread_key_t protect_on;

struct _ezt_write_trace _ezt_trace;
int* _ezt_timestamp_config = &_ezt_trace.timestamp_config;
__thread enum ezt_trace_status thread_status = ezt_trace_status_uninitialized;
__thread uint64_t thread_rank=0;
__thread uint64_t otf2_thread_id=0;
int using_mpi = 0;

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

static void init_recursion_shield() {
  static int init_done = 0;
  if (!init_done) {
    pthread_key_create(&protect_on, NULL);
    init_done = 1;

    todo_set_status("ezt_init_recursion_shield", init_complete);
  }
}

void eztrace_register_init_routine(eztrace_function_t init_func) {
  _ezt_trace.init_routines[_ezt_trace.nb_module++] = init_func;
}

int _eztrace_fd() {
  static int fd = -1;
  /* eztrace may have problems when printing message if the
     application closes stdout and/or stdout (this may happen at the
     end of the application, before eztrace_stop is called).  

     To bypass this limitation, let's duplication stderr, and use the
     resulting fd for printing messages.
 */
  if(fd < 0) {
    EZTRACE_PROTECT {
      EZTRACE_PROTECT_ON();
      fd=dup(2);			/* duplicate stderr */
      EZTRACE_PROTECT_OFF();
    } else {
      return 2;			/* couldn't call dup, so let's return stderr */
    }
  }
  return fd;
}

static char const* _eztrace_get_filedir() {
  char* res = getenv("EZTRACE_TRACE_DIR");
  return res;
}

void eztrace_set_filename() {
  /* check if user specified a dirname */
  const char* str = _eztrace_get_filedir();

  if (_ezt_trace.filename) {
    free(_ezt_trace.filename);
    _ezt_trace.filename = NULL;
  }

  if(str) {
    _ezt_trace.filename = strdup(str);
  } else {
    /* no dirname specified, use the program name */
    char cmd[1024];
    _eztrace_get_current_program_name(cmd);

    _ezt_trace.filename = malloc(1050);
    snprintf(_ezt_trace.filename, 1050, "%s_trace", cmd);
  }

  printf("dir: %s\n", _ezt_trace.filename);
}


/* return the library name from a symbol string */
static char* get_lib_name(char* symbol) {
  char* ret;
  int begin = 0;
  int end = -1;
  int i = 0;

  /* the format of symbol is : '/path/to/libxxx.so (function+0xoffset) [0xaddress]*/
  /* so we need to locate the last / and the first ( */
  while (symbol[i] != 0) {
    if (symbol[i] == '/')
      begin = i + 1;
    if (symbol[i] == '(') {
      end = i;
      break;
    }
    i++;
  }

  ret = &symbol[begin];
  /* replace ( with a \0 */
  if (end >= 0)
    symbol[end] = 0;
  return ret;
}

/* return the function name (or library name if the function name is unknown)
 * from a symbol string.
 * This function may alter the symbol string.
 */
static char* get_function_name(char* symbol) {
  char* ret = symbol;
  int len = strlen(symbol);
  int begin = 0;
  int end = 0;
  int i;

  /* the format of symbol is : 'libxxx.so (function+0xoffset) [0xaddress]*/
  /* the goal is to retrieve the function+0xoffset string */
  for (i = 0; i < len; i++) {
    if (symbol[i] == '(') {
      begin = i;
      if (symbol[i + 1] == '+' || symbol[i + 1] == ')') {
        return get_lib_name(symbol);
      }
    }
    if (symbol[i] == ')') {
      end = i;
      break;
    }
  }
  if (begin == 0) {
    return symbol;
  }
  ret = &symbol[begin + 1];
  symbol[end] = 0;
  return ret;
}

void eztrace_error_handler(int signo) {
  static volatile int shield = 0;
  /* in case several thread receive signals simultaneously, only the first one
   * handle it.
   */
  while (shield)
    ;
  shield = 1;

  set_recursion_shield_on();

  eztrace_log(dbg_lvl_normal, "[EZTrace] signal %d catched. my pid=%d\n", signo, getpid());

  //  void* buffer[50];
  /* get pointers to functions */
  //  int nb_calls = backtrace(buffer, 50);

  //  EZTRACE_EVENT_PACKED_2(EZTRACE_SIGNAL_RECEIVED, signo, nb_calls);
  //  eztrace_record_backtrace(nb_calls);
  set_recursion_shield_off();
}

void eztrace_abort() {
  eztrace_log(dbg_lvl_normal, "EZTrace aborts.\n");

  char* res = getenv("EZTRACE_DEBUGGER");
  if(res) {
    char host_name[1024];
    gethostname(host_name, 1024);
    eztrace_log(dbg_lvl_error, "To debug this problem, connect to machine %s and run gdb -p %d\n",
		host_name, getpid());
    while(1);
  }
  abort();
}

void eztrace_signal_handler(int signo) {
  static volatile int shield = 0;
  /* in case several thread receive signals simultaneously, only the first one
   * handle it.
   */
  while (shield)
    ;
  shield = 1;

  eztrace_log(dbg_lvl_error, "EZTrace received signal %d...\n", signo);
  if (signo == SIGSEGV)
    eztrace_error_handler(signo);

  eztrace_stop();
  eztrace_log(dbg_lvl_normal, "Signal handling done\n");
  exit(EXIT_FAILURE);
}

/* when an alarm signal is received, check for sampling information */
void _eztrace_alarm_sighandler(int signo __attribute__((unused))) {
  ezt_sampling_check_callbacks();
}

#ifdef __linux__
long _ezt_alarm_interval = 0;
int alarm_enabled = 0;
int alarm_set = 0;

void eztrace_set_alarm() {
  if (_ezt_alarm_interval >= 0 && alarm_enabled && (!alarm_set)) {
    alarm_set = 1;

    struct sigevent sevp;
    sevp.sigev_notify = SIGEV_THREAD_ID | SIGEV_SIGNAL;
    sevp.sigev_signo = SIGALRM;
    sevp.sigev_value.sival_int = 0;
    sevp.sigev_notify_function = NULL;
    sevp.sigev_notify_attributes = NULL;
    sevp._sigev_un._tid = pthread_self();

    timer_t* t = malloc(sizeof(timer_t));
    int ret = timer_create(CLOCK_REALTIME, &sevp, t);
    if (ret != 0) {
      eztrace_error("timer create failed: %s", strerror(errno));
    }

    struct itimerspec new_value, old_value;
    new_value.it_interval.tv_sec = 0;
    new_value.it_interval.tv_nsec = _ezt_alarm_interval;

    new_value.it_value.tv_sec = 0;
    new_value.it_value.tv_nsec = _ezt_alarm_interval;

    ret = timer_settime(*t, 0, &new_value, &old_value);
    if (ret != 0) {
      eztrace_error("timer settime failed: %s", strerror(errno));
    }
  }
}
#else
void eztrace_set_alarm() {
}
#endif

static void _eztrace_set_sighandler() {
  char* res = getenv("EZTRACE_SIGNAL_HANDLER");

  if (res && !strncmp(res, "1", 2)) {
    signal(SIGSEGV, eztrace_signal_handler);
    signal(SIGINT, eztrace_signal_handler);
    signal(SIGTERM, eztrace_signal_handler);
    signal(SIGABRT, eztrace_signal_handler);
    signal(SIGHUP, eztrace_signal_handler);
  }

#ifdef __linux__
  res = getenv("EZTRACE_SIGALARM");
  if (res && (strncmp(res, "0", 2) != 0)) {
    /* convert from ms to ns */
    alarm_enabled = 1;
    _ezt_alarm_interval = atoi(res) * 1000000;
    eztrace_log(dbg_lvl_debug, "[EZTrace] Setting an alarm every %d ms\n", atoi(res));
    signal(SIGALRM, _eztrace_alarm_sighandler);
    eztrace_set_alarm();
  }
#endif
}



void eztrace_start_() {
  eztrace_start();

}

static void _get_current_program_name(char* buffer) {
  char tmp_buffer[1024];
  FILE* f = fopen("/proc/self/cmdline", "r");
  fscanf(f, "%s", tmp_buffer);
  fclose(f);
  sprintf(buffer, "%s", basename(tmp_buffer));
}


static void do_nothing(int signo MAYBE_UNUSED) {
  /* noting to do */
}

void  _init_modules() ;

static void _eztrace_load_config() {
  static int done = 0;
  if(done) return;
  done=1;

  _ezt_timestamp_config = &_ezt_trace.timestamp_config;
  _ezt_trace.timestamp_config = TS_SOURCE_CLOCK_MONOTONIC | TS_RELATIVE;

  char* ts_src = getenv("EZTRACE_TIMESTAMP_SOURCE");
  if (ts_src) {
    if(strcmp(ts_src, "rdtsc") == 0) {
      _ezt_trace.timestamp_config ^= TS_SOURCE_CLOCK_MONOTONIC;
      _ezt_trace.timestamp_config |= TS_SOURCE_RDTSC;
    }
  }

  char* ts_config = getenv("EZTRACE_TIMESTAMP_CONFIG");
  if (ts_config) {
    if(strcmp(ts_config, "absolute") == 0) {
      _ezt_trace.timestamp_config ^= TS_RELATIVE;
      _ezt_trace.timestamp_config |= TS_ABSOLUTE;
    }
  }

  _ezt_trace.debug_level = dbg_lvl_normal;
  char* debug_mode = getenv("EZTRACE_DEBUG");
  if (debug_mode) {
    int dbg_lvl = atoi(debug_mode);
    if(dbg_lvl > dbg_lvl_max) dbg_lvl = dbg_lvl_max;
    _ezt_trace.debug_level = dbg_lvl;
    eztrace_log(dbg_lvl_normal, "EZTrace Debug mode enabled (trace level: %d)\n",
		_ezt_trace.debug_level);
  }
}

void _eztrace_init() {

  enum todo_status ezt_otf2_status = todo_get_status("ezt_otf2");
  if(ezt_otf2_status >= init_started &&  ezt_otf2_status < init_complete)
    /* ezt_otf2 initialization is pending */
    return;
  enum todo_status ezt_init_status = todo_get_status("eztrace_init");
  if(ezt_init_status == init_complete)
    /* eztrace_init already performed */
    return;

  eztrace_log(dbg_lvl_debug, "eztrace_init starts\n");

  todo_set_status("eztrace_init", init_started);

  /* send a signal so that the current process synchronizes with
   * pptrace (that waits for a signal, cf pptrace_install_hijack in pptrace.c)
   */
  signal(SIGUSR2, do_nothing);
  eztrace_log(dbg_lvl_debug, "eztrace_init: send SIGUSR2 to synchronize with pptrace\n");
  kill(getpid(), SIGUSR2);

  if(!using_mpi)
    _ezt_trace.status = ezt_trace_status_uninitialized;

  _eztrace_load_config();
  
  eztrace_log(dbg_lvl_normal, "Starting EZTrace (pid: %d)...\n", getpid());
  if(using_mpi) {
    eztrace_log(dbg_lvl_normal, "MPI mode selected\n");
  }

  /* make sure eztrace_stop is called when the program stops */
  atexit(eztrace_stop);
  _eztrace_set_sighandler();

  todo_set_status("eztrace_init", init_stopped);
  todo_set_status("eztrace_init", init_complete);
  eztrace_log(dbg_lvl_debug, "eztrace_init ends\n");
  return;
}

void ezt_init_complete() {
  todo_set_status("eztrace", init_complete);
  if(!using_mpi)
    eztrace_log(dbg_lvl_verbose, "EZTrace initialization is now complete.\n");
}

int ezt_is_mpi_mode() {
  if(dlsym(RTLD_DEFAULT, "MPI_Init") && dlsym(RTLD_DEFAULT, "ezt_mpi_initialize_trace")) {
    return 1;
  }
  return 0;
}


extern struct ezt_internal_module* ezt_otf2_module;
void eztrace_core_constructor() __attribute__((constructor));
void eztrace_core_constructor() {
  _eztrace_load_config();
  char cmd[1024];
  _get_current_program_name(cmd);

  if((strcmp(cmd, "eztrace_avail") == 0) ||
     (strcmp(cmd, "eztrace") == 0) ||
     getenv("EZTRACE_DONT_TRACE"))
    eztrace_should_trace = 0;
  else
    eztrace_should_trace = 1;

  eztrace_log(dbg_lvl_debug, "eztrace_core constructor starts\n");

  enqueue_todo("eztrace_init", _eztrace_init, "ezt_otf2", init_complete);
  enqueue_todo("ezt_init_recursion_shield", init_recursion_shield, "eztrace_init", init_complete);
  enqueue_todo("ezt_init_thread", ezt_init_thread, "eztrace_init", init_complete);
  enqueue_todo("ezt_init_modules", _init_modules, "eztrace_init", init_complete);

  if(ezt_is_mpi_mode()) {
    using_mpi = 1;
    if(todo_get_status("ezt_mpi") != init_complete) {
      add_todo_dependency("eztrace_init", "mpi_init", init_complete);
      add_todo_dependency("ezt_otf2", "mpi_init", init_complete);
    }

    /* using MPI. The "eztrace" task does not depend on anything.
     * This way, the constructors can finish and let the application call MPI_Init
     */
    enqueue_todo("eztrace", ezt_init_complete, NULL, status_invalid);
  } else {
    /* not using MPI. We need to make sure */
    enqueue_todo("eztrace", ezt_init_complete, "eztrace_init", init_complete);
    add_todo_dependency("eztrace", "ezt_otf2", init_complete);
  }

  todo_progress();
  eztrace_log(dbg_lvl_debug, "eztrace_core constructor ends\n");
}

void eztrace_start() {
  /* avoid executing this function several times */
  if (_ezt_trace.status >= ezt_trace_status_running)
    return;

  todo_progress();
  
  _ezt_trace.status = ezt_trace_status_running;

  /* now, we can start recording events. Call pthread_core so that it
     records the Enter("Working") event
  */
  ezt_pthread_first_event();

  /* the current thread needs to be registered since eztrace won't
   * intercept any pthread_create for this one
   */
  if (!eztrace_autostart_enabled()) {
    /* call the initialisation routines that were registered */
    for (int i = 0; i < _ezt_trace.nb_module; i++)
      _ezt_trace.init_routines[i]();
  }

}


void eztrace_stop_() {
  eztrace_stop();
}

void eztrace_stop() {
  if (_ezt_trace.status >= ezt_trace_status_being_finalized ||
      _ezt_trace.status < ezt_trace_status_running)
    return;

  _ezt_trace.status = ezt_trace_status_being_finalized;
  finalize_modules();

  _eztrace_run_atexit_functions();

  ezt_otf2_finalize();
  _ezt_trace.status = ezt_trace_status_finalized;

  if(eztrace_should_trace) {
    eztrace_log(dbg_lvl_normal, "Stopping EZTrace (pid:%d)...\n", getpid());
  }
}

void eztrace_silent_start() {
  eztrace_start();
  _ezt_trace.status = ezt_trace_status_paused;
}

void eztrace_pause() {
  _ezt_trace.status = ezt_trace_status_paused;
}

void eztrace_resume() {
  _ezt_trace.status = ezt_trace_status_running;
}

int recursion_shield_on() {
  init_recursion_shield();
  void* ret = NULL;
  ret = pthread_getspecific(protect_on);
  return (ret != NULL);
}

void set_recursion_shield_on() {
  init_recursion_shield();
  pthread_setspecific(protect_on, (void*)1);
}
void set_recursion_shield_off() {
  pthread_setspecific(protect_on, (void*)NULL);
}

static void _eztrace_get_function_name_from_pointer_fallback(void* pointer,
							     char* output_str,
							     int buffer_len MAYBE_UNUSED) {
  char** functions;
  functions = backtrace_symbols(pointer, 1);
  char* ptr = get_function_name(functions[0]);
  strncpy(output_str, ptr, strlen(ptr) + 1);
  free(functions);
}

#if HAVE_LIBBACKTRACE

__thread struct backtrace_state* backtrace_state = NULL;
__thread char current_frame[1024];

static void error_callback(void* data, const char* msg, int errnum) {
  eztrace_error("ERROR: %s (%d)", msg, errnum);
}

static int backtrace_callback(void* data, uintptr_t pc,
                              const char* filename, int lineno,
                              const char* function) {
  if (filename == NULL) {
    /* cannot find debugging info */
    _eztrace_get_function_name_from_pointer_fallback((void*)pc, current_frame, 1024);
  } else {
    snprintf(current_frame, 1024, "at %s:%d %s", filename, lineno, function);
  }
  return 0;
}
#endif /* HAVE_LIBBACKTRACE */

/* retrieve the name of the function corresponding to pointer
 * the collected information is copied into output_str
 * buffer_len is the maximum number of bytes written to output_str
 */
static void _eztrace_get_function_name_from_pointer(void* pointer,
						    char* output_str,
						    int buffer_len) {
#if HAVE_LIBBACKTRACE
  if (!backtrace_state) {
    backtrace_state = backtrace_create_state(NULL, 0, error_callback, NULL);
  }
  backtrace_pcinfo(backtrace_state, (uintptr_t)pointer,
                   backtrace_callback,
                   error_callback,
                   NULL);

  strncpy(output_str, current_frame, strlen(current_frame) + 1);
#else
  _eztrace_get_function_name_from_pointer_fallback(pointer, output_str, buffer_len);
#endif
}

/* get information on frame frameid
 * the collected information is copied into output_str
 * buffer_len is the maximum number of bytes written to output_str
 */
void eztrace_get_stack_frame(int frameid,
                             char* output_str,
                             int buffer_len) {
  void* buffer[frameid + 1];

  /* get pointers to functions */
  int nb_calls = backtrace(buffer, frameid + 1);
  if (nb_calls > frameid) {
    _eztrace_get_function_name_from_pointer(buffer[frameid],
					    output_str,
					    buffer_len);
  } else if (buffer_len > 0) {
    output_str[0] = '\0';
  }
}

#define RECORD_BACKTRACES 1
#ifdef RECORD_BACKTRACES

/* record events (code=EZTRACE_CALLING_FUNCTION) containing the backtrace */
void eztrace_record_backtrace(int backtrace_depth __attribute__((unused))) {
  /* the current backtrace looks like this:
   * 0 - record_backtrace()
   * 1 - eztrace_callback()
   * 2 - calling_function
   *
   * So, we need to get the name of the function in frame 2.
   */
#if 0
  void* bt_buffer[backtrace_depth];

  /* get pointers to functions */
  int nb_calls = backtrace(bt_buffer, backtrace_depth);

  EZTRACE_EVENT1_PACKED_UNPROTECTED(EZTRACE_BACKTRACE_DEPTH, nb_calls);
  int i;
  for (i = 0; i < nb_calls; i++) {
    char buffer[1024];
    _eztrace_get_function_name_from_pointer(bt_buffer[i], buffer, 1024);
    litl_write_probe_raw(_ezt_trace.litl_trace, EZTRACE_CALLING_FUNCTION, strlen(buffer), buffer);
  }
#endif
}

void record_backtrace() {
  EZTRACE_PROTECT {
    EZTRACE_PROTECT_ON();
    eztrace_record_backtrace(15);
    EZTRACE_PROTECT_OFF();
  }
}

#else
void eztrace_record_backtrace(int backtrace_depth) {
}
void record_backtrace() {
}
#endif

struct eztrace_atexit_token_t {
  eztrace_atexit_function_t func;
  void* param;
};

struct eztrace_atexit_list_t {
  struct eztrace_atexit_token_t* list;
  int nb_allocated;
  int nb_functions;
};

struct eztrace_atexit_list_t* atexit_list = NULL;

/* register a function to be called before eztrace_stop. */
void eztrace_atexit(eztrace_atexit_function_t f, void* param) {
  if (!atexit_list) {
    /* first time this function is called. Allocate/initialize the atexit_list structure */
    atexit_list = malloc(sizeof(struct eztrace_atexit_list_t));
    atexit_list->nb_allocated = 10;
    atexit_list->list = malloc(sizeof(struct eztrace_atexit_token_t) * atexit_list->nb_allocated);
    atexit_list->nb_functions = 0;
  }
  int num = atexit_list->nb_functions++;

  if (num >= atexit_list->nb_allocated) {
    /* too many atexit callbacks. We need to expand the array */
    atexit_list->nb_allocated *= 2;
    void* ptr = realloc(atexit_list->list, sizeof(struct eztrace_atexit_token_t) * atexit_list->nb_allocated);
    assert(ptr);
    atexit_list->list = ptr;
  }

  atexit_list->list[num].func = f;
  atexit_list->list[num].param = param;
}

/* run all the atexit callbacks that were registered */
static void _eztrace_run_atexit_functions() {
  if (atexit_list) {
    int i;
    for (i = 0; i < atexit_list->nb_functions; i++) {
      atexit_list->list[i].func(atexit_list->list[i].param);
    }
  }
}
