// This is the source code to JavaAppLauncher from Oracle. I started from rev. 34, obtained from https://svn.java.net/svn/appbundler~svn.
// Changes were necessary to allow command-line arguments (eg., -linkmode connect -linkmode foo) to be propagated to the Java code.
// Other tweaks were also made, to allow us to use a JRE outside of the JLink.app bundle, and to support command-line args like -XmxNNNN.
//
// New features:
//   - Propagate command-line args to Java (e.g., MathLink args)
//   - Takes starting classpath from Info.plist
//   - Allows command line to specify -X JVM arguments (overriding -Xmx256m from Info.plist) and -D.
//   - Info.plist can specify JRE to use. Key is JVMRuntime. If not present, or value is "bundled", then use JRE in M layout;
//       if value is "system", use default system Java7; if path provided, use that.
//
// Compile with:
//
//     clang -I $JAVA_HOME/include -I $JAVA_HOME/include/darwin -o JLink -framework Cocoa -mmacosx-version-min=10.14 main.m
//
//  tgayley, June 2013



/*
 * Copyright 2012, Oracle and/or its affiliates. All rights reserved.
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#import <Cocoa/Cocoa.h>
#include <TargetConditionals.h>
#include <dlfcn.h>
#include <jni.h>

#define JAVA_LAUNCH_ERROR "JavaLaunchError"

#define JVM_RUNTIME_KEY "JVMRuntime"
#define JVM_MAIN_CLASS_NAME_KEY "JVMMainClassName"
#define JVM_OPTIONS_KEY "JVMOptions"
#define JVM_ARGUMENTS_KEY "JVMArguments"

#define UNSPECIFIED_ERROR "An unknown error occurred."

#define APP_ROOT_PREFIX "$APP_ROOT"

#define LIBJLI_DYLIB "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/jli/libjli.dylib"

#if TARGET_CPU_ARM64
#define SYSTEMID "MacOSX-ARM64"
#elif TARGET_CPU_X86_64
#define SYSTEMID "MacOSX-x86-64"
#else
#error Need to define SYSTEMID for this CPU architecture
#endif


typedef int (JNICALL *JLI_Launch_t)(int argc, char ** argv,
                                    int jargc, const char** jargv,
                                    int appclassc, const char** appclassv,
                                    const char* fullversion,
                                    const char* dotversion,
                                    const char* pname,
                                    const char* lname,
                                    jboolean javaargs,
                                    jboolean cpwildcard,
                                    jboolean javaw,
                                    jint ergo);

int launch(int argc, char *argv[]);

int main(int argc, char *argv[]) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    int result;
    @try {
        launch(argc, argv);
        result = 0;
    } @catch (NSException *exception) {
        NSAlert *alert = [[NSAlert alloc] init];
        [alert setAlertStyle:NSCriticalAlertStyle];
        [alert setMessageText:[exception reason]];
        [alert runModal];

        result = 1;
    }

    [pool drain];

    return result;
}

int launch(int argc, char *argv[]) {

    char *commandName = argv[0];

    // Get the main bundle
    NSBundle *mainBundle = [NSBundle mainBundle];

    // Set the working directory to the user's home directory
    chdir([NSHomeDirectory() UTF8String]);

    // Get the main bundle's info dictionary
    NSDictionary *infoDictionary = [mainBundle infoDictionary];

	// Determine the JRE to use.
    NSString *runtime = [infoDictionary objectForKey:@JVM_RUNTIME_KEY];
    const char *libjliPath = NULL;
    if (runtime == nil || [runtime isEqualToString:@"bundled"]) {
    	// Use the JRE bundled in Mathematica layout.
        libjliPath = [[[mainBundle bundlePath] stringByAppendingPathComponent:@"../../../Java/" SYSTEMID "/lib/jli/libjli.dylib"] fileSystemRepresentation];
     } else if ([runtime isEqualToString:@"system"]) {
    	// Use the system's default JRE.
        libjliPath = LIBJLI_DYLIB;
    } else {
    	// Use user-supplied path from JVMRuntime key in Info.plist.
        libjliPath = [[runtime stringByAppendingPathComponent:@"Contents/Home/jre/lib/jli/libjli.dylib"] fileSystemRepresentation];
    }

    void *libJLI = dlopen(libjliPath, RTLD_LAZY);

    JLI_Launch_t jli_LaunchFxnPtr = NULL;
    if (libJLI != NULL) {
        jli_LaunchFxnPtr = dlsym(libJLI, "JLI_Launch");
    }

    if (jli_LaunchFxnPtr == NULL) {
        [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR
            reason:@"Unable to load Java Runtime Environment."
            userInfo:nil] raise];
    }

    // Get the main class name
    NSString *mainClassName = [infoDictionary objectForKey:@JVM_MAIN_CLASS_NAME_KEY];
    if (mainClassName == nil) {
        [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR
            reason:@"Main class name is required."
            userInfo:nil] raise];
    }

    // Get the VM options
    NSArray *options = [infoDictionary objectForKey:@JVM_OPTIONS_KEY];
    if (options == nil) {
        options = [NSArray array];
    }

    // Get the application arguments
    NSArray *arguments = [infoDictionary objectForKey:@JVM_ARGUMENTS_KEY];
    if (arguments == nil) {
        arguments = [NSArray array];
    }

    // Initialize the arguments to JLI_Launch(). We make room for 2*(argc-1) because we potentially add each
    // of those arguments twice (if they are JVM-intended args like -Xmx512m they get added before the main class
    // name, and then again afterwards; these second additions have no effect, but blindly adding them is easier
    // than trying to cull them out).
    int newArgc = 1 + [options count] + 1 + [arguments count] + 2*(argc-1);
    char *newArgv[newArgc];

    int i = 0;
    newArgv[i++] = commandName;

    for (NSString *option in options) {
        option = [option stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
        newArgv[i++] = strdup([option UTF8String]);
    }

	// Now add any command-line args that are intended for the JVM. This allows a command-line spec like -Xmx512m to override
	// the one in the Info.plist, as later options override earlier ones. Only support -X... and -D... options.
	int j;
    for (j = 1; j < argc; j++) {
    	char* s = argv[j];
    	if (strlen(s) > 2 && s[0] == '-' && (s[1] == 'X' || s[1] == 'D'))
    		newArgv[i++] = s;
    }	
	
    newArgv[i++] = strdup([mainClassName UTF8String]);

    for (NSString *argument in arguments) {
        argument = [argument stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
        newArgv[i++] = strdup([argument UTF8String]);
    }

	// Here we propagate the command-line args initially passed to JavaAppLauncher to Java. These include the MathLink
	// args for connecting back to the kernel during a typical launch. We will also be appending any JVM-specific args
	// like -Xmx512m, although these were already added earlier to the command line, before the main class name. No problem; these
	// args will be ignored by J/Link.
    for (j = 1; j < argc; j++) {
    	newArgv[i++] = argv[j];
    }

	// We made room for 2*(argc-1) extra args, but probably didn't add that many, so fill out the array.
	while (i < newArgc)
		newArgv[i++] = "";
		
    // Invoke JLI_Launch()
    return jli_LaunchFxnPtr(newArgc, newArgv,
                            0, NULL,
                            0, NULL,
                            "",
                            "",
                            "java",
                            "java",
                            FALSE,
                            FALSE,
                            FALSE,
                            0);
}
