/* -*- Mode: C; c-file-style: "gnu" -*-
   japhar.c -- Java interpreter.
   Created: Chris Toshok <toshok@hungry.com>, 26-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) 1998 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"

#ifdef USE_DLL_LTDL
#include <ltdl.h>
#endif

static JavaVMOption options[1024]; /* hope noone ever needs more than 1024 command line options :) */
static int num_options = 0;

static char *clazzname = NULL;

static jclass string_cls;
static jint param_start;
static jvalue cmd_params[1];
JavaVM *jvm = NULL;

static
void
usage (char *name)
{
  printf ("Usage: %s [OPTION]... CLASS [ARG]...\n", name);
  printf ("Execute Java bytecode in CLASS, starting with the"
          " method\n\"static public void main(String[])\"."
          "  Each ARG is passed\ninto \"main\" via the String array.\n\n"
	  "  --classpath=PATH                   use PATH to lookup classes\n"
	  "  --verbose:{gc,jni,class,method}    output verbose information about \n"
          "                                     garbage collection, JNI calls, and \n"
          "                                     class loading/unloading, and method \n"
          "                                     calls/returns respectively.\n"
          "  --help                             display this help and exit\n"
          "  --version                          output version information and exit\n");
}

static void
exit_hook(int status)
{
  (*jvm)->DestroyJavaVM(jvm);
  exit (status);
}

static void
parse_command_line (int argc, char **argv)
{
  int i;
  char *classpath = NULL;

  options[num_options++].optionString = "-Djava.library.path=/usr/local/japhar/lib" /* XXX */;
  options[num_options++].optionString = "-Dsun.boot.library.path=/usr/local/japhar/lib" /* XXX */;
  options[num_options++].optionString = "-Djava.awt.graphicsenv=sun/awt/X11GraphicsEnvironment" /* XXX */;

  for (i = 1; i < argc; i ++)
    {
      char *arg;
      jboolean is_switch = JNI_FALSE;
      int two_dashes = 0;

      arg = argv[i];

      /* handle both -arg and --arg */
      if (arg[0] == '-') { arg++; is_switch = JNI_TRUE; }
      if (arg[0] == '-') { arg++; two_dashes = 1; }

      if (is_switch)
	{
	  if (!PL_strcmp(arg, "version"))
	    {
	      printf ("GNU Japhar " VERSION " <URL:http://www.japhar.org/>\n"
		      "Copyright (C) 1998 The Hungry Programmers\n"
		      "\n"
		      "Send mail requests to japhar@japhar.org\n"
		      "\n"
		      "Japhar comes with ABSOLUTELY NO WARRANTY.\n"
		      "You may redistribute copies of Japhar under the terms of the GNU\n"
		      "Library General Public License.  For more information about these matters, see\n"
		      "the files named COPYING.\n");
	      exit (0);
	    }
	  else if (!PL_strcmp(arg, "help"))
	    {
	      usage(argv[0]);
	      exit(0);
	    }
	  else if (!PL_strncmp(arg, "classpath=", 10 /*strlen(classpath=)*/)) 
	    {
	      classpath = arg + 10 /* strlen(classpath=) */;
	    }
	  else if (!PL_strncmp(arg, "classpath", 9 /* strlen(classpath) */))
	    {
	      if (i == argc - 1)
		;
	      else
		classpath = argv[++i];
	    }
	  else if (!PL_strncmp(arg, "verbose:", 8 /* strlen(verbose:) */))
	    {
	      options[num_options++].optionString = argv[i] + two_dashes;
	    }
	  else if (arg[0] == 'D')
	    {
	      options[num_options++].optionString = argv[i] + two_dashes;
	    }
	  else if (arg[0] == 'X')
	    {
#if 0
	      if (!PL_strcmp(arg[0], "Xjdk-1.1"))
		jni_version = JNI_VERSION_1_1;
	      else if (!PL_strcmp(arg[0], "Xjdk-1.2"))
		jni_version = JNI_VERSION_1_2;
#endif
	      
	      options[num_options++].optionString = argv[i] + two_dashes;
	    }
	  else
	    {
	      fprintf (stderr, "unrecognized option: %s\n", argv[i]);
	      usage(argv[0]);
	      exit(0);
	    }
	}
      else
	{
	  clazzname = argv[i];
	  param_start = i + 1;
	  break;
	}
    }

  {
    char *cp_value;
    char *new_cp_option;

    if (classpath)
      cp_value = classpath;
    else
      {
	HVMClasspath *user_classpath = HVM_ClasspathGetUserClasspath();
	HVMClasspath *sys_classpath = HVM_ClasspathGetSystemClasspath();
	user_classpath = HVM_ClasspathConcat(user_classpath, sys_classpath);
	cp_value = HVM_ClasspathToString(user_classpath);
	HVM_ClasspathDestroy(user_classpath);
      }
    
	new_cp_option = PR_smprintf("-Djava.class.path=%s%s.",
                                cp_value, PATH_SEPARATOR);

    options[num_options++].optionString = new_cp_option;
  } 

  if (!clazzname)
    { 
      usage (argv[0]);
      exit (0);
    }

  options[num_options].optionString = "exit";
  options[num_options].extraInfo = exit_hook;
  num_options++;
}

static void
create_java_command_line(int argc, char **argv,
                         JNIEnv *env, JavaVM *jvm)
{
  jobjectArray params;
  int argi = param_start;

  string_cls = (*env)->FindClass(env, "java/lang/String");
  if ((*env)->ExceptionOccurred(env))
    return;

  params = (*env)->NewObjectArray(env, argc - argi, string_cls, NULL);

  while (argi < argc)
    {
      jstring str = (*env)->NewStringUTF(env, argv[ argi ]);

      (*env)->SetObjectArrayElement(env, params, argi - param_start,
                                    str);
      argi++;
    }
  
  cmd_params[0].l = params;
}

static void
handleException(JNIEnv *env,
                JavaVM *jvm)
{
  (*env)->ExceptionDescribe(env);
 
  (*env)->ExceptionClear(env);

  (*jvm)->DestroyJavaVM(jvm);

  exit(0);
}

int
main(int argc,
     char **argv)
{
  JNIEnv *env = NULL;
  JavaVMInitArgs vm_args;

  jclass cls;
  jmethodID mid;

#ifdef USE_DLL_LTDL
  LTDL_SET_PRELOADED_SYMBOLS();
#endif

  vm_args.version = JNI_VERSION_1_2;

  /* parse the command line arguments, extracting the clazzname 
     (and parameters) */
  parse_command_line(argc, argv);

  vm_args.options = options;
  vm_args.nOptions = num_options;
  vm_args.ignoreUnrecognized = JNI_FALSE;

  /*
  ** load and initialize a JavaVM, return a JNI interface pointer in
  ** env.
  */
  if (JNI_CreateJavaVM(&jvm, &env, &vm_args) == -1)
    {
      fprintf(stderr, "Could not create the Java virtual machine\n");
      exit(0);
    }

  create_java_command_line(argc, argv, env, jvm);
  if ((*env)->ExceptionOccurred(env))
    handleException(env, jvm);

  /* invoke the main method of the class specified on the command line */
  cls = (*env)->FindClass(env, clazzname);
  if ((*env)->ExceptionOccurred(env))
    handleException(env, jvm);

  mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
  if ((*env)->ExceptionOccurred(env))
    handleException(env, jvm);

  (*env)->CallStaticVoidMethodA(env, cls, mid, cmd_params);
  if ((*env)->ExceptionOccurred(env))
    handleException(env, jvm);

  /* we now enter into this screwed loop, waiting until all the other
     user threads (if any) die. */
  while ((*jvm)->DestroyJavaVM(jvm) == -1)
    {
      PR_Sleep(PR_INTERVAL_NO_WAIT);
    }

  /* keep 'gcc -Wall -pedantic' happy */
  return 0;
}
