/* -*- Mode: C; c-file-style: "gnu" -*-
   thread.c -- native methods for java/lang/Thread.
   Created: Chris Toshok <toshok@hungry.com>, 28-Jul-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 "arch.h"
#include "jniint.h"
#include "op_stack.h"

#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <assert.h>

extern PRLogModuleInfo *nativeLm;

/*
 * Tests if this thread is alive. A thread is alive if it has been
 * started and has not yet died.
*/
static jboolean
isAlive(HungryEnv *henv)
{
  if (NULL != henv && STATE_FINISHED != henv->current_state)
    return JNI_TRUE;

  return JNI_FALSE;
}

JNIEXPORT jobject JNICALL
Java_java_lang_Thread_currentThread(JNIEnv *env,
				    jclass cls)
{
  HungryEnv *henv = HVM_ThreadGetEnv();

  return henv->java_thread;
}

JNIEXPORT void JNICALL
Java_java_lang_Thread_yield(JNIEnv *env,
			    jclass cls)
{
  PR_Sleep(PR_INTERVAL_NO_WAIT);
}

JNIEXPORT void JNICALL
Java_java_lang_Thread_sleep(JNIEnv *env,
			    jclass cls,
			    jlong millis)
{
  PR_Sleep(millis);
}

typedef struct {
  jobject thread_obj;
  jobject threadgroup;
  HungryVM *hvm;
} thread_start_data;

static void
real_thread_start(void *foo)
{
  thread_start_data *data = foo;
  HungryVM *hvm = data->hvm;
  JavaVM *vm = hvm->vm;
  JNIEnv *env;
  HungryEnv *henv;
  jmethodID run_method;
  jobject thread_obj;
  japhar_object* thread_jobj;
  jmethodID getName;
  jstring name_str;
  JavaVMAttachArgs args;
  jclass thread_class;

  PR_LOG(nativeLm, PR_LOG_DEBUG, ("in real_thread_start\n"));

  thread_obj = data->thread_obj;
  thread_jobj = (japhar_object*)thread_obj;

  memset(&args, 0, sizeof(args));
  args.version = JNI_VERSION_1_2;
  args.group = data->threadgroup;

  (*vm)->AttachCurrentThread(vm, &env, &args);
  henv = HVM_ThreadGetEnv();

  thread_class = (*env)->GetObjectClass(env, thread_obj);

  henv->java_thread = thread_obj;

  getName = (*env)->GetMethodID(env, thread_class, "getName", "()Ljava/lang/String;");
  name_str = (*env)->CallObjectMethod(env, thread_obj, getName);
  
  if (name_str)
    henv->name = HVM_StringToCString(henv, name_str);
  else
    henv->name = strdup("java-thread");

  if ( NULL == henv->name )
    {
      /* Report failure, clean up and return */
      PR_LOG(nativeLm, PR_LOG_ERROR, ("Failed to set up new threads name\n"));

      HVM_ExceptionThrow(henv, "java/lang/VirtualMachineError",
		      "Cannot allocate thread name");
      return;
    }

  henv->thread_env = env;

  henv->current_state = STATE_RUNNING;

  HVM_ObjectSetNativeState(thread_obj, henv);

  run_method = (*env)->GetMethodID(env, thread_class, "run", "()V");

  PR_LOG(nativeLm, PR_LOG_DEBUG,
	 ("Starting %s.run() thread_obj=%p meth=%p\n",
	  ((ClazzFile*)jclass_to_clazzfile(henv, thread_class))->class_name,
	  thread_obj, run_method));

  (*env)->CallVoidMethod(env, thread_obj, run_method);

  PR_LOG(nativeLm, PR_LOG_DEBUG, ("run done\n"));

  henv->current_state = STATE_FINISHED;

  /* XXX Should we free the allocated thread info? [pere] */
  HVM_ObjectSetNativeState(thread_obj, NULL);
  free(henv->stack_lowwater);
  free(henv->name);

  /* Not sure if this is correct, but it solved a problem with Thread.join(). */
  HVM_MonitorEnter(henv, thread_jobj);
  HVM_MonitorNotifyAll(henv, thread_jobj);
  HVM_MonitorExit(henv, thread_jobj);

  (*vm)->DetachCurrentThread(vm);

  return;
}

JNIEXPORT void JNICALL
Java_java_lang_Thread_start(JNIEnv *env,
			    jobject obj)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  jclass thread_class;
  jfieldID priorityfield;
  jint priority;
  thread_start_data *data;
  jmethodID getThreadGroup;

#if 0
  if ( isAlive(henv) )
    {
      HVM_ExceptionThrow(henv, "java/lang/IllegalThreadStateException",
		      "thread already started");
      return;
    }
#endif

  thread_class = (*env)->FindClass(env, "java/lang/Thread");
  priorityfield = (*env)->GetFieldID(env, thread_class, "priority", "I");
  priority = (*env)->GetIntField(env, obj, priorityfield);

  data = calloc(1, sizeof(thread_start_data));
  data->thread_obj = obj;
  data->hvm = henv->vm;

  getThreadGroup = (*env)->GetMethodID(env,
				       (*env)->FindClass(env, "java/lang/Thread"),
				       "getThreadGroup",
				       "()Ljava/lang/ThreadGroup;");
  data->threadgroup = (*env)->CallObjectMethod(env,
					       henv->java_thread,
					       getThreadGroup);


  PR_CreateThread(PR_USER_THREAD,
		  real_thread_start, data,
		  priority /* XXX */,
		  PR_LOCAL_THREAD,
		  PR_JOINABLE_THREAD,
		  0);
}

JNIEXPORT jboolean JNICALL
Java_java_lang_Thread_isInterrupted(JNIEnv *env,
				    jobject obj,
				    jboolean ClearInterrupted)
{
  HungryEnv *henv = HVM_ObjectGetNativeState(obj);

  assert(NULL != henv);

  if (NULL != henv && STATE_INTERRUPTED == henv->current_state)
    {
      return JNI_TRUE;
    }

  return JNI_FALSE;
}

JNIEXPORT jboolean JNICALL
Java_java_lang_Thread_isAlive(JNIEnv *env,
			      jobject obj)
{ 
  HungryEnv *henv = HVM_ObjectGetNativeState(obj);

 return isAlive(henv);
}

JNIEXPORT jint JNICALL
Java_java_lang_Thread_countStackFrames(JNIEnv *env,
				       jobject obj)
{
  (*env)->FatalError(env, "java.lang.Thread.countStackFrames() unimplemented");
  return 0;
}

JNIEXPORT void JNICALL
Java_java_lang_Thread_setPriority0(JNIEnv *env,
				   jobject obj,
				   jint newPriority)
{
  jclass thread_class = (*env)->FindClass(env, "java/lang/Thread");
  jfieldID priority = (*env)->GetFieldID(env, thread_class, "priority", "I");
  HungryEnv *henv = HVM_ObjectGetNativeState(obj); 

  (*env)->SetIntField(env, obj, priority, newPriority);

  /* Only change the priority of already running thread */
  if (NULL != henv)
    PR_SetThreadPriority(henv->thread_id, newPriority);
}

/* For Classpath, not JDK */
JNIEXPORT void JNICALL
Java_java_lang_Thread_nativeSetPriority(JNIEnv *env,
			      	        jobject obj,
				        jint newPriority)
{
  Java_java_lang_Thread_setPriority0(env, obj, newPriority);
}

JNIEXPORT void JNICALL
Java_java_lang_Thread_stop0(JNIEnv *env,
			    jobject thread,
			    jobject throwable)
{
  /* XXX this are deprecate anyway.  don't implement them. */
}

/* For Classpath, not JDK */
JNIEXPORT void JNICALL
Java_java_lang_Thread_nativeStop(JNIEnv *env,
			         jobject thread,
			         jobject throwable)
{
  Java_java_lang_Thread_stop0(env, thread, throwable);
}

JNIEXPORT void JNICALL
Java_java_lang_Thread_suspend0(JNIEnv *env,
			       jobject obj)
{
#if 0
  HungryEnv *henv = HVM_ObjectGetNativeState(obj);
  THREAD_suspend(henv->thread_id);
#endif
}

/* For Classpath, not JDK */
JNIEXPORT void JNICALL
Java_java_lang_Thread_nativeSuspend(JNIEnv *env,
				    jobject obj)
{
  Java_java_lang_Thread_suspend0(env, obj);
}

JNIEXPORT void JNICALL
Java_java_lang_Thread_resume0(JNIEnv *env,
			      jobject obj)
{
#if 0
  HungryEnv *henv = HVM_ObjectGetNativeState(obj);
  THREAD_resume(henv->thread_id);
#endif
}

/* For Classpath, not JDK */
JNIEXPORT void JNICALL
Java_java_lang_Thread_nativeResume(JNIEnv *env,
			           jobject obj)
{
  Java_java_lang_Thread_resume0(env, obj);
}

JNIEXPORT void JNICALL
Java_java_lang_Thread_interrupt0(JNIEnv *env,
				 jobject obj)
{
  HungryEnv *henv = HVM_ObjectGetNativeState(obj);

  if (NULL != henv)
    {
      /* XXX Should also interrupt the thread */
      PR_LOG(nativeLm, PR_LOG_ERROR, ("Thread.interrupt0() is unfinished!"));
      henv->current_state = STATE_INTERRUPTED;
    }
}

/* For Classpath, not JDK */
JNIEXPORT void JNICALL
Java_java_lang_Thread_nativeInterrupt(JNIEnv *env,
				      jobject obj)
{
  Java_java_lang_Thread_interrupt0(env, obj); 
}

/* For Classpath, not JDK */
JNIEXPORT void JNICALL
Java_java_lang_Thread_nativeDestroy(JNIEnv *env,
				    jobject obj)
{
  /* XXX do something here */
  (*env)->FatalError(env, "java.lang.Thread.nativeDestroy() unimplemented");
}

/* For Classpath, not JDK */
JNIEXPORT void JNICALL
Java_java_lang_Thread_nativeInit(JNIEnv *env,
				 jobject obj)
{
  /* XXX Do nothing for now */
}

/* JDK 1.2 uses registration of native methods... */
static JNINativeMethod thr_native_methods[] = {
  { "countStackFrames",		"()I",	(void*)Java_java_lang_Thread_countStackFrames },
  { "interrupt0",		"()V",	(void*)Java_java_lang_Thread_interrupt0 },
  { "isAlive",			"()Z",	(void*)Java_java_lang_Thread_isAlive },
  { "isInterrupted",		"(Z)Z",	(void*)Java_java_lang_Thread_isInterrupted },
  { "resume0",			"()V",	(void*)Java_java_lang_Thread_resume0 },
  { "setPriority0",		"(I)V",	(void*)Java_java_lang_Thread_setPriority0 },
  { "sleep",			"(J)V",	(void*)Java_java_lang_Thread_sleep },
  { "start",			"()V",	(void*)Java_java_lang_Thread_start },
  { "stop0",			"(Ljava/lang/Object;)V", (void*)Java_java_lang_Thread_stop0 },
  { "suspend0",			"()V",	(void*)Java_java_lang_Thread_suspend0 },
  { "yield",			"()V",	(void*)Java_java_lang_Thread_yield }
};
static int num_thr_native_methods = sizeof(thr_native_methods) / sizeof(thr_native_methods[0]);

JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env,
				      jclass cls)
{
#ifdef HAVE_LIBFFI
  (*env)->RegisterNatives(env, cls,
			  thr_native_methods,
			  num_thr_native_methods);
#endif
}
