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

#include <stdio.h>
#include <GTG.h>
#include <assert.h>
#include "gomp_ev_codes.h"
#include "eztrace_list.h"
#include "eztrace_stack.h"
#include "eztrace_convert.h"
#include "pomp-lib-dummy/pomp2_lib.h"

static struct ezt_list_t section_list;

static int recording_stats = 0;
static int nb_parallel_region = 0;

/* maximum number of level for nested tasks */
#define MAX_NESTED_TASKS 128

#define GOMP_CHANGE() if(!recording_stats) CHANGE()

#if 0
# define OMP_PRINTF printf x
#else
# define OMP_PRINTF(...)
#endif

/* contains all the information of a parallel section */
struct gomp_section_t {
  void (*func)(void *);
  app_ptr data;
  POMP2_Region_handle id;
  int nb_threads;
  struct eztrace_container_t * main_thread;
  struct eztrace_container_t ** working_threads;
  char** link_value_start;
  char** link_id_start;

  char** link_value_end;
  char** link_id_end;
  float start_time; /* time at which the parallel_start occurs */
  float* fork_time; /* time at which the fork happens for each thread */
  float* join_time; /* time at which the thread reach join */
  float join_done_time; /* time at which the join ends */
  int nb_joined;	/* number of threads that finished end_loop */
  ezt_stack_token_t token;
};

/* pointer to a parallel section. This structure is used in the
 * section_stack field of the gomp_thread_info_t structure
 */
struct gomp_thread_section_t {
  struct gomp_section_t *process_section;
  ezt_stack_token_t token;
};

/* contains the list of parallel sections that a thread is currently processing */
struct gomp_thread_info_t {
  ezt_stack_t section_stack;

  /* timestamp of the last start_task_create event */
  double start_create_task[MAX_NESTED_TASKS];
  int nb_nested_task_creation;
  /* timestamp of the last start_task_exec event */
  double start_exec_task[MAX_NESTED_TASKS];
  int nb_nested_tasks;

  /* total time spent creating tasks */
  double create_task_duration;
  /* number of created tasks */
  unsigned nb_create_task;
  /* total time spent executing tasks */
  double exec_task_duration;
  /* number of executed tasks */
  unsigned nb_exec_task;

  /* TODO: also count the parallel loops ? */
  struct thread_info_t *p_thread;
};

/* contains the list of parallel sections that a process is currently processing */
struct gomp_process_info_t {
  struct ezt_list_t section_list;
  struct process_info_t *p_process;
  struct ezt_list_t finished_section_list;

  /* total time spent creating tasks */
  double create_task_duration;
  /* number of created tasks */
  unsigned nb_create_task;
  /* total time spent executing tasks */
  double exec_task_duration;
  /* number of executed tasks */
  unsigned nb_exec_task;
};

/* add a hook in the process structure in order to store information
 * about pending parallel sections
 */
static struct gomp_process_info_t *__register_process_hook(
    struct process_info_t *p_process) {
  struct gomp_process_info_t *g_info = (struct gomp_process_info_t*) malloc(
      sizeof(struct gomp_process_info_t));
  g_info->p_process = p_process;
  ezt_list_new(&g_info->section_list);
  ezt_list_new(&g_info->finished_section_list);

  g_info->create_task_duration=0;
  g_info->nb_create_task=0;
  g_info->exec_task_duration=0;
  g_info->nb_exec_task=0;

  /* add the hook in the thread info structure */
  ezt_hook_list_add(&g_info->p_process->hooks, g_info,
		    (uint8_t) EZTRACE_GOMP_EVENTS_ID);
  return g_info;
}

/* add a hook in the thread structure in order to store information
 * about pending parallel sections
 */
struct gomp_thread_info_t *__register_thread_hook(int tid) {
  DECLARE_CUR_PROCESS(p_process);
  struct gomp_thread_info_t *g_info = (struct gomp_thread_info_t*) malloc(
      sizeof(struct gomp_thread_info_t));
  g_info->p_thread = GET_THREAD_INFO(CUR_INDEX, tid);
  ezt_stack_new(&g_info->section_stack);

  int i;
  for(i=0; i<MAX_NESTED_TASKS; i++) {
    g_info->start_exec_task[i] = -1;
    g_info->start_create_task[i] = -1;
  }
  g_info->nb_nested_tasks = 0;
  g_info->nb_nested_task_creation = 0;

  g_info->nb_create_task = 0;
  g_info->nb_exec_task = 0;

  g_info->create_task_duration = 0;
  g_info->exec_task_duration = 0;

  /* add the hook in the thread info structure */
  ezt_hook_list_add(&g_info->p_thread->hooks, g_info,
		    (uint8_t) EZTRACE_GOMP_EVENTS_ID);
  return g_info;
}

/* declare a var variable that points to the process_info structure */
#define  INIT_GOMP_PROCESS_INFO(p_process, var)				\
  struct gomp_process_info_t *var = (struct gomp_process_info_t*)	\
    ezt_hook_list_retrieve_data(&p_process->hooks, (uint8_t)EZTRACE_GOMP_EVENTS_ID); \
  if(!(var)) {								\
    var = __register_process_hook(p_process);				\
  }

/* declare a var variable that points to the thread_info structure */
#define INIT_GOMP_THREAD_INFO(p_thread_info, var)			\
  struct gomp_thread_info_t *var = (struct gomp_thread_info_t*)		\
    ezt_hook_list_retrieve_data(&p_thread_info->hooks, (uint8_t)EZTRACE_GOMP_EVENTS_ID); \
  if(!(var)) {								\
    var = __register_thread_hook(CUR_THREAD_ID);				\
  }

/* retrieve the gomp_section structure that corresponds to a section id */
struct ezt_list_token_t *__find_matching_section(
    struct process_info_t*p_process, POMP2_Region_handle id) {
  INIT_GOMP_PROCESS_INFO(p_process, process_info)
  struct ezt_list_token_t *token = NULL;
  ezt_list_foreach(&process_info->section_list, token)
  {
    struct gomp_section_t * section = (struct gomp_section_t *) token->data;
    if (section->id == id)
      return token;
  }

  return NULL;
}

void handle_gomp_new_fork() {

  /* This event occurs when a parallel section begins.
   * This event is generated by the new thread (ie. not by the main thread
   * that called #omp parallel)
   */
  FUNC_NAME;

  /* We have to:
   * - retrieve the gomp_section structur that was created by the main thread
   * - add this section to the thread's stack
   * - create the links between the main thread and the current thread
   * - set the state of the current thread to 'working'
   */
  DECLARE_CUR_PROCESS(p_process);
  DECLARE_CUR_THREAD(p_thread);
  INIT_GOMP_THREAD_INFO(p_thread, g_info);

  POMP2_Region_handle section_id;
  int my_id;
  int nb_threads;
  GET_PARAM_PACKED_3(CUR_EV, section_id, my_id, nb_threads);

  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);

  /* retrieve the gomp_section structure that was created by the main thread */
  OMP_PRINTF("[%x] %s %d (%d threads)\n", CUR_THREAD_ID, __FUNCTION__, section_id, nb_threads);

  struct ezt_list_token_t *token = __find_matching_section(p_process,
							   section_id);
  assert(token);

  struct gomp_section_t * section = (struct gomp_section_t *) token->data;

  assert(section->main_thread);
  char* main_thread_str = section->main_thread->id;

  if (section->nb_threads) {
    assert(section->nb_threads == nb_threads);
  }

  if (!section->nb_threads) {
    /* We are the first thread that reach the section, allocate everything */
    section->id = section_id;
    section->nb_threads = nb_threads;
    section->nb_joined = 0;
    section->working_threads = malloc(
	sizeof(struct eztrace_container_t*) * section->nb_threads);

    section->link_value_start = malloc(sizeof(char*) * section->nb_threads);
    section->link_id_start = malloc(sizeof(char*) * section->nb_threads);

    section->link_value_end = malloc(sizeof(char*) * section->nb_threads);
    section->link_id_end = malloc(sizeof(char*) * section->nb_threads);

    section->fork_time = malloc(sizeof(float) * section->nb_threads);
    section->join_time = malloc(sizeof(float) * section->nb_threads);
  }

  assert(section->id == section_id);

  section->working_threads[my_id] =
    GET_THREAD_CONTAINER(CUR_INDEX, CUR_THREAD_ID);
  section->fork_time[my_id] = CURRENT;

  /* create the links between the main thread and the current thread */
  int res __attribute__ ((__unused__));
  res = asprintf(&section->link_id_start[my_id], "%d_%d_%d", CUR_RANK,
		 section->id, my_id);
  res = asprintf(&section->link_value_start[my_id], "p#%d_section#%d_thread#%d",
		 CUR_RANK, section->id, my_id);

  /* add this section to the thread's stack */
  struct gomp_thread_section_t *p_th_section =
    (struct gomp_thread_section_t*) malloc(
	sizeof(struct gomp_thread_section_t));
  p_th_section->process_section = section;
  p_th_section->token.data = p_th_section;
  ezt_stack_push(&g_info->section_stack, &p_th_section->token);

  GOMP_CHANGE()
    startLink(section->start_time, "GOMP_Section_Link", "C_Prog",
	      main_thread_str, thread_id, section->link_value_start[my_id],
	      section->link_id_start[my_id]);
  GOMP_CHANGE()
    endLink(CURRENT, "GOMP_Section_Link", "C_Prog", main_thread_str, thread_id,
	    section->link_value_start[my_id], section->link_id_start[my_id]);

  /* set the state of the current thread to 'working' */
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Section_State");
}

void handle_gomp_new_join() {
  /* This event occurs when a parallel section ends.
   * This event is generated by the new thread (ie. not by the main thread
   * that called #omp parallel)
   */
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);

  /* We have to:
   * - retrieve and remove the gomp_section structure that was stacked
   * - create the links between the main thread and the current thread
   * - set the state of the current thread to 'blocked'
   */

  DECLARE_CUR_THREAD(p_thread);
  INIT_GOMP_THREAD_INFO(p_thread, g_info);

  DECLARE_CUR_PROCESS(p_process);
  INIT_GOMP_PROCESS_INFO(p_process, process_info);

  int my_id;
  GET_PARAM_PACKED_1(CUR_EV, my_id);

  /* retrieve and remove the gomp_section structure that was stacked */
  ezt_stack_token_t *token = ezt_stack_get_top(&g_info->section_stack);
  if(g_info->section_stack.nb_item) {
    assert(token);
    struct gomp_thread_section_t *thread_section = (struct gomp_thread_section_t *) token->data;
    struct gomp_section_t *section = thread_section->process_section;

    OMP_PRINTF("[%x] %s %d\n", CUR_THREAD_ID, __FUNCTION__, section->id);

    section->join_time[my_id] = CURRENT;
    /* create the links between the main thread and the current thread */
    int res __attribute__ ((__unused__));
    res = asprintf(&section->link_id_end[my_id], "%d_%d_%d_end", CUR_RANK,
		   section->id, my_id);
    res = asprintf(&section->link_value_end[my_id], "p#%d_section#%d_thread#%d",
		   CUR_RANK, section->id, my_id);

    GOMP_CHANGE()
      startLink(CURRENT, "GOMP_Section_Link", "C_Prog", thread_id,
		section->main_thread->id, section->link_value_end[my_id],
		section->link_id_end[my_id]);

    section->nb_joined++;
    if(section->nb_joined == section->nb_threads) {
      /* This is the last thread to finish end_loop */

      section->join_done_time = CURRENT;
      /* end all the links that were started by the new_join event */
      int i;
      for (i = 0; i < section->nb_threads; i++) {
	GOMP_CHANGE()
	  endLink(CURRENT, "GOMP_Section_Link", "C_Prog",
		  section->working_threads[i]->id, section->main_thread->id,
		  section->link_value_end[i], section->link_id_end[i]);
      }

      /* free the section structure and set the current state to working */
      struct ezt_list_token_t *process_section_token = __find_matching_section(p_process, section->id);
      assert(process_section_token);
      ezt_list_remove(process_section_token);
      ezt_list_add(&process_info->finished_section_list, &section->token);
    }
    GOMP_CHANGE()
      popState(CURRENT, "ST_Thread", thread_id);
  }
}

void handle_gomp_parallel_start() {
  /* This event occurs when a parallel section begins.
   * This event is generated by the main thread
   */
  FUNC_NAME;
  struct gomp_section_t * section = (struct gomp_section_t*) malloc(
      sizeof(struct gomp_section_t));

  nb_parallel_region++;
  /* Initialize the section strcuture */
  GET_PARAM_PACKED_1(CUR_EV, section->id);
  section->nb_threads = 0;
  section->main_thread = GET_THREAD_CONTAINER(CUR_INDEX, CUR_THREAD_ID);
  section->working_threads = NULL;
  section->start_time = CURRENT;
  section->token.data = section;
  DECLARE_CUR_PROCESS(p_process);
  INIT_GOMP_PROCESS_INFO(p_process, g_info);

  OMP_PRINTF("[%x] %s %d\n", CUR_THREAD_ID, __FUNCTION__, section->id);

  /* add the section structure to the list of pending sections */
  ezt_list_add(&g_info->section_list, &section->token);
}

void handle_gomp_critical_start() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "STV_Blocked");
}

void handle_gomp_critical_start_done() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Critical");
}

void handle_gomp_critical_stop() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_finalize() {
}

void handle_pomp2_atomic_enter() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Atomic");
}

void handle_pomp2_atomic_exit() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_critical_enter() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "STV_Blocked");
}

void handle_pomp2_critical_begin() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Critical");
}

void handle_pomp2_critical_end() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_critical_exit() {
}

void handle_pomp2_barrier_enter() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Barrier");
}

void handle_pomp2_barrier_exit() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_implicit_barrier_enter() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Implicit_Barrier");
}

void handle_pomp2_implicit_barrier_exit() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_flush_enter() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Flush");
}

void handle_pomp2_flush_exit() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_task_create_begin() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  DECLARE_CUR_THREAD(p_thread);
  INIT_GOMP_THREAD_INFO(p_thread, g_info);

  assert(g_info->start_create_task[g_info->nb_nested_task_creation] < 0);
  assert(g_info->nb_nested_task_creation < MAX_NESTED_TASKS);

  g_info->start_create_task[g_info->nb_nested_task_creation] = CURRENT;
  g_info->nb_nested_task_creation++;

  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Task_Create");
}

void handle_pomp2_task_create_end() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  DECLARE_CUR_THREAD(p_thread);
  INIT_GOMP_THREAD_INFO(p_thread, g_info);

  assert(g_info->start_create_task[g_info->nb_nested_task_creation-1] > 0);
  g_info->create_task_duration += CURRENT-g_info->start_create_task[g_info->nb_nested_task_creation-1];
  g_info->start_create_task[g_info->nb_nested_task_creation-1] = -1;
  g_info->nb_nested_task_creation--;
  g_info->nb_create_task++;

  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_untied_task_create_begin() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  DECLARE_CUR_THREAD(p_thread);
  INIT_GOMP_THREAD_INFO(p_thread, g_info);

  assert(g_info->start_create_task[g_info->nb_nested_task_creation] < 0);
  assert(g_info->nb_nested_task_creation < MAX_NESTED_TASKS);

  g_info->start_create_task[g_info->nb_nested_task_creation] = CURRENT;
  g_info->nb_nested_task_creation++;

  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Untied_Task_Create");
}

void handle_pomp2_untied_task_create_end() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  DECLARE_CUR_THREAD(p_thread);
  INIT_GOMP_THREAD_INFO(p_thread, g_info);

  assert(g_info->start_create_task[g_info->nb_nested_task_creation-1] > 0);
  g_info->create_task_duration += CURRENT-g_info->start_create_task[g_info->nb_nested_task_creation-1];
  g_info->start_create_task[g_info->nb_nested_task_creation-1] = -1;
  g_info->nb_nested_task_creation--;
  g_info->nb_create_task++;

  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}


void handle_pomp2_task_begin() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  DECLARE_CUR_THREAD(p_thread);
  INIT_GOMP_THREAD_INFO(p_thread, g_info);
  assert(g_info->start_exec_task[g_info->nb_nested_tasks] < 0);
  assert(g_info->nb_nested_tasks<MAX_NESTED_TASKS);

  g_info->start_exec_task[g_info->nb_nested_tasks] = CURRENT;
  g_info->nb_nested_tasks++;

  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Task");
}

void handle_pomp2_task_end() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  DECLARE_CUR_THREAD(p_thread);
  INIT_GOMP_THREAD_INFO(p_thread, g_info);
  assert(g_info->start_exec_task[g_info->nb_nested_tasks-1] > 0);
  g_info->exec_task_duration += CURRENT-g_info->start_exec_task[g_info->nb_nested_tasks-1];
  g_info->start_exec_task[g_info->nb_nested_tasks-1] = -1;
  g_info->nb_nested_tasks--;
  g_info->nb_exec_task++;

  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_untied_task_begin() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  DECLARE_CUR_THREAD(p_thread);
  INIT_GOMP_THREAD_INFO(p_thread, g_info);

  assert(g_info->start_exec_task[g_info->nb_nested_tasks] < 0);
  assert(g_info->nb_nested_tasks<MAX_NESTED_TASKS);

  g_info->start_exec_task[g_info->nb_nested_tasks] = CURRENT;
  g_info->nb_nested_tasks++;

  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Untied_Task");
}

void handle_pomp2_untied_task_end() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  DECLARE_CUR_THREAD(p_thread);
  INIT_GOMP_THREAD_INFO(p_thread, g_info);

  assert(g_info->start_exec_task[g_info->nb_nested_tasks-1] > 0);
  g_info->exec_task_duration += CURRENT-g_info->start_exec_task[g_info->nb_nested_tasks-1];
  g_info->start_exec_task[g_info->nb_nested_tasks-1] = -1;
  g_info->nb_nested_tasks--;
  g_info->nb_exec_task++;

  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_taskwait_begin() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_TaskWait");
}

void handle_pomp2_taskwait_end() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_for_enter() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_For");
}

void handle_pomp2_for_enter_extra() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  wait_for_an_event_in_cur_thread(EZTRACE_CALLING_FUNCTION);
  GOMP_CHANGE() {
    char *callsite = malloc(1024*sizeof(char));
    snprintf(callsite, 1024, "%s", LITL_READ_RAW(CUR_EV)->data);
    FUNC_NAME;
    pushStateExtra(CURRENT, "ST_Thread", thread_id, "GOMP_For", callsite);
  }
}

void handle_pomp2_for_exit() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_master_begin() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    pushState(CURRENT, "ST_Thread", thread_id, "GOMP_Master");
}

void handle_pomp2_master_end() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE()
    popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_parallel_begin() {
  /* Since the runtime functions provide more information, let's forget these compiler functions */
  FUNC_NAME;
}

void handle_pomp2_parallel_end() {
  /* Since the runtime functions provide more information, let's forget these compiler functions */
  FUNC_NAME;
}

void handle_pomp2_parallel_fork() {
  /* Since the runtime functions provide more information, let's forget these compiler functions */
  FUNC_NAME;
}

void handle_pomp2_parallel_join() {
  /* Since the runtime functions provide more information, let's forget these compiler functions */
  FUNC_NAME;
}

void handle_pomp2_sections_enter() {
  /* todo */
}

void handle_pomp2_sections_exit() {
  /* todo */
}

void handle_pomp2_section_begin() {
  /* todo */
}

void handle_pomp2_section_end() {
  /* todo */
}

void handle_pomp2_single_enter() {
  /* todo */
}

void handle_pomp2_single_exit() {
  /* todo */
}

void handle_pomp2_single_begin() {
  /* todo */
}

void handle_pomp2_single_end() {
  /* todo */
}

void handle_pomp2_workshare_enter() {
  /* todo */
}

void handle_pomp2_workshare_exit() {
  /* todo */
}

#ifdef OPENMP_FOUND
void handle_pomp2_set_lock_entry() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE() pushState(CURRENT, "ST_Thread", thread_id, "STV_GOMP_Lock");
}

void handle_pomp2_set_lock_exit() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE() popState(CURRENT, "ST_Thread", thread_id);
  GOMP_CHANGE() pushState(CURRENT, "ST_Thread", thread_id, "STV_GOMP_Locked");
}

void handle_pomp2_test_lock_success() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE() pushState(CURRENT, "ST_Thread", thread_id, "STV_GOMP_Locked");
}

void handle_pomp2_unset_lock() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE() popState(CURRENT, "ST_Thread", thread_id);
}

void handle_pomp2_set_nest_lock_entry() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE() pushState(CURRENT, "ST_Thread", thread_id, "STV_GOMP_NLock");
}

void handle_pomp2_set_nest_lock_exit() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE() popState(CURRENT, "ST_Thread", thread_id);
  GOMP_CHANGE() pushState(CURRENT, "ST_Thread", thread_id, "STV_GOMP_NLocked");
}

void handle_pomp2_test_nest_lock_success() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE() pushState(CURRENT, "ST_Thread", thread_id, "STV_GOMP_NLocked");
}

void handle_pomp2_unset_nest_lock() {
  FUNC_NAME;
  DECLARE_THREAD_ID_STR(thread_id, CUR_INDEX, CUR_THREAD_ID);
  GOMP_CHANGE() popState(CURRENT, "ST_Thread", thread_id);
}

#endif /* OPENMP_FOUND */

int eztrace_convert_gomp_init() {
  if (get_mode() == EZTRACE_CONVERT) {
    addLinkType("GOMP_Section_Link", "OpenMP Parallel Section", "CT_Program",
		"CT_Thread", "CT_Thread");

    addEntityValue("GOMP_Section_State", "ST_Thread", "GOMP_Section_State",
		   GTG_PINK);
    addEntityValue("GOMP_Critical", "ST_Thread", "GOMP_Critical", GTG_GREEN);
    addEntityValue("GOMP_Atomic", "ST_Thread", "GOMP_Atomic", GTG_GREEN);
    addEntityValue("GOMP_Barrier", "ST_Thread", "GOMP_Barrier", GTG_ORANGE);
    addEntityValue("GOMP_Implicit_Barrier", "ST_Thread", "GOMP_Implicit_Barrier", GTG_RED);

    addEntityValue("GOMP_For", "ST_Thread", "GOMP_For", GTG_LIGHTGREY);
    addEntityValue("GOMP_Master", "ST_Thread", "GOMP_Master", GTG_WHITE);

#ifdef OPENMP_FOUND
    addEntityValue("STV_GOMP_Lock", "ST_Thread", "Blocked on an OpenMP lock", GTG_RED);
    addEntityValue("STV_GOMP_Locked", "ST_Thread", "Holding an OpenMP lock", GTG_GREEN);
    addEntityValue("STV_GOMP_NLock", "ST_Thread", "Blocked on an OpenMP nested lock", GTG_RED);
    addEntityValue("STV_GOMP_NLocked", "ST_Thread", "Holding an OpenMP nested lock", GTG_GREEN);
#endif /* OPENMP_FOUND */

    addEntityValue("GOMP_TaskWait", "ST_Thread", "GOMP_TaskWait", GTG_ORANGE);

    addEntityValue("GOMP_Task", "ST_Thread", "GOMP_Task", GTG_WHITE);
    addEntityValue("GOMP_Untied_Task", "ST_Thread", "GOMP_Untied_Task", GTG_TEAL);
    addEntityValue("GOMP_Task_Create", "ST_Thread", "Creating an OpenMP Task",
		   GTG_KAKI);
    addEntityValue("GOMP_Untied_Task_Create", "ST_Thread",
		   "Creating an OpenMP Task", GTG_SEABLUE);
  }
  ezt_list_new(&section_list);
  return 0;
}

/* return 1 if the event was handled */
int handle_gomp_events(eztrace_event_t *ev) {
  if (!STARTED)
    return 0;

  switch (LITL_READ_GET_CODE(ev)) {
  /* OpenMP */
  case EZTRACE_GOMP_NEW_FORK:
    handle_gomp_new_fork();
    break;
  case EZTRACE_GOMP_NEW_JOIN:
    handle_gomp_new_join();
    break;
  case EZTRACE_GOMP_PARALLEL_START:
    handle_gomp_parallel_start();
    break;

  case EZTRACE_GOMP_CRITICAL_START:
    handle_gomp_critical_start();
    break;
  case EZTRACE_GOMP_CRITICAL_START_DONE:
    handle_gomp_critical_start_done();
    break;
  case EZTRACE_GOMP_CRITICAL_STOP:
    handle_gomp_critical_stop();
    break;

  case EZTRACE_POMP2_FINALIZE:
    handle_pomp2_finalize();
    break;
  case EZTRACE_POMP2_ATOMIC_ENTER:
    handle_pomp2_atomic_enter();
    break;
  case EZTRACE_POMP2_ATOMIC_EXIT:
    handle_pomp2_atomic_exit();
    break;
  case EZTRACE_POMP2_BARRIER_ENTER:
    handle_pomp2_barrier_enter();
    break;
  case EZTRACE_POMP2_BARRIER_EXIT:
    handle_pomp2_barrier_exit();
    break;
  case EZTRACE_POMP2_IMPLICIT_BARRIER_ENTER:
    handle_pomp2_implicit_barrier_enter();
    break;
  case EZTRACE_POMP2_IMPLICIT_BARRIER_EXIT:
    handle_pomp2_implicit_barrier_exit();
    break;
  case EZTRACE_POMP2_FLUSH_ENTER:
    handle_pomp2_flush_enter();
    break;
  case EZTRACE_POMP2_FLUSH_EXIT:
    handle_pomp2_flush_exit();
    break;
  case EZTRACE_POMP2_CRITICAL_ENTER:
    handle_pomp2_critical_enter();
    break;
  case EZTRACE_POMP2_CRITICAL_EXIT:
    handle_pomp2_critical_exit();
    break;
  case EZTRACE_POMP2_CRITICAL_BEGIN:
    handle_pomp2_critical_begin();
    break;
  case EZTRACE_POMP2_CRITICAL_END:
    handle_pomp2_critical_end();
    break;
  case EZTRACE_POMP2_FOR_ENTER:
    handle_pomp2_for_enter();
    break;
  case EZTRACE_POMP2_FOR_ENTER_EXTRA:
    handle_pomp2_for_enter_extra();
    break;
  case EZTRACE_POMP2_FOR_EXIT:
    handle_pomp2_for_exit();
    break;
  case EZTRACE_POMP2_MASTER_BEGIN:
    handle_pomp2_master_begin();
    break;
  case EZTRACE_POMP2_MASTER_END:
    handle_pomp2_master_end();
    break;
  case EZTRACE_POMP2_PARALLEL_BEGIN:
    handle_pomp2_parallel_begin();
    break;
  case EZTRACE_POMP2_PARALLEL_END:
    handle_pomp2_parallel_end();
    break;
  case EZTRACE_POMP2_PARALLEL_FORK:
    handle_pomp2_parallel_fork();
    break;
  case EZTRACE_POMP2_PARALLEL_JOIN:
    handle_pomp2_parallel_join();
    break;
  case EZTRACE_POMP2_SECTIONS_ENTER:
    handle_pomp2_sections_enter();
    break;
  case EZTRACE_POMP2_SECTIONS_EXIT:
    handle_pomp2_sections_exit();
    break;
  case EZTRACE_POMP2_SECTION_BEGIN:
    handle_pomp2_section_begin();
    break;
  case EZTRACE_POMP2_SECTION_END:
    handle_pomp2_section_end();
    break;
  case EZTRACE_POMP2_SINGLE_ENTER:
    handle_pomp2_single_enter();
    break;
  case EZTRACE_POMP2_SINGLE_EXIT:
    handle_pomp2_single_exit();
    break;
  case EZTRACE_POMP2_SINGLE_BEGIN:
    handle_pomp2_single_begin();
    break;
  case EZTRACE_POMP2_SINGLE_END:
    handle_pomp2_single_end();
    break;
  case EZTRACE_POMP2_WORKSHARE_ENTER:
    handle_pomp2_workshare_enter();
    break;
  case EZTRACE_POMP2_WORKSHARE_EXIT:
    handle_pomp2_workshare_exit();
    break;

  case EZTRACE_POMP2_TASK_CREATE_BEGIN:
    handle_pomp2_task_create_begin();
    break;
  case EZTRACE_POMP2_TASK_CREATE_END:
    handle_pomp2_task_create_end();
    break;
  case EZTRACE_POMP2_TASK_BEGIN:
    handle_pomp2_task_begin();
    break;
  case EZTRACE_POMP2_TASK_END:
    handle_pomp2_task_end();
    break;

  case EZTRACE_POMP2_UNTIED_TASK_CREATE_BEGIN:
    handle_pomp2_untied_task_create_begin();
    break;
  case EZTRACE_POMP2_UNTIED_TASK_CREATE_END:
    handle_pomp2_untied_task_create_end();
    break;
  case EZTRACE_POMP2_UNTIED_TASK_BEGIN:
    handle_pomp2_untied_task_begin();
    break;
  case EZTRACE_POMP2_UNTIED_TASK_END:
    handle_pomp2_untied_task_end();
    break;

  case EZTRACE_POMP2_TASKWAIT_BEGIN:
    handle_pomp2_taskwait_begin();
    break;
  case EZTRACE_POMP2_TASKWAIT_END:
    handle_pomp2_taskwait_end();
    break;

#ifdef OPENMP_FOUND
    case EZTRACE_POMP2_SET_LOCK_ENTRY:
    handle_pomp2_set_lock_entry();
    break;
    case EZTRACE_POMP2_SET_LOCK_EXIT:
    handle_pomp2_set_lock_exit();
    break;
    case EZTRACE_POMP2_TEST_LOCK_SUCCESS:
    handle_pomp2_test_lock_success();
    break;
    case EZTRACE_POMP2_UNSET_LOCK:
    handle_pomp2_unset_lock();
    break;

    case EZTRACE_POMP2_SET_NEST_LOCK_ENTRY:
    handle_pomp2_set_nest_lock_entry();
    break;
    case EZTRACE_POMP2_SET_NEST_LOCK_EXIT:
    handle_pomp2_set_nest_lock_exit();
    break;
    case EZTRACE_POMP2_TEST_NEST_LOCK_SUCCESS:
    handle_pomp2_test_nest_lock_success();
    break;
    case EZTRACE_POMP2_UNSET_NEST_LOCK:
    handle_pomp2_unset_nest_lock();
    break;
#endif	/* OPENMP_FOUND */

  default:
    return 0;
  }
  return 1;
}

/* return 1 if the event was handled */
int handle_gomp_stats(eztrace_event_t *ev) {
  recording_stats = 1;
  return handle_gomp_events(ev);
}

void print_gomp_stats() {
  printf("\nOpenMP:\n");
  printf("----------\n");
  printf("%d parallel regions\n", nb_parallel_region);
  /* todo:
   * add:
   *  - min/max/average duration of parallel regions
   *  - maximum depth (nested)
   */

  /* browse the processes finished_section_list and extract statistics */
  int i;
  for (i = 0; i < NB_TRACES; i++) {
    struct process_info_t *p_process = GET_PROCESS_INFO(i);
    struct gomp_process_info_t *p_info =
      (struct gomp_process_info_t*) ezt_hook_list_retrieve_data(
	  &p_process->hooks, (uint8_t) EZTRACE_GOMP_EVENTS_ID);

    if (!p_info)
      continue; /* No gomp process info attached, skip this process */

    /* print the time spent by each thread creating/executing tasks */
    struct eztrace_container_t *process_cont = GET_PROCESS_CONTAINER(i);
    unsigned thread_id;
    for(thread_id = 0; thread_id<process_cont->nb_children; thread_id++) {
      struct eztrace_container_t *p_thread = process_cont->children[thread_id];
      struct thread_info_t* thread_info = (struct thread_info_t*) (p_thread->container_info);
      INIT_GOMP_THREAD_INFO(thread_info, g_info);
      double thread_duration = p_thread->end_timestamp - p_thread->start_timestamp;

      p_info->create_task_duration += g_info->create_task_duration;
      p_info->nb_create_task += g_info->nb_create_task;
      p_info->exec_task_duration += g_info->exec_task_duration;
      p_info->nb_exec_task += g_info->nb_exec_task;

      printf("%s: (%lf ms)\n", p_thread->name, thread_duration);

      printf("\t%d tasks created (%lf ms, %lf ms per task, %lf%% of the thread run time)\t %d tasks executed (%lf ms, %lf ms per task, %lf%% of the thread run time)\n",
	     g_info->nb_create_task, g_info->create_task_duration,
	     (g_info->nb_create_task?(g_info->create_task_duration/g_info->nb_create_task):0),
	     100*g_info->create_task_duration/thread_duration,
	     g_info->nb_exec_task, g_info->exec_task_duration,
	     (g_info->nb_exec_task?(g_info->exec_task_duration/g_info->nb_exec_task):0),
	     100*g_info->exec_task_duration/thread_duration);
    }

    /* print the summary for the process */
    printf("%s: %d tasks created (%lf ms, %lf ms per task)\t %d tasks executed (%lf ms, %lf ms per task)\n",
	   process_cont->name, p_info->nb_create_task, p_info->create_task_duration,
	   (p_info->nb_create_task?(p_info->create_task_duration/p_info->nb_create_task):0),
	   p_info->nb_exec_task, p_info->exec_task_duration,
	   (p_info->nb_exec_task?(p_info->exec_task_duration/p_info->nb_exec_task):0));

    /* todo: implement other statistics (min/max duration, depth, etc.) */
    unsigned nb_sections = 0;
    float total_duration = 0;
    ezt_stack_token_t *token;
    ezt_list_foreach(&p_info->finished_section_list, token)
    {
      struct gomp_section_t * section = (struct gomp_section_t *) token->data;

      total_duration += section->join_done_time - section->start_time;
      nb_sections++;
    }
    printf("%s: \t %d parallel regions (average duration: %f ms)\n",
	   p_process->container->name, nb_sections,
	   total_duration / nb_sections);
  }
}

struct eztrace_convert_module gomp_module;
void libinit(void) __attribute__ ((constructor));
void libinit(void) {
  gomp_module.api_version = EZTRACE_API_VERSION;
  gomp_module.init = eztrace_convert_gomp_init;
  gomp_module.handle = handle_gomp_events;
  gomp_module.handle_stats = handle_gomp_stats;
  gomp_module.print_stats = print_gomp_stats;
  gomp_module.module_prefix = EZTRACE_GOMP_EVENTS_ID;
  int res __attribute__ ((__unused__));
  res = asprintf(&gomp_module.name, "omp");
  res = asprintf(&gomp_module.description,
		 "Module for OpenMP parallel regions");

  gomp_module.token.data = &gomp_module;
  eztrace_convert_register_module(&gomp_module);
}

void libfinalize(void) __attribute__ ((destructor));
void libfinalize(void) {
}
