/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode:nil -*-
   init.c -- Startup functions
   Created: Petter Reinholdtsen <pere@td.org.uit.no>, 1998-12-04
 */
/*
  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 "runtimeint.h"
#include "arch.h"
#include "qop.h"
#include "op_stack.h"

PRLogModuleInfo *arrayClassLm;
PRLogModuleInfo *arrayLm;
PRLogModuleInfo *exceptionLm;
PRLogModuleInfo *dllLm;
PRLogModuleInfo *gcLm;
PRLogModuleInfo *interpLm;
PRLogModuleInfo *inflateLm;
PRLogModuleInfo *jniLm;
PRLogModuleInfo *jvmdiLm;
PRLogModuleInfo *methodLm;
PRLogModuleInfo *nativeGlueLm;
PRLogModuleInfo *objectLm;
PRLogModuleInfo *parseClassLm;
PRLogModuleInfo *primitiveClassLm;
PRLogModuleInfo *sigLm;

/* native methods specific to the sun classes.zip */
PRLogModuleInfo *nativeLm;
PRLogModuleInfo *awtLm;

static void
initialize_logging(void)
{
  static int inited = 0;

  if (!inited)
    {
      inited = 1;

      arrayClassLm     = PR_NewLogModule ("ArrayClass");
      arrayLm          = PR_NewLogModule ("Array");
      awtLm            = PR_NewLogModule ("AWT");
      exceptionLm      = PR_NewLogModule ("Exception");
      dllLm            = PR_NewLogModule ("DLL");
      gcLm             = PR_NewLogModule ("GC");
      interpLm         = PR_NewLogModule ("Interp");
      inflateLm        = PR_NewLogModule ("Inflate");
      jniLm            = PR_NewLogModule ("JNI");
      jvmdiLm          = PR_NewLogModule ("JVMDI");
      methodLm         = PR_NewLogModule ("Method");
      nativeGlueLm     = PR_NewLogModule ("NativeGlue");
      objectLm         = PR_NewLogModule ("Object");
      parseClassLm     = PR_NewLogModule ("ParseClass");
      primitiveClassLm = PR_NewLogModule ("PrimitiveClass");
      sigLm            = PR_NewLogModule ("Sig");
      nativeLm         = PR_NewLogModule ("Native");
    }
}


/*
 * Return PR_TRUE if everything went OK, and PR_FALSE otherwise.
 */
static PRBool
initialize_system_libraries(void)
{
#if !defined( _WINDOWS ) || defined( USE_DLL_LTDL )
  HVMDllLibHandle native_handle;

  native_handle = HVM_DllFind("lang");
  if (!native_handle)
    return PR_FALSE;

  native_handle = HVM_DllFind("io");
  if (!native_handle)
    return PR_FALSE;

  native_handle = HVM_DllFind("util");
  if (!native_handle)
    return PR_FALSE;

  native_handle = HVM_DllFind("reflect");
  if (!native_handle)
    return PR_FALSE;

  native_handle = HVM_DllFind("security");
  if (!native_handle)
    return PR_FALSE;
#endif

  return PR_TRUE;
}

/* This is also used by java.lang.Thread */
#define STACK_SIZE 1000
static PRBool
setup_stackframes(HungryEnv* henv)
{
  StackFrame *frames = (StackFrame*)PR_MALLOC(sizeof(StackFrame) * STACK_SIZE);

  if (NULL == frames)
    return PR_FALSE;

  henv->stack_lowwater = frames;
  henv->stack_highwater = frames;
  henv->stack_highwater += STACK_SIZE;
  henv->top_frame = henv->stack_highwater;

  return PR_TRUE;
}

static OpStack *
op_stack_allocate(int size)
{
  OpStack *new_stack = (OpStack*)PR_MALLOC(sizeof(OpStack));
  if ( NULL == new_stack )
    {
      return NULL;
    }

  new_stack->stack_top = (void**)PR_MALLOC(sizeof(void*) * size);
  if ( NULL == new_stack->stack_top )
    {
      PR_DELETE(new_stack);
      return NULL;
    }
  new_stack->stack_bottom = new_stack->stack_top;
  new_stack->high_water = new_stack->stack_bottom + size;

  return new_stack;
}

static void
op_stack_deallocate(OpStack *stack)
{
  PR_DELETE(stack->stack_top);
  PR_DELETE(stack);
}

PR_IMPLEMENT(void)
HVM_ThreadInit(HungryVM *hvm, HungryEnv *henv,
               PRBool is_primordial,
               char *name,
               japhar_object* threadgroup,
               int priority /* if -1, assign the NORM_PRIORITY */)
{
  japhar_object* thread;
  FieldStruct* field;
  MethodStruct* method;
  japhar_object* thread_name;
  StackFrame *initial_native_frame;
  InterpValue priority_val;

  static japhar_object* System = NULL;
  static japhar_object* Thread = NULL;
  static japhar_object* ThreadGroup = NULL;
  static ClazzFile* System_cf;
  static ClazzFile* Thread_cf;
  static ClazzFile* ThreadGroup_cf;

  PR_ASSERT(NULL != henv);

  henv->vm = hvm;

  if (!System)
    {
      System_cf = HVM_ClassFind(henv, java_lang_System);
      if (System_cf)
        System = clazzfile_to_jclass(henv, System_cf);
    }
  if (!Thread)
    {
      Thread_cf = HVM_ClassFind(henv, java_lang_Thread);
      if (Thread_cf)
        Thread = clazzfile_to_jclass(henv, Thread_cf);
    }
  if (!ThreadGroup)
    {
      ThreadGroup_cf = HVM_ClassFind(henv, java_lang_ThreadGroup);
      if (ThreadGroup_cf)
        ThreadGroup = clazzfile_to_jclass(henv, ThreadGroup_cf);
    }

  if ((!Thread) || (!ThreadGroup))
    {
      fprintf(stderr,
              "Unable to initialize threads: cannot find class java/lang/%s\n",
              ((!Thread) ? "Thread" : "ThreadGroup"));
      exit(1);
    }
  /* just to make sure. */
  if (!System)
    {
      fprintf(stderr,
              "Unable to initialize vm:  cannot find class java/lang/System\n");
      exit(1);
    }

  if (NULL == henv)
    {
      HVM_ExceptionThrow(henv, java_lang_VirtualMachineError,
                      "Cannot allocate thread-local info");
      return;
    }
  henv->name = PL_strdup(name);

  henv->op_stack = op_stack_allocate(OPSTACK_SIZE);
  if (NULL == henv->op_stack)
    {
      PR_DELETE(henv->name);
      PR_DELETE(henv);
      HVM_ExceptionThrow(henv, java_lang_VirtualMachineError,
                      "Cannot allocate thread-local op_stack");
      return;
    }

  if ( PR_FALSE == setup_stackframes(henv) )
    {
      PR_DELETE(henv->name);
      PR_DELETE(henv);
      op_stack_deallocate(henv->op_stack);
      HVM_ExceptionThrow(henv, java_lang_VirtualMachineError,
                      "Cannot allocate thread-local stack");
      return;
    }

  henv->thread_id = PR_GetCurrentThread();
  HVM_ThreadSetEnv(henv);

  thread = HVM_ObjectNew(henv, Thread_cf);

  /* have to put this off until here. */
  henv->java_thread = thread;

  henv->current_state = STATE_RUNNING;

  if (priority == -1)
    {
      field = HVM_FieldFindStatic(henv, Thread_cf, "NORM_PRIORITY", "I");
      HVM_FieldGetStatic(Thread_cf, field, &priority_val);
    }

  field = HVM_FieldFind(henv, Thread_cf, "priority", "I");
  HVM_FieldSet(thread, field, priority_val);

  /* don't use Thread.PrivateInfo, even if we're using JDK1.1 */
  HVM_ObjectSetNativeState(thread, henv);

  initial_native_frame = push_frame(henv, 0);

  initial_native_frame->opstack_top = initial_native_frame->_env->op_stack->stack_top;
  initial_native_frame->this_pointer = NULL;

  initial_native_frame->flags |= FRAME_NATIVE;

  thread_name = HVM_StringFromCString(henv, name);

  if (is_primordial)
    {
      threadgroup = HVM_ObjectNew(henv, ThreadGroup_cf);
      method = HVM_MethodFind(henv, ThreadGroup_cf, "<init>", "()V");
      initial_native_frame->method = method;
      HVM_MethodCallA(henv, method, threadgroup, NULL);

      if (henv->_exception)
        {
          printf ("ThreadGroup.<init> failed.\n");
          return;
        }
    }

  if (threadgroup)
    method = HVM_MethodFind(henv, Thread_cf, "<init>",
                           "(Ljava/lang/ThreadGroup;Ljava/lang/String;)V");
  else
    method = HVM_MethodFind(henv, Thread_cf, "<init>",
                           "(Ljava/lang/String;)V");

  if (henv->_exception)
    {
      printf ("Thread.<init> lookup failed.\n");
      return;
    }

  initial_native_frame->method = method;
  if (threadgroup)
    HVM_MethodCall(henv, method, thread, threadgroup, thread_name);
  else
    HVM_MethodCall(henv, method, thread, thread_name);

  if (henv->_exception)
    {
      printf ("Thread.<init> failed.\n");
      return;
    }

  if (is_primordial)
    {
      /* we need to initialize the system class first (if it's necessary) */
      method = HVM_MethodFindStatic(henv, System_cf,
                                    "initializeSystemClass",
                                    "()V");
      if (method)
        {
          initial_native_frame->method = method;
          HVM_MethodCallStatic(henv, method, NULL);

          if (henv->_exception)
            {
              pop_frame(henv);
              return;
            }
        }

    }

  pop_frame(henv);
}

/* we have to do this one by hand, since all the class initialization
   stuff relies on java.lang.Class already being initialized. */
static PRBool
initialize_class_class(HungryEnv *henv)
{
  ClazzFile *class_cf = HVM_ClassFind(henv, java_lang_Class);

  if (NULL == class_cf)
    return PR_FALSE;

  class_cf->nesting_level = 1;
  calculate_instance_field_offsets(henv, class_cf);

  return PR_TRUE;
}

PR_IMPLEMENT(PRBool)
HVM_VMAllocate(HungryVM **p_new_vm, HungryEnv **p_new_env)
{
  HungryVM *new_vm;
  HungryEnv *new_env;

  /* initializing logging straight away */
  initialize_logging();

  /* allocate our new VM */
  new_vm = (HungryVM*)PR_CALLOC(sizeof(HungryVM));
  if (new_vm == NULL)
    {
      PR_LOG(interpLm, PR_LOG_DEBUG, ("Unable to alloc HungryVM"));
      return PR_FALSE;
    }

  /* and create the monitor used to synchronize dealings with it */
  new_vm->_mon = PR_NewMonitor();
  if (NULL == new_vm->_mon)
    {
      PR_LOG(interpLm, PR_LOG_DEBUG, ("Unable to create monitor"));
      return PR_FALSE;
    }

  /* allocate our per thread environment */
  new_env = PR_CALLOC(sizeof(HungryEnv));
  if (NULL == new_env)
    {
      PR_LOG(interpLm, PR_LOG_DEBUG, ("Unable to alloc HungryEnv"));
      return PR_FALSE;
    }

  new_env->vm = new_vm;

  /* create the mapping from henv to native thread, and vice versa */
  new_env->thread_id = PR_GetCurrentThread();
  HVM_ThreadSetEnv(new_env);

  /* make sure that the env is actually there. */
  PR_ASSERT(HVM_ThreadGetEnv() != NULL);
  if (NULL == HVM_ThreadGetEnv())
    return PR_FALSE;

  *p_new_vm = new_vm;
  *p_new_env = new_env;

  return PR_TRUE;
}

PRBool
HVM_VMInitialize(HungryVM *new_vm, HungryEnv *new_env)
{
  /* initialize our classpath */
  new_vm->_cp = HVM_ClasspathNew();
  HVM_ClasspathAddPath(new_vm->_cp, new_vm->_classpath);

  /* initialize DLL before using it */
  if ( PR_FALSE == HVM_DllInit() )
    {
      PR_LOG(interpLm, PR_LOG_DEBUG, ("Unable to initialize DLL subsystem"));
      return PR_FALSE;
    }

  /* make sure our native symbols are loading before we go trying to
     execute java code. */
  if ( PR_FALSE == initialize_system_libraries() )
    {
      PR_LOG(interpLm, PR_LOG_DEBUG, ("Unable to load system libraries"));
      return PR_FALSE;
    }

  /* create our class repository (hash table of ClazzFile*'s and class
     descriptors. */
  initialize_system_classloader(new_vm);

  init_collector(new_vm);

  /* initialize some java.lang.Class specific stuff. */
  if (PR_FALSE == initialize_class_class(new_env))
    {
      PR_LOG(interpLm, PR_LOG_DEBUG, ("Failed to initialize java.lang.Class"));
      return PR_FALSE;
    }

  /* add the env to the vm's list of envs, and make it the special,
     "initial" env */
  new_vm->_initial_env = new_env;
  ENQUEUE(new_env, new_vm->_envs);

  /* get the thread machinery going. */
  HVM_ThreadInit(new_vm, new_env,
                 PR_TRUE,
                 "main",
                 NULL, -1);

  if (new_env->_exception)
    {
      HVM_ExceptionPrintBacktrace(new_env, new_env->_exception);
      return PR_FALSE;
    }

  return PR_TRUE;
}

PRBool
HVM_VMStartup(HungryVM **p_new_vm, HungryEnv **p_new_env)
{
  HungryVM *new_vm;
  HungryEnv *new_env;

  if (PR_FALSE == HVM_VMAllocate(&new_vm, &new_env))
    return PR_FALSE;

  if (PR_FALSE == HVM_VMInitialize(new_vm, new_env))
    return PR_FALSE;

  *p_new_vm = new_vm;
  *p_new_env = new_env;

  return PR_TRUE;
}

void
HVM_VMShutdown(HungryVM *hvm)
{
  /* Since we're deleting this vm, collect its garbage. */
  shutdown_collector(hvm, hvm->_finalizers_on_exit);

  HVM_DllExit();
}
