/* -*- 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 "jni.h"
#include "interp.h"
#include "native-threads.h"
#include "objects.h"
#include "exceptions.h"
#include "log.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>

#include "op_stack.h"

#define MYLOG "Native"

/*
 * Tests if this thread is alive. A thread is alive if it has been
 * started and has not yet died.
*/
static jboolean
isAlive(JNIEnv *env, jobject obj)
{
  JThreadInfo *info = NSA_GetNativeState(obj);

  if (NULL != info && STATE_FINISHED != STATE(info))
    return JNI_TRUE;

  return JNI_FALSE;
}

JNIEXPORT jobject JNICALL
Java_java_lang_Thread_currentThread(JNIEnv *env,
				    jclass cls)
{
  JThreadInfo *info = THREAD_getJavaInfo();

  return info->java_thread;
}

JNIEXPORT void JNICALL
Java_java_lang_Thread_yield(JNIEnv *env,
			    jclass cls)
{
  /* XXX more here? */
  THREAD_yield();
}

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

static char *
jstring2charptr(JNIEnv *env, jstring jstr)
{
  char *str_copy;
  const jbyte *str_chars;
  int str_length;
  str_chars = (*env)->GetStringUTFChars(env, jstr, NULL);
  str_length = (*env)->GetStringUTFLength(env, jstr) + 1;
  str_copy = (char*) malloc(str_length * sizeof(char));
  strncpy(str_copy, (char*)str_chars, str_length);
  (*env)->ReleaseStringUTFChars(env, jstr, str_chars);
  str_copy[str_length-1] = 0; /* Make sure the string is zero terminated */
  return str_copy;
}

static void *
real_thread_start(void *foo)
{
  jmethodID run_method;
  jobject obj = (jobject)foo;
  JNIEnv *env = THREAD_getEnv();
  HMonitor monitor = MONITOR_getForObject(obj);
  JThreadInfo *native_thread_info;
  char *java_name;
  jmethodID getName;
  jstring name_str;

  jclass thread_class = (*env)->GetObjectClass(env, obj);

  JAVARLOG0(MYLOG, 1, "in real_thread_start\n");

  native_thread_info = (JThreadInfo*)calloc(1, sizeof(JThreadInfo));
  if (!native_thread_info)
    {
      throw_Exception(env, "java/lang/VirtualMachineError",
		      "Cannot allocate thread info");
      return NULL;
    }
  native_thread_info->java_thread = obj;

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

  THREAD_setName(THREAD_getCurrent(), native_thread_info->name);

  if ( NULL == native_thread_info->name )
    {
      /* Report failure, clean up and return */
      JAVARLOG0(MYLOG, 1, "Failed to set up new threads name\n");
      free(native_thread_info);

      throw_Exception(env, "java/lang/VirtualMachineError",
		      "Cannot allocate thread name");
      return NULL;
    }

  native_thread_info->op_stack = op_stack_allocate(OPSTACK_SIZE);
  if ( NULL == native_thread_info->op_stack )
    {
      /* Report failure, clean up and return */
      JAVARLOG0(MYLOG, 1, "Failed to set up new threads name\n");
      free(native_thread_info->name);
      free(native_thread_info);

      throw_Exception(env, "java/lang/VirtualMachineError",
		      "Cannot allocate thread op_stack");
      return NULL;
    }

  /* setup_stackframes is from libruntime/interloop.c */
  if (JNI_FALSE == setup_stackframes(native_thread_info) )
    {
      /* Report failure, clean up and return */
      JAVARLOG0(MYLOG, 1, "Failed to set up new threads StackFrames\n");
      op_stack_deallocate(native_thread_info->op_stack);
      free(native_thread_info->name);
      free(native_thread_info);

      throw_Exception(env, "java/lang/VirtualMachineError",
		      "Cannot allocate thread stackframes");
      return NULL;
    }

  native_thread_info->thread_env = env;

  STATE(native_thread_info) = STATE_RUNNING;

  THREAD_setJavaInfo(native_thread_info);

  NSA_SetNativeState(obj, native_thread_info);

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

  JAVARLOG3(MYLOG, 1, "Starting %s.run() obj=%p meth=%p\n",
	    ((ClazzFile*)jclass_to_clazzfile(env, thread_class))->class_name,
	    obj, run_method);

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

  JAVARLOG0(MYLOG, 1, "run done\n");

  STATE(native_thread_info) = STATE_FINISHED;

  /* XXX Should we free the allocated thread info? [pere] */
  NSA_SetNativeState(obj, NULL);
  op_stack_deallocate(native_thread_info->op_stack);
  free(native_thread_info->stack_lowwater);
  free(native_thread_info->name);
  free(native_thread_info);

  /* Not sure if this is correct, but it solved a problem with Thread.join(). */
  MONITOR_enter(monitor);
  MONITOR_notifyAll(monitor);
  MONITOR_exit(monitor);

  return NULL;
}

JNIEXPORT void JNICALL
Java_java_lang_Thread_start(JNIEnv *env,
			    jobject obj)
{
  jclass thread_class;
  jfieldID priorityfield;
  jint priority;

  if ( isAlive(env, obj) )
    {
      throw_Exception(env, "java/lang/IllegalThreadStateException",
		      "thread already started");
      return;
    }

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

  THREAD_start(real_thread_start,
	       obj,
	       priority,
	       16000 /* XXX initialStackSize */);
}

JNIEXPORT jboolean JNICALL
Java_java_lang_Thread_isInterrupted(JNIEnv *env,
				    jobject obj,
				    jboolean ClearInterrupted)
{
  JThreadInfo *info = NSA_GetNativeState(obj);

  assert(NULL != info);

  if (NULL != info && STATE_INTERRUPTED == STATE(info))
    {
      return JNI_TRUE;
    }

  return JNI_FALSE;
}

JNIEXPORT jboolean JNICALL
Java_java_lang_Thread_isAlive(JNIEnv *env,
			      jobject obj)
{
  return isAlive(env, obj);
}

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");
  JThreadInfo *info = NSA_GetNativeState(obj);

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

  /* Only change the priority of already running thread */
  if (NULL != info)
    THREAD_setPriority(info->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)
{
  /* hmm... this is a hack.  the throwable object is what we're
     supposed to throw in the thread parameter's environment. */

  JThreadInfo* info = NSA_GetNativeState(thread);
  (*info->thread_env)->Throw(info->thread_env, throwable);
}

/* 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)
{
  JThreadInfo *info = NSA_GetNativeState(obj);
  THREAD_suspend(info->thread_id);
}

/* 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)
{
  JThreadInfo *info = NSA_GetNativeState(obj);
  THREAD_resume(info->thread_id);
}

/* 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)
{
  JThreadInfo *info = NSA_GetNativeState(obj);

  if (NULL != info)
    {
      /* XXX Should also interrupt the thread */
      JAVARLOG0(MYLOG, 1, "Thread.interrupt0() is unfinished!");
      STATE(info)=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
}
