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

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

#if 0
#ifdef WITH_MOTIF_AWT
/* we need the following to force particular pieces of motif to be
   linked in if it's an archive (.a).  We don't need this if it's a
   shared library, but since at least one person doesn't have a motif
   shared library (me), we can't depend on it.  Besides, this won't
   break in the case of shared libraries... */
#include <Xm/Xm.h>
extern WidgetClass xmDrawingAreaWidgetClass;
extern WidgetClass xmCascadeButtonWidgetClass;
extern WidgetClass xmMainWindowWidgetClass;
extern WidgetClass xmFormWidgetClass;
static WidgetClass *foo = &xmDrawingAreaWidgetClass;
static WidgetClass *bar = &xmCascadeButtonWidgetClass;
static WidgetClass *baz = &xmMainWindowWidgetClass;
static WidgetClass *baz2 = &xmFormWidgetClass;
void *foof00 = XtAppInitialize;
#endif
#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];

#ifdef _WIN32
#define PATH_SEPARATOR_STR ";"
#else
#define PATH_SEPARATOR_STR ":"
#endif

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
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 (!strcmp(arg, "version"))
	{
          printf ("GNU Japhar %s <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",
		  VERSION);
          exit (0);
	}
      else if (!strcmp(arg, "help"))
	{
	  usage(argv[0]);
	  exit(0);
	}
      else if (!strncmp(arg, "classpath=", 10 /*strlen(classpath=)*/)) 
	{
	  classpath = arg + 10 /* strlen(classpath=) */;
	}
      else if (!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 (!strcmp(arg[0], "Xjdk-1.1"))
	    jni_version = JNI_VERSION_1_1;
	  else if (!strcmp(arg[0], "Xjdk-1.2"))
	    jni_version = JNI_VERSION_1_2;
#endif

	  options[num_options++].optionString = argv[i] + two_dashes;
	}
      else
	{
	  if (is_switch)
	    {
	      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
      {
	char *classpath_env = getenv("CLASSPATH");
	char *sys_classpath = CLASSPATH_getSystemClasspath();

	if (classpath_env)
	  {
	    cp_value = (char*)malloc(strlen(classpath_env)
				     + strlen(sys_classpath)
				     + 1 /* : */
				     + 1 /* \0 */);
	    strcpy(cp_value, classpath_env);
	    strcat(cp_value, PATH_SEPARATOR_STR);
	    strcat(cp_value, sys_classpath);
	  }
	else
	  {
	    cp_value = strdup(sys_classpath);
	  }

#if notyet
	free(sys_classpath);
#endif
      }
    
    new_cp_option = (char*)malloc(strlen("-Djava.class.path=") 
				  + strlen(cp_value) + 2 /* for ":." */ + 1);
    strcpy(new_cp_option, "-Djava.class.path=");
    strcat(new_cp_option, cp_value);
    strcat(new_cp_option, PATH_SEPARATOR_STR ".");
    
    options[num_options++].optionString = new_cp_option;
#if notyet
    free(cp_value);
#endif
  } 

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

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);
  params = (*env)->NewGlobalRef(env, params);

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

      (*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)
{
  JavaVM *jvm = NULL;
  JNIEnv *env = NULL;
  JavaVMInitArgs vm_args;

  jclass cls;
  jmethodID mid;

  vm_args.version = JNI_VERSION_1_2;

#if 0
  /* turn on class loading spew. */
  vm_args.enableVerboseClassLoading = JNI_TRUE;
#endif

  /* 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)
    {
      THREAD_yield();
    }

  /* keep 'gcc -Wall -pedantic' happy */
  exit(0);
}
