/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) CNRS, INRIA, Université Bordeaux 1, Télécom SudParis
 * See COPYING in top-level directory.
 */

#include "eztrace_convert.h"
#include <inttypes.h>

static int recording_stats = 0;

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

/* structure attached to each process */
struct pthread_process_info_t {
  struct process_info_t *p_process; /* pointer to the corresponding process */
  struct ezt_list_t lock_list; /* list of struct pthread_lock_info_t */
};

enum lock_type_t {
  MUTEX=0,
  SPINLOCK=1,
  RWLOCK=2,
  SEMAPHORE=3,
  BARRIER=4,
  CONDITION=5,
  NB_LOCK_TYPES=6
};

/* structure that contains information on a lock(mutex, spinloc, etc.)
 */
struct pthread_lock_info_t {
  struct ezt_list_token_t token; /* token used for inserting this structure in the lock_list */
  enum lock_type_t lock; /* type of lock */
  app_ptr ptr; /* address of the lock in the application */
  char* info; /* name of the lock */
  /* statistics: */
  uint32_t nb_acquire; /* number of lock_stop */
  int last_owner_tid; /* tid of the last thread that acquired the lock */
  float time_acquire; /* last time the lock was acquired */
  float time_release; /* last time the lock was released */

  double duration_critical_section; /* time spent in critical section */
  double duration_acquire; /* time spent in waiting for a lock */

  /* todo: improve statistics:
   * - compute min/max/average critical section duration
   * - record the time spent waiting for the lock
   */
};

struct pthread_thread_info_t {
  float start_acquire[NB_LOCK_TYPES]; /* date of the last start_lock event */
  float duration_acquire[NB_LOCK_TYPES]; /* time spent in waiting for a lock */
  float duration_critical_section[NB_LOCK_TYPES]; /* time spent in a critical section */
  struct thread_info_t *p_thread;
};

static void __lock_type_to_str(char *str, enum lock_type_t l) {
  switch (l) {
  case MUTEX:
    strcpy(str, "mutex");
    break;
  case SPINLOCK:
    strcpy(str, "spinlock");
    break;
  case RWLOCK:
    strcpy(str, "rwlock");
    break;
  case SEMAPHORE:
    strcpy(str, "semaphore");
    break;
  case BARRIER:
    strcpy(str, "barrier");
    break;
  case CONDITION:
    strcpy(str, "condition");
    break;
  default:
    strcpy(str, "<<unknown>>");
  };
}

/* print a message that warns about a dangerous behavior. Thread 1 locked, but thread2 unlocked.
 * return 1 if this behavior is detected or 0 otherwise
 */
static int __check_lock_mismatch(struct pthread_lock_info_t *p_info,
                                 struct pthread_thread_info_t *p_thread) {
  int cur_tid = p_thread->p_thread->tid;

  if (p_info->last_owner_tid == cur_tid)
    return 0;

  char lock_type_str [80];
  __lock_type_to_str(lock_type_str, p_info->lock);

  printf("Warning: t=%f - thread %d locked ", CURRENT, p_info->last_owner_tid);
  printf("%s %p, but thread %d unlocks it!\n", lock_type_str, (void*)p_info->ptr, cur_tid);
  return 1;
}

/* add a hook in the process structure in order to store information
 * about locks used by the process
 */
static struct pthread_process_info_t *__register_process_hook(
    struct process_info_t *p_process) {
  struct pthread_process_info_t *p_info =
    (struct pthread_process_info_t*) malloc(
        sizeof(struct pthread_process_info_t));
  p_info->p_process = p_process;
  ezt_list_new(&p_info->lock_list);

  /* add the hook in the process info structure */
  ezt_hook_list_add(&p_info->p_process->hooks, p_info,
                    (uint8_t) EZTRACE_PTHREAD_EVENTS_ID);
  return p_info;
}

/* declare a pthread_process_info_t that corresponds to p_process
 */
#define  INIT_PTHREAD_PROCESS_INFO(p_process, var)			\
  struct pthread_process_info_t *var = (struct pthread_process_info_t*)	\
    ezt_hook_list_retrieve_data(&p_process->hooks, (uint8_t)EZTRACE_PTHREAD_EVENTS_ID); \
  if(!(var)) {								\
    var = __register_process_hook(p_process);				\
  }

/* get the pthread_lock_info_t* lock_info that corresponds to lock_ptr
 * This initialize lock_info->info
 */
#define GET_LOCK_INFO(lock_info, p_process, lock_ptr, lock_type)	\
  do {									\
    INIT_PTHREAD_PROCESS_INFO(p_process, p_info);			\
    lock_info = __find_lock_info(&p_info->lock_list, lock_ptr);		\
    if(!lock_info) {							\
      lock_info = __new_lock_info(p_info, lock_ptr, lock_type);		\
    }									\
  }while(0)

/* add the current duration of critical section (time_release-time_acquire) to the
 * sum of critical sections
 */
#define update_duration_critical_section(p_lock, p_info)		\
  do {									\
    p_lock->duration_critical_section += p_lock->time_release - p_lock->time_acquire; \
    p_info->duration_critical_section[p_lock->lock] += p_lock->time_release - p_lock->time_acquire; \
  } while(0)

#define update_duration_acquire(p_lock, p_info)				\
  do{									\
    p_lock->duration_acquire += p_lock->time_acquire - p_info->start_acquire[p_lock->lock]; \
    p_info->duration_acquire[p_lock->lock] += p_lock->time_acquire - p_info->start_acquire[p_lock->lock]; \
  } while(0)

/* record informations on a lock start acquire event (for statistics) */
#define record_lock_start_acquire(p_lock, p_info)	\
  do {							\
    p_info->start_acquire[p_lock->lock] = CURRENT;	\
  } while(0)

/* record informations on a lock acquire event (for statistics) */
#define record_lock_acquired(p_lock, p_info)		\
  do {							\
    p_lock->time_acquire = CURRENT;			\
    update_duration_acquire(p_lock, p_info);		\
    p_lock->nb_acquire++;				\
    p_lock->last_owner_tid = p_info->p_thread->tid;	\
  } while(0)

/* record informations on a lock release event (for statistics) */
#define record_lock_release(p_lock, p_info)		\
  do {							\
    p_lock->time_release = CURRENT;			\
    update_duration_critical_section(p_lock, p_info);	\
    __check_lock_mismatch(p_lock, p_info);		\
  }while(0)

/* Free the lock_info_list attached to a process
 * Since this also frees the locks info strings, this should not be called before
 * the trace is written to disk.
 */
static void __free_lock_info_list(struct pthread_process_info_t* p_info) {

  while (!ezt_list_empty(&p_info->lock_list)) {
    struct ezt_list_token_t *t = ezt_list_get_head(&(p_info->lock_list));
    struct pthread_lock_info_t *p = (struct pthread_lock_info_t *) t->data;

    ezt_list_remove(t);

    if (p) {
      free(p->info);
      free(p);
    }
  }
}

/* create a new lock_info structure */
static struct pthread_lock_info_t*
__new_lock_info(struct pthread_process_info_t* p_info, app_ptr ptr,
                enum lock_type_t lock_type) {
  struct pthread_lock_info_t* res = malloc(sizeof(struct pthread_lock_info_t));
  int ret __attribute__ ((__unused__));

  /* initialize the structure */
  res->lock = lock_type;
  res->ptr = ptr;

  switch (lock_type) {
  case MUTEX:
    ret = asprintf(&res->info, "Mutex %p.", (void*)ptr);
    break;

  case SPINLOCK:
    ret = asprintf(&res->info, "Spinlock %p.", (void*)ptr);
    break;

  case RWLOCK:
    ret = asprintf(&res->info, "RWLock %p.", (void*)ptr);
    break;

  case SEMAPHORE:
    ret = asprintf(&res->info, "Semaphore %p.", (void*)ptr);
    break;

  case BARRIER:
    ret = asprintf(&res->info, "Barrier %p.", (void*)ptr);
    break;

  default:
    fprintf(stderr, "unknown lock type: %d\n", lock_type);
  }

  ret = asprintf(&res->info, "%s_ptr_%p", p_info->p_process->container->id,
                 (void*) ptr);

  /* initialize statistics */
  res->nb_acquire = 0;
  res->last_owner_tid = -1;
  res->duration_critical_section = 0;
  res->duration_acquire = 0;

  res->token.data = res;

  /* add the lock to the list of locks */
  ezt_list_add(&p_info->lock_list, &res->token);

  return res;
}

/* find the lock_info that corresponds to ptr.
 * return NULL if not found
 */
static struct pthread_lock_info_t*__find_lock_info(struct ezt_list_t *lock_list,
                                                   app_ptr ptr) {
  struct ezt_list_token_t *t = NULL;
  struct pthread_lock_info_t *p;
  ezt_list_foreach(lock_list, t)
  {
    p = (struct pthread_lock_info_t *) t->data;
    if (p->ptr == ptr)
      return p;
  }
  return NULL;
}
