/* StarPU --- Runtime system for heterogeneous multicore architectures.
 *
 * Copyright (C) 2010-2012 University of Bordeaux
 * Copyright (C) 2012, 2014 CNRS
 * Copyright (C) 2012 Vincent Danjean <Vincent.Danjean@ens-lyon.org>
 *
 * StarPU is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * StarPU is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU Lesser General Public License in COPYING.LGPL for more details.
 */

#include "gc.h"
#include "event.h"
#include "socl.h"

#include <stdlib.h>

/**
 * Garbage collection thread
 */

/* List of entities to be released */
static volatile entity gc_list = NULL;
static volatile entity entities = NULL;

/* Mutex and cond for release */
static starpu_pthread_mutex_t gc_mutex = STARPU_PTHREAD_MUTEX_INITIALIZER;
static starpu_pthread_cond_t  gc_cond = STARPU_PTHREAD_COND_INITIALIZER;

/* Set to 1 to stop release thread execution */
static volatile int gc_stop_required = 0;

#define GC_LOCK starpu_pthread_mutex_lock(&gc_mutex)
#define GC_UNLOCK { starpu_pthread_cond_signal(&gc_cond); \
                    starpu_pthread_mutex_unlock(&gc_mutex);}
#define GC_UNLOCK_NO_SIGNAL starpu_pthread_mutex_unlock(&gc_mutex)

/* Thread routine */
static void * gc_thread_routine(void *UNUSED(arg)) {
  
  GC_LOCK;

  do {
    /* Make a copy of the gc_list to allow callbacks to add things into it */
    entity rs = gc_list;
    gc_list = NULL;

    GC_UNLOCK_NO_SIGNAL;

    entity r = rs;
    while (r != NULL) {
      /* Call entity release callback */
      if (r->release_callback != NULL) {
        r->release_callback(r);
      }

      /* Release entity */
      entity next = r->next;
      free(r);

      r = next;
    }

    GC_LOCK;

    /* Check if new entities have been added */
    if (gc_list != NULL)
      continue;

    /* Stop if required */
    if (gc_stop_required) {
      GC_UNLOCK_NO_SIGNAL;
      break;
    }

    /* Otherwise we sleep */
    starpu_pthread_cond_wait(&gc_cond, &gc_mutex);

  } while (1);

  starpu_pthread_exit(NULL);
}

static starpu_pthread_t gc_thread;

/* Start garbage collection */
void gc_start(void) {
  starpu_pthread_create(&gc_thread, NULL, gc_thread_routine, NULL);
}

/* Stop garbage collection */
void gc_stop(void) {
  GC_LOCK;

  gc_stop_required = 1;

  GC_UNLOCK;

  starpu_pthread_join(gc_thread, NULL);
}

int gc_entity_release_ex(entity e, const char * DEBUG_PARAM(caller)) {

  DEBUG_MSG("[%s] Decrementing refcount of %s %p to ", caller, e->name, e);

  /* Decrement reference count */
  int refs = __sync_sub_and_fetch(&e->refs, 1);

  DEBUG_MSG_NOHEAD("%d\n", refs);

  assert(refs >= 0);

  if (refs != 0)
    return 0;

  DEBUG_MSG("[%s] Releasing %s %p\n", caller, e->name, e);

  GC_LOCK;

  /* Remove entity from the entities list */
  if (e->prev != NULL)
    e->prev->next = e->next;
  if (e->next != NULL)
    e->next->prev = e->prev;
  if (entities == e)
    entities = e->next;

  /* Put entity in the release queue */
  e->next = gc_list;
  gc_list = e;

  GC_UNLOCK;

  return 1;
}



/**
 * Initialize entity
 */
void gc_entity_init(void *arg, void (*release_callback)(void*), char * name) {

  DEBUG_MSG("Initializing entity %p (%s)\n", arg, name);

  struct entity * e = (entity)arg;

  e->dispatch = &socl_master_dispatch;
  e->refs = 1;
  e->release_callback = release_callback;
  e->prev = NULL;
  e->name = name;

  GC_LOCK;

  e->next = entities;
  if (entities != NULL)
    entities->prev = e;
  entities = e;

  GC_UNLOCK_NO_SIGNAL;
}

/**
 * Allocate and initialize entity
 */
void * gc_entity_alloc(unsigned int size, void (*release_callback)(void*), char * name) {
  void * e = malloc(size);
  gc_entity_init(e, release_callback, name);
  return e;
}

/** Retain entity */
void gc_entity_retain_ex(void *arg, const char * DEBUG_PARAM(caller)) {
	struct entity * e = (entity)arg;

   #ifdef DEBUG
	int refs =
   #endif
      __sync_add_and_fetch(&e->refs, 1);


   DEBUG_MSG("[%s] Incrementing refcount of %s %p to %d\n", caller, e->name, e, refs);
}

int gc_active_entity_count(void) {
	int i = 0;

	entity e = entities;
	while (e != NULL) {
		i++;
		e = e->next;
	}

	return i;
}

void gc_print_remaining_entities(void) {
   DEBUG_MSG("Remaining entities:\n");

   GC_LOCK;

   entity e = entities;
   while (e != NULL) {
      DEBUG_MSG("  - %s %p\n", e->name, e);
      e = e->next;
   }

   GC_UNLOCK;
}

#undef GC_LOCK
#undef GC_UNLOCK
#undef GC_UNLOCK_NO_SIGNAL
