/**************************************************************************\
 *
 *  This file is part of the Coin 3D visualization library.
 *  Copyright (C) 1998-2006 by Systems in Motion.  All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  ("GPL") version 2 as published by the Free Software Foundation.
 *  See the file LICENSE.GPL at the root directory of this source
 *  distribution for additional information about the GNU GPL.
 *
 *  For using Coin with software that can not be combined with the GNU
 *  GPL, and for taking advantage of the additional benefits of our
 *  support services, please contact Systems in Motion about acquiring
 *  a Coin Professional Edition License.
 *
 *  See http://www.coin3d.org/ for more information.
 *
 *  Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY.
 *  http://www.sim.no/  sales@sim.no  coin-support@coin3d.org
 *
\**************************************************************************/

#include <Inventor/C/threads/sched.h>
#include <assert.h>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */

#ifndef HAVE_THREADS

/* FIXME: instead of disallowing the use of these completely when
   thread support is not available (that's why there are asserts
   within them), could we perhaps implement in such a manner that they
   still work, but within only the calling thread?

   20051202 mortene. */

cc_sched * cc_sched_construct(int numthreads) { assert(FALSE); return NULL; }
void cc_sched_destruct(cc_sched * sched) { assert(FALSE); }
void cc_sched_set_num_threads(cc_sched * sched, int num) { assert(FALSE); }
int cc_sched_get_num_threads(cc_sched * sched) { assert(FALSE); return 0; }
void cc_sched_schedule(cc_sched * sched, 
                       void (*workfunc)(void *), void * closure,
                       unsigned int priority) { assert(FALSE); }
void cc_sched_wait_all(cc_sched * sched) { assert(FALSE); }

#else /* HAVE_THREADS */

#include <stdlib.h>

#include <Inventor/C/errors/debugerror.h>
#include <Inventor/C/threads/condvar.h>
#include <Inventor/C/threads/mutex.h>
#include <Inventor/C/threads/schedp.h>
#include <Inventor/C/threads/thread.h>
#include <Inventor/C/threads/wpool.h>

/* ********************************************************************** */

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

/* private methods */

static void sched_worker_entry_point(void * userdata);

typedef struct {
  void (*workfunc)(void *);
  void * closure;
  unsigned int priority;
} sched_item;

static int
sched_item_compare(void * o1, void * o2)
{
  sched_item * i1, * i2;
  
  i1 = (sched_item*) o1;
  i2 = (sched_item*) o2;
  
  return ((int) i1->priority) - ((int) i2->priority);
}

/* assumes mutex is locked */
static void
sched_try_trigger(cc_sched * sched)
{
  if (cc_wpool_try_begin(sched->pool, 1)) {
    cc_wpool_start_worker(sched->pool, sched_worker_entry_point, sched);
    cc_wpool_end(sched->pool);
  }
}

void
sched_worker_entry_point(void * userdata)
{
  sched_item * item;
  cc_sched * sched = (cc_sched*) userdata;

  cc_mutex_lock(sched->mutex);
  while (!cc_heap_empty(sched->itemheap)) {
    item = (sched_item*) cc_heap_extract_top(sched->itemheap);

    cc_mutex_unlock(sched->mutex);
    item->workfunc(item->closure);
    cc_mutex_lock(sched->mutex);
    cc_memalloc_deallocate(sched->itemalloc, (void*) item);
  }
  /* in case the main thread is in sched_wait_all() */
  cc_condvar_wake_one(sched->cond);
  cc_mutex_unlock(sched->mutex);
}

/* ********************************************************************** */
/* public api */


/*!
  Construct a scheduler that uses \a numthreads threads.
*/
cc_sched *
cc_sched_construct(int numthreads)
{
  cc_sched * sched = (cc_sched*) malloc(sizeof(cc_sched));
  assert(sched);
  sched->pool = cc_wpool_construct(numthreads);
  sched->mutex = cc_mutex_construct();
  sched->cond = cc_condvar_construct();
 
  sched->itemheap = cc_heap_construct(64, sched_item_compare, FALSE);
  sched->itemalloc = cc_memalloc_construct(sizeof(sched_item));

  return sched;
}

/*!
  Destruct the scheduler.
*/
void
cc_sched_destruct(cc_sched * sched)
{
  cc_sched_wait_all(sched);

  cc_heap_destruct(sched->itemheap);
  cc_memalloc_destruct(sched->itemalloc);

  cc_condvar_destruct(sched->cond);
  cc_mutex_destruct(sched->mutex);
  cc_wpool_destruct(sched->pool);
  free(sched);
}

/*!
  Set/change the number of threads used by the scheduler.
*/
void
cc_sched_set_num_threads(cc_sched * sched, int num)
{
  cc_sched_wait_all(sched);
  cc_wpool_set_num_workers(sched->pool, num);
}

/*!
  Returns the number of threads used by the scheduler.
*/
int
cc_sched_get_num_threads(cc_sched * sched)
{
  return cc_wpool_get_num_workers(sched->pool);
}

/*! 
  Schedule a new job. A thread calls \a workfunc with the \a closure
  argument when a thread becomes available. \a priority is currently
  ignored, but in the future this will be supported.
*/
void
cc_sched_schedule(cc_sched * sched,
                  void (*workfunc)(void *), void * closure,
                  unsigned int priority)
{
  sched_item * item;

  cc_mutex_lock(sched->mutex);
  item = (sched_item*) cc_memalloc_allocate(sched->itemalloc);
  
  item->workfunc = workfunc;
  item->closure = closure;
  item->priority = priority;

  cc_heap_add(sched->itemheap, (void*) item);
  sched_try_trigger(sched);

  cc_mutex_unlock(sched->mutex);
}

/*!
  Wait for all jobs to finish.
*/
void
cc_sched_wait_all(cc_sched * sched)
{
  cc_mutex_lock(sched->mutex);
  while (!cc_heap_empty(sched->itemheap)) {
    cc_condvar_wait(sched->cond, sched->mutex);
  }
  cc_mutex_unlock(sched->mutex);
  cc_wpool_wait_all(sched->pool);
}

/* ********************************************************************** */

#ifdef __cplusplus
} /* extern "C" */
#endif /* __cplusplus */

#endif /* HAVE_THREADS */
