/*
  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 "jniint.h"
#include "native-threads.h"

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <signal.h>
#include <assert.h>

#if defined(USE_NSPR_THREADS)
# include <prthread.h>
#elif defined(USE_PTHREADS)
/* Hack for Linux RedHat 4.1 (libc5) */
# define _MIT_POSIX_THREADS 1
# include <pthread.h>
# ifdef HAVE_PTHREAD_NP_H
#  include <pthread_np.h>
# endif
#elif defined(USE_CTHREADS)
#  include <cthreads.h>
#elif defined(USE_WIN32_THREADS)
#  include <process.h>
#  include <windows.h>
#else
#  error "Missing threads. port me!"
#endif

#if defined(USE_NSPR_THREADS)
typedef PRThread *japhar_thread_t;
typedef PRUintn japhar_key_t;
#elif defined(USE_PTHREADS)
typedef pthread_t japhar_thread_t;
typedef pthread_key_t japhar_key_t;
#elif defined(USE_CTHREADS)
typedef cthread_t japhar_thread_t;
typedef cthread_key_t japhar_key_t;
#elif defined(USE_WIN32_THREADS)
/* This is the Thread ID, not the handle */
typedef DWORD japhar_thread_t;
typedef DWORD japhar_key_t;
#else
#  error "Missing threads. port me!"
#endif

static japhar_key_t JNIVM_KEY = (japhar_key_t)-1;
static japhar_key_t JNIENV_KEY = (japhar_key_t)-1;
static japhar_key_t JTHREADINFO_KEY = (japhar_key_t)-1;

static inline void thread_onetime_init(void)
{
  static int one_time = 0;

  if (one_time) return;

  one_time = 1;

#if defined(USE_CTHREADS)
  cthread_init();
#endif
}

struct start_arg_cons {
  void *(*start_function)(void*);
  void *arg;
  JavaVM *vm;
  char wait_for_thread;
  HMonitor mon;
  jobject threadgroup;
};

static void *
#ifdef USE_WIN32_THREADS
/* What is this stuff? [pere 1998-05-26] */
WINAPI
#endif
thread_start(void *start_arg)
{
  struct start_arg_cons *cons = (struct start_arg_cons*)start_arg;
  void *(*function)(void*) = cons->start_function;
  void *arg = cons->arg;
  JavaVM *vm = cons->vm;
  JNIEnv *env;
  void* result;
  JavaVMAttachArgs args;

  args.version = JNI_VERSION_1_2;
  args.group = cons->threadgroup;

  if (vm)
    (*vm)->AttachCurrentThread(vm, &env, &args);

  assert(NULL != function);
  
  MONITOR_enter(cons->mon);
  cons->wait_for_thread = 1;
  MONITOR_notifyAll(cons->mon);
  MONITOR_exit(cons->mon);

  result = (*function)(arg);

  if (vm)
    (*vm)->DetachCurrentThread(vm);

  /* free up the thread specific stack. XXX */

  return result;
}

JNIEXPORT HThread JNICALL
THREAD_start(void *(*start_function)(void*),
	     void *start_arg,
	     int priority,
	     int initialStackSize)
{
  JNIEnv *env = THREAD_getEnv();
  jmethodID getThreadGroup;
  int num_times_waited;

  japhar_thread_t new_thread;
  struct start_arg_cons *cons =
    (struct start_arg_cons*)malloc(sizeof(struct start_arg_cons));

  thread_onetime_init();
  
  cons->start_function = start_function;
  cons->arg = start_arg;
  cons->vm = THREAD_getVM();
  cons->wait_for_thread = 0;
  cons->mon = MONITOR_create();
  
  getThreadGroup = (*env)->GetMethodID(env,
				       (*env)->FindClass(env, "java/lang/Thread"),
				       "getThreadGroup",
				       "()Ljava/lang/ThreadGroup;");
  cons->threadgroup = (*env)->CallObjectMethod(env,
					       ((HungryJNIEnv*)env)->_java_info->java_thread,
					       getThreadGroup);

#if defined(USE_NSPR_THREADS)
  new_thread = PR_CreateThread(PR_USER_THREAD,
			       (void (*)(void *)) thread_start,
			       cons,
			       PR_PRIORITY_NORMAL,
			       PR_LOCAL_THREAD,
			       PR_JOINABLE_THREAD,
			       0);
#elif defined(USE_PTHREADS)
  if (pthread_create(&new_thread, NULL, thread_start, cons))
    {
      fprintf (stderr, "pthread_create failed.\n");
      exit(1);
    }
#elif defined(USE_CTHREADS)
  new_thread = cthread_fork(thread_start, cons);
  if (new_thread == NULL)
    {
      fprintf (stderr, "cthread_fork failed.\n");
      exit(1);
    }
#elif defined(USE_WIN32_THREADS)
  {
    HANDLE hThread;
    hThread = (HANDLE)_beginthreadex(NULL, 0, (unsigned int (__stdcall *)(void *))thread_start, (void *)cons,
				     0, (unsigned int*)&new_thread);
    if (NULL == hThread)
      {
	fprintf(stderr, "CreateThread failed.\n");
	exit(1);
      }
    CloseHandle(hThread);
  }
#else
#  error "Missing threads. port me!"
#endif
  THREAD_setPriority((HThread)new_thread, priority);

  num_times_waited = 0;
  /* spin, waiting for the new thread to wake up.  after it attaches
     itself. */
  while(JNI_TRUE) 
    {
      char foo;
      MONITOR_enter(cons->mon);
      MONITOR_timedWait(cons->mon, 1000);
      foo = cons->wait_for_thread;
      MONITOR_exit(cons->mon);
      if (foo)
	break;
      num_times_waited++;
      if (num_times_waited > 5)
	/* something's wrong if we're waiting 5 seconds for a new thread to start... */
	abort();
    }

  MONITOR_destroy(cons->mon);
  free(cons);

  return (HThread)new_thread;
}

JNIEXPORT void JNICALL
THREAD_setName(HThread thread, char *name)
{
#if defined( USE_PTHREADS )
#  if defined( HAVE_PTHREAD_SET_NAME_NP )
  pthread_set_name_np((pthread_t)thread, name);
#  endif
#endif
}

JNIEXPORT void JNICALL
THREAD_exit(void)
{
  thread_onetime_init();

#if defined(USE_NSPR_THREADS)
  /* nothing here. */
#elif defined(USE_PTHREADS)
  pthread_exit(NULL);
#elif defined(USE_CTHREADS)
  cthread_exit(NULL);
#elif defined(USE_WIN32_THREADS)
  ExitThread(0);
#else
#  error "Missing threads. port me!"
#endif
}

/* Not sure what this method is supposed to do... [pere] */
JNIEXPORT int JNICALL
THREAD_delete(HThread thread)
{
  thread_onetime_init();

#if defined(USE_NSPR_THREADS)
  /* nothing needed here. */
  return 0;
#elif defined(USE_PTHREADS)
  return pthread_detach((pthread_t)thread);
#elif defined(USE_CTHREADS)
  cthread_detach((cthread_t)thread);
  return 0;
#elif defined(USE_WIN32_THREADS)
  /* nothing needed here ? [pere] */
  return 0;
#else
#  error "Missing threads. port me!"
#endif
}

JNIEXPORT void JNICALL
THREAD_suspend(HThread thread)
{
#ifdef USE_PTHREADS
#  if HAVE_PTHREAD_SUSPEND_NP
  pthread_suspend_np( (pthread_t)thread );
#  endif
#endif
}

JNIEXPORT void JNICALL
THREAD_resume(HThread thread)
{
#ifdef USE_PTHREADS
#  if HAVE_PTHREAD_RESUME_NP
  pthread_suspend_np( (pthread_t)thread );
#  endif
#endif
}

JNIEXPORT void JNICALL
THREAD_yield(void)
{
  thread_onetime_init();

#if defined(USE_NSPR_THREADS)
  PR_Sleep(PR_INTERVAL_NO_WAIT);
#elif defined(USE_PTHREADS)
  /* PThreads changed while they walked! */
#  if defined(HAVE_PTHREAD_YIELD)
  pthread_yield();
#  elif defined(HAVE_SCHED_YIELD)
  sched_yield();
#  elif defined(HAVE_THR_YIELD)
  /*
   * This is part of UNIX International thread API, but Solaris seems
   * to need it when using pthreads. [pere]
   */
  thr_yield();
#  elif defined(HAVE_YIELD)
  yield();
#  else
#    error yield() missing! - port me!
#endif
#elif defined(USE_CTHREADS)
  cthread_yield();
#elif defined(USE_WIN32_THREADS)
  Sleep(0);
#else
#  error "Missing threads. port me!"
#endif
}

/* Halt the thread for the given milliseconds */
JNIEXPORT void JNICALL
THREAD_sleep(jlong millis)
{
  struct timeval timeout;

  timeout.tv_sec = (long)(millis / 1000);
  timeout.tv_usec = (long)(millis % 1000);

  select(0, NULL, NULL, NULL, &timeout);
}

/*
 * change the given threads priority to the value given.
 */
JNIEXPORT void JNICALL
THREAD_setPriority(HThread thread, int priority)
{
#if defined(USE_NSPR_THREADS)
  /* XXX */
#elif defined(USE_PTHREADS)
#  if defined(HAVE_PTHREAD_GETSCHEDPARAM) && defined(HAVE_PTHREAD_SETSCHEDPARAM)
  {
    int policy;
    struct sched_param param;
    pthread_getschedparam((japhar_thread_t)thread, &policy, &param);
    /* XXX Not sure if the priority should be scaled in any way */
    param.sched_priority = priority;
    pthread_setschedparam((japhar_thread_t)thread, policy, &param);
  }
#  elif defined(HAVE_PTHREAD_SETPRIO)
  /* XXX This part is untested [pere] */
  pthread_setprio((japhar_thread_t)thread, priority);
#  else
#    error "Missing threads. port me!"
#  endif
#elif defined(USE_CTHREADS)
  fprintf(stderr, "Unimplemented THREAD_setPriority(, %d) stub\n", priority);
#elif defined(USE_WIN32_THREADS)
  if (! SetThreadPriority(thread, priority))
    {
      fprintf(stderr, "Setting thread priority %d failed\n", priority);
    }
#else
#  error "Missing threads. port me!"
#endif
}

JNIEXPORT HThread JNICALL
THREAD_getCurrent(void)
{
  thread_onetime_init();

#if defined(USE_NSPR_THREADS)
  return (HThread)PR_GetCurrentThread();
#elif defined(USE_PTHREADS)
  return (HThread)pthread_self();
#elif defined(USE_CTHREADS)
  return (HThread)cthread_self();
#elif defined(USE_WIN32_THREADS)
  return (HThread)GetCurrentThreadId();
#else
#  error "Missing threads. port me!"
#endif
}

static void
thread_createKey(japhar_key_t *key)
{
  thread_onetime_init();

  if (
#if defined(USE_NSPR_THREADS)
      (PR_NewThreadPrivateIndex(key, NULL) != PR_SUCCESS)
#elif defined(USE_PTHREADS)
      (pthread_key_create(key, NULL) != 0)
#elif defined(USE_CTHREADS)
      (cthread_keycreate(key) != 0)
#elif defined(USE_WIN32_THREADS)
      (TLS_OUT_OF_INDEXES == (*key = TlsAlloc() ) )
#else
#  error "Missing threads. port me!"
#endif
      )
    abort();
}

static void
thread_setKeyValue(japhar_key_t *key, const void* value)
{
  if (*key == -1)
    thread_createKey(key);

  if (
#if defined(USE_NSPR_THREADS)
      (PR_SetThreadPrivate(*key, (void*)value) != PR_SUCCESS)
#elif defined(USE_PTHREADS)
      (pthread_setspecific(*key, value) != 0)
#elif defined(USE_CTHREADS)
      (cthread_setspecific(*key, (any_t)value) != 0)
#elif defined(USE_WIN32_THREADS)
      (TlsSetValue(*key, (LPVOID)value) == 0)
#else
#  error "Missing threads. port me!"
#endif
      )
    {
#ifdef USE_WIN32_THREADS
      fprintf (stderr, "Error setting Tls.  error = %d\n", GetLastError());
#endif
      abort();
    }
}

static void*
thread_getKeyValue(japhar_key_t *key)
{
  void* value = NULL;

  if (*key == -1)
    thread_createKey(key);

#if defined(USE_NSPR_THREADS)
  value = (void*)PR_GetThreadPrivate(*key);
#elif defined(USE_PTHREADS)
#  ifdef OLD_FREEBSD_PTHREADS
  pthread_getspecific(*key, (void*)&value);
#  else
  value = pthread_getspecific(*key);
#  endif
#elif defined(USE_CTHREADS)
  cthread_getspecific(*key, (any_t*)&value);
#elif defined(USE_WIN32_THREADS)
  value = TlsGetValue(*key);
#else
#  error "Missing threads. port me!"
#endif /* USE_CTHREADS */

  return value;
}

JNIEXPORT void JNICALL
THREAD_setEnv(JNIEnv *env)
{
  thread_setKeyValue(&JNIENV_KEY, env);
}

JNIEXPORT void JNICALL
THREAD_setVM(JavaVM *vm)
{
  thread_setKeyValue(&JNIVM_KEY, vm);
}

JNIEXPORT JavaVM * JNICALL
THREAD_getVM(void)
{
  return (JavaVM*)thread_getKeyValue(&JNIVM_KEY);
}

JNIEXPORT JNIEnv * JNICALL
THREAD_getEnv(void)
{
  return (JNIEnv*)thread_getKeyValue(&JNIENV_KEY);
}

/*
 * This is really JThreadInfo*, but I don't want this module to
 * include interp.h. [pere]
 */

JNIEXPORT void JNICALL
THREAD_setJavaInfo(void *info)
{
  JNIEnv *env;
  HungryJNIEnv *henv;

  thread_setKeyValue(&JTHREADINFO_KEY, (void*)info);

  /* XXX Why is this done here, and not by the callers [pere] */
  env = THREAD_getEnv();
  henv = (HungryJNIEnv*)env;
  henv->_java_info = info;
}

JNIEXPORT void* JNICALL
THREAD_getJavaInfo(void)
{
  return thread_getKeyValue(&JTHREADINFO_KEY);
}
