/* -*- Mode: C; c-file-style: "gnu" -*-
   monitors.c -- pthread code for monitors.
   Created: Chris Toshok <toshok@hungry.com>, 10-Aug-1997
 */

/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "native-threads.h"
#include "ClazzFile.h"
#include "objects.h"
#include "exceptions.h"
#include "log.h"

#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <assert.h>

#if defined(USE_NSPR_THREADS)
#  include "prthread.h"
#  include "prmon.h"
#  define THREAD_EQUAL(a,b) (a == b)
#elif defined(USE_PTHREADS)
/* Hack for Linux RedHat 4.1 (libc5) */
#  define _MIT_POSIX_THREADS 1
#  include <pthread.h>
#  define THREAD_EQUAL(a,b) pthread_equal((pthread_t)a,(pthread_t)b)
#elif defined(USE_CTHREADS)
#  include <cthreads.h>
#  define THREAD_EQUAL(a,b) (a == b)
#elif defined(USE_WIN32_THREADS)
#  include <windows.h>
#  define THREAD_EQUAL(a,b) (a == b)
#else
#  error "Missing monitors. port me!"
#endif

#define MYLOG "Monitors"

struct MonitorStruct {
  int depth;
  HThread owner;
#if defined(USE_NSPR_THREADS)
  PRMonitor *_mon;
#elif defined(USE_PTHREADS)
  pthread_cond_t cv;
  pthread_mutex_t mutex;
#elif defined(USE_CTHREADS)
  condition_t cv;
  mutex_t mutex;
#elif defined(USE_WIN32_THREADS)
#define MANUAL 0
#define AUTO   1
  HANDLE cv[2];
  HANDLE mutex;
  int nWaiters;
#else
#  error "Missing monitors. port me!"
#endif
};

JNIEXPORT HMonitor JNICALL
MONITOR_create(void)
{
  HMonitor monitor = (HMonitor)calloc(1, sizeof(struct MonitorStruct));
  JAVARLOG1(MYLOG, 1, "MONITOR_create() = %x\n", monitor);

  monitor->owner = 0;
  monitor->depth = 0;

#if defined(USE_NSPR_THREADS)
  monitor->_mon = PR_NewMonitor();
#elif defined(USE_PTHREADS)
  pthread_cond_init(&monitor->cv, NULL);
  pthread_mutex_init(&monitor->mutex, NULL);
#elif defined(USE_CTHREADS)
  monitor->cv = condition_alloc();
  monitor->mutex = mutex_alloc();
  condition_init(monitor->cv);
  mutex_init(monitor->mutex);
#elif defined(USE_WIN32_THREADS)
  /* After an idea from "Multithreading programming techniques" */
  monitor->cv[MANUAL] = CreateEvent(NULL, TRUE, FALSE,  NULL);
  monitor->cv[AUTO] = CreateEvent(NULL, FALSE, FALSE,  NULL);
  monitor->mutex = CreateMutex(NULL, FALSE, NULL);
  assert(NULL != monitor->mutex);
  monitor->nWaiters = 0;
#else
#  error "Missing monitors. port me!"
#endif

  return monitor;
}

JNIEXPORT void JNICALL
MONITOR_destroy(HMonitor monitor)
{
#if defined(USE_NSPR_THREADS)

  PR_DestroyMonitor(monitor->_mon);

#elif defined(USE_PTHREADS)
  pthread_mutex_destroy(&monitor->mutex);
  pthread_cond_destroy(&monitor->cv);
#elif defined(USE_CTHREADS)
  condition_free(monitor->cv);
  mutex_free(monitor->mutex);
#elif defined(USE_WIN32_THREADS)
  CloseHandle(monitor->mutex);
  CloseHandle(monitor->cv[MANUAL]);
  CloseHandle(monitor->cv[AUTO]);
#else
#  error "Missing monitors. port me!"
#endif

  free(monitor);
}

JNIEXPORT void JNICALL
MONITOR_enter(HMonitor monitor)
{
  HThread current = THREAD_getCurrent();
  JAVARLOG1(MYLOG, 2, "MONITOR_enter(%x)\n", monitor);

  assert(NULL != monitor);

  /*
   * This is no race condition because the owner is initialized to 0,
   * which is different from current. [pere]
   */
  if ( ! THREAD_EQUAL(monitor->owner, current) )
    {
#if defined(USE_NSPR_THREADS)
      PR_EnterMonitor(monitor->_mon);
#elif defined(USE_PTHREADS)
      int ret = pthread_mutex_lock(&monitor->mutex);
      assert(0 == ret);
#elif defined(USE_CTHREADS)
      int ret = mutex_lock(monitor->mutex);
      assert(0 == ret);
#elif defined(USE_WIN32_THREADS)
      DWORD ret = WaitForSingleObject(monitor->mutex, INFINITE);
      assert(0xffffffff != ret);
#else
#  error "Missing monitors. port me!"
#endif
      monitor->owner = current;
    }
  monitor->depth ++;
}

JNIEXPORT int JNICALL
MONITOR_exit(HMonitor monitor)
{
  HThread current = THREAD_getCurrent();
  JAVARLOG1(MYLOG, 2, "MONITOR_exit(%x)\n", monitor);

  /*
   * XXX This assert is for debugging.  Correct behaviour is to throw
   * XXX IllegalMonitorStateException.  It is the callers responsibility
   * XXX to throw this exception if this method returns false (0)
   */
  assert(THREAD_EQUAL(monitor->owner, current));
  if ( ! THREAD_EQUAL(monitor->owner, current) )
    return 0;

  if (--monitor->depth == 0)
    {
      monitor->owner = 0;

#if defined(USE_NSPR_THREADS)
      PR_ExitMonitor(monitor->_mon);
#elif defined(USE_PTHREADS)
      pthread_mutex_unlock(&monitor->mutex);
      /* I don't think the broadcasts should be here [pere] */
      /* pthread_cond_broadcast(&monitor->cv); */
#elif defined(USE_CTHREADS)
      mutex_unlock(monitor->mutex);
      /* condition_broadcast(monitor->cv); */
#elif defined(USE_WIN32_THREADS)
      {
	BOOL ret = ReleaseMutex(monitor->mutex);
	assert(FALSE != ret);
      }
#else
#  error "Missing monitors. port me!"
#endif
    }
  return 1;
}

JNIEXPORT int JNICALL
MONITOR_wait(HMonitor monitor)
{
  HThread current = THREAD_getCurrent();
  int depth;
  JAVARLOG1(MYLOG, 1, "MONITOR_wait(%x)\n", monitor);

  /*
   * XXX This assert is for debugging.  Correct behaviour is to throw
   * XXX IllegalMonitorStateException.  It is the callers responsibility
   * XXX to throw this exception if this method returns false (0)
   */
  assert(THREAD_EQUAL(monitor->owner, current));
  if ( ! THREAD_EQUAL(monitor->owner, current) )
    return 0;

  /* waiting might release the monitor */
  depth = monitor->depth;
  monitor->owner = 0;
  monitor->depth = 0;
  
#if defined(USE_NSPR_THREADS)
  PR_Wait(monitor->_mon, PR_INTERVAL_NO_TIMEOUT);
#elif defined(USE_PTHREADS)
  pthread_cond_wait(&monitor->cv, &monitor->mutex);
#elif defined(USE_CTHREADS)
  condition_wait(monitor->cv, monitor->mutex);
#elif defined(USE_WIN32_THREADS)
  {
    DWORD ret;
    ++monitor->nWaiters;
    ReleaseMutex(monitor->mutex);
    ret = WaitForMultipleObjects(2, monitor->cv, FALSE, INFINITE);
    WaitForSingleObject(monitor->mutex, INFINITE);
    --monitor->nWaiters;
    if (ret == WAIT_OBJECT_0 + MANUAL) 
      {
	if (!monitor->nWaiters) /* last waiter only */
	  ResetEvent(monitor->cv[MANUAL]); 
      }
  }
#else
#  error "Missing monitors. port me!"
#endif

  /* we now own the monitor again*/
  monitor->owner = current;
  monitor->depth = depth;
  return 1;
}

JNIEXPORT int JNICALL
MONITOR_timedWait(HMonitor monitor, jlong millis)
{
  HThread current = THREAD_getCurrent();
  int depth;

  JAVARLOG2(MYLOG, 1, "MONITOR_timedWait(%x, %ld)\n", monitor, millis);

  /*
   * XXX This assert is for debugging.  Correct behaviour is to throw
   * XXX IllegalMonitorStateException.  It is the callers responsibility
   * XXX to throw this exception if this method returns false (0)
   */
  assert(THREAD_EQUAL(monitor->owner, current));
  if ( ! THREAD_EQUAL(monitor->owner, current) )
    return 0;

  /* XXX Is this correct? 0 equals infinity ? [pere] */
  if (0 == millis)
    return MONITOR_wait(monitor);

  /* waiting might release the monitor */
  depth = monitor->depth;
  monitor->owner = 0;
  monitor->depth = 0;
  
#if defined(USE_NSPR_THREADS)
  PR_Wait(monitor->_mon, (PRUint32)millis);
#elif defined(USE_PTHREADS)
  {
    struct timespec timeout;
    timeout.tv_sec = time(NULL) + (millis / 1000);
    timeout.tv_nsec = (millis % 1000) * 1000;
    pthread_cond_timedwait(&monitor->cv, &monitor->mutex, &timeout);
    /* XXX Hm, how do we handle timeouts ? [pere] */
  }
#elif defined (CTHREADS)
  /* XXX How do you specify timeout in C-threads? [pere] */
  condition_wait(monitor->cv, monitor->mutex);
#elif defined(USE_WIN32_THREADS)
  {
    DWORD ret;
    ++monitor->nWaiters;
    ReleaseMutex(monitor->mutex);
    ret = WaitForMultipleObjects(2, monitor->cv, FALSE, millis);
    /*
     * XXX Hm, how should we handle timeout.  We have lost the mutex,
     * and can not return imediately.
     */
    WaitForSingleObject(monitor->mutex, INFINITE);
    --monitor->nWaiters;
    if (ret == WAIT_OBJECT_0 + MANUAL) 
      {
	if (0 == monitor->nWaiters) /* last waiter only */
	  ResetEvent(monitor->cv[MANUAL]); 
      }
  }
#else
#  error "Missing monitors. port me!"
#endif
  /* we now own the monitor again*/
  monitor->owner = current;
  monitor->depth = depth;
  return 1;
}

JNIEXPORT int JNICALL
MONITOR_notifyOne(HMonitor monitor)
{
  HThread current = THREAD_getCurrent();
  JAVARLOG1(MYLOG, 1, "MONITOR_notifyOne(%x)\n", monitor);

  /*
   * XXX This assert is for debugging.  Correct behaviour is to throw
   * XXX IllegalMonitorStateException.  It is the callers responsibility
   * XXX to throw this exception if this method returns false (0)
   */
  assert(THREAD_EQUAL(monitor->owner, current));
  if ( ! THREAD_EQUAL(monitor->owner, current) )
    return 0;

#if defined(USE_NSPR_THREADS)
  PR_Notify(monitor->_mon);
#elif defined(USE_PTHREADS)
  /* this doesn't sound exactly right... */  
  pthread_cond_signal(&monitor->cv);
#elif defined(USE_CTHREADS)
  cond_signal(monitor->cv);
#elif defined(USE_WIN32_THREADS)
  if (monitor->nWaiters)
    SetEvent(monitor->cv[AUTO]);
#else
#  error "Missing monitors. port me!"
#endif
  return 1;
}

JNIEXPORT int JNICALL
MONITOR_notifyAll(HMonitor monitor)
{
  HThread current = THREAD_getCurrent();
  JAVARLOG1(MYLOG, 1, "MONITOR_notifyAll(%x)\n", monitor);

  /*
   * XXX This assert is for debugging.  Correct behaviour is to throw
   * XXX IllegalMonitorStateException.  It is the callers responsibility
   * XXX to throw this exception if this method returns false (0)
   */
  assert(THREAD_EQUAL(monitor->owner, current));
  if ( ! THREAD_EQUAL(monitor->owner, current) )
    return 0;

#if defined(USE_NSPR_THREADS)
  PR_NotifyAll(monitor->_mon);
#elif defined(USE_PTHREADS)
  pthread_cond_broadcast(&monitor->cv);
#elif defined(USE_CTHREADS)
  cond_broadcast(monitor->cv);
#elif defined(USE_WIN32_THREADS)
  if (monitor->nWaiters)
    SetEvent(monitor->cv[MANUAL]);
#else
#  error "Missing monitors. port me!"
#endif
  return 1;
}

JNIEXPORT HMonitor JNICALL
MONITOR_getForObject(jobject obj)
{
  HMonitor monitor;
  japhar_object *jobj;

  obj_to_object(obj, jobj);

  monitor = jobj->monitor;

  if (!monitor)
    {
      monitor = jobj->monitor = MONITOR_create();
    }

  return monitor;
}
