/*
 * Copyright (c) 2001, 2004 IBM Corp and others.  All rights reserved.
 * This file is made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors: 
 * 	  Andre Weinand (OTI Labs)
 */
 
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <unistd.h>

#include <Carbon/Carbon.h>
#include <JavaVM/jni.h>

#include "java_swt.h"
#include "util.h"

#define CLASSPATH "-Djava.class.path="
#define GENERIC_JAVA_ICON "/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Resources/GenericApp.icns"
#define APP_PACKAGE_PATTERN ".app/Contents/MacOS/"
#define JAVAROOT_PATH "/Contents/Resources/Java"

/* SPI */
extern OSErr CPSEnableForegroundOperation(ProcessSerialNumber *psn,  UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);

static void setDockIcon(const char *dockIcon);
static char *createString(CFStringRef sr, bool expand);
static void addOption(JavaVMInitArgs *vm_args, char *option);


static char *fgAppPackagePath;
static char *fgJavaRootPath;


int main(int argc, const char **argv)
{
	const char *progName= argv[0];
	const char *iconFile= NULL;
	const char *dockName= NULL;	// name shown in dock and application menu
	const char *jvmVersion= NULL;
	bool debug= false;
	bool useInfoPlist= false;
	int i, status, optionArgCount= 0, argIndex, result= 0;
	JavaVMInitArgs vm_args;
	char *mainClassName= NULL;
    JNIEnv *env;
    JavaVM *theVM; 
	char wd[300];
	char **javaMainArgs;	// here we collect all arguments passed to Java main method
	int javaMainArgCount= 0;
    

  	vm_args.ignoreUnrecognized= JNI_TRUE;
    vm_args.nOptions= 0;
    vm_args.options= (JavaVMOption*) calloc(argc+100, sizeof(JavaVMOption));
    vm_args.version= JNI_VERSION_1_4;	// on Mac OS X this indicates a 1.4.x JVM
  
	// quick check for debug flag
	for (i= 1; i < argc; i++) {
		const char *arg= argv[i];
		if (strcmp(arg, "-XX:debug") == 0) {
			debug= true;
		} else if (argc < 3 && strncmp(arg, "-psn_", 5) == 0) {
			useInfoPlist= true;
		}
	}
    
	if (debug) {	// print arguments to console
		fprintf(stderr, "java_swt\n");
		fprintf(stderr, "  wd: %s\n", getcwd(wd, sizeof(wd)));
		for (i= 0; i < argc; i++)
			fprintf(stderr, "  %02d: <%s>\n", i, argv[i]);
	}

	addOption(&vm_args, strconcat("-Dorg.eclipse.swtlauncher=", progName));
		
    if (useInfoPlist) {
    	
    	/* working directory */
    	char *buffer= strsave(argv[0]);
     	char *pos= my_strcasestr(buffer, APP_PACKAGE_PATTERN);
    	if (pos != NULL) {
    		pos[5]= '.';
   			pos[6]= '.';
   			pos[7]= '\0';
    	}
	    if (chdir(buffer) != 0)
	    	fprintf(stderr, "couldn't chdir to %s\n", buffer);
	    free(buffer);
	    
	    // find path to application bundle
		pos= my_strcasestr(argv[0], APP_PACKAGE_PATTERN);
		if (pos != NULL) {
			int l= pos-argv[0] + 4;	// reserve space for ".app"
			fgAppPackagePath= malloc(l+1);
			strncpy(fgAppPackagePath, argv[0], l);
			fgAppPackagePath[l]= '\0';	// terminate result
		}	
	    
	    if (debug)
	    	fprintf(stderr, "  $APP_PACKAGE=%s\n", fgAppPackagePath);
	    
	    // and this is $JAVAROOT
	    fgJavaRootPath= strconcat(fgAppPackagePath, JAVAROOT_PATH);
	    if (debug)
	    	fprintf(stderr, "  $JAVAROOT=%s\n", fgJavaRootPath);
	    	
		addOption(&vm_args, strconcat("-Djava.library.path=", fgJavaRootPath));
		
     	char *workingDir= NULL;
    		    
	    // Get the main bundle for the app
		CFBundleRef mainBundle= CFBundleGetMainBundle();
		if (mainBundle != NULL) {
			// Get an instance of the info plist.
			CFDictionaryRef bundleInfoDict= CFBundleGetInfoDictionary(mainBundle);
			// If we succeeded, look for our property.
			if (bundleInfoDict != NULL) {
			    CFDictionaryRef ar= CFDictionaryGetValue(bundleInfoDict, CFSTR("Java"));
				if (ar != 0) {

	        		// JVM Version
					jvmVersion= createString(CFDictionaryGetValue(ar, CFSTR("JVMVersion")), true);
					
	        		// Main Class
					mainClassName= createString(CFDictionaryGetValue(ar, CFSTR("MainClass")), false);
					
					// Class Path
					CFStringRef classPath= CFDictionaryGetValue(ar, CFSTR("ClassPath"));
					if (classPath != NULL) {
						char *cp= createString(classPath, true);
						addOption(&vm_args, strconcat(CLASSPATH, cp));
	        			free(cp);
	        		}

					// Working Directory
					workingDir= createString(CFDictionaryGetValue(ar, CFSTR("WorkingDirectory")), true);

					// Arguments
					CFArrayRef args= CFDictionaryGetValue(ar, CFSTR("Arguments"));
					if (args != NULL) {
				    	CFIndex size= CFArrayGetCount(args);
				    	javaMainArgs= calloc(size, sizeof(char*));
					    for (i= 0; i < size; i++) {
							CFStringRef sr= (CFStringRef) CFArrayGetValueAtIndex(args, i);
							javaMainArgs[javaMainArgCount++]= createString(sr, true);
						}
					}
						
					// Properties
			    	CFDictionaryRef properties= CFDictionaryGetValue(ar, CFSTR("Properties"));
			    	if (properties != NULL) {
				    	int n= CFDictionaryGetCount(properties);
				    	if (n > 0) {
					    	CFTypeRef *keys= malloc(n*sizeof(CFTypeRef*));
					    	CFTypeRef *values= malloc(n*sizeof(CFTypeRef*));
					    	CFDictionaryGetKeysAndValues(properties, keys, values); 
					    	int i;
					    	for (i= 0; i < n; i++) {
					    		char *k= createString((CFStringRef) keys[i], false);
					    		char *v= createString((CFStringRef) values[i], true);				    		
					    		char *t= malloc(2+strlen(k)+1+strlen(v)+1);
					    		sprintf(t, "-D%s=%s", k, v);
					    		addOption(&vm_args, t);
					    		free(k);
					    		free(v);
					    	}
					    	free(keys);
					    	free(values);
				    	}
				    }
					
					// VM Options
					CFStringRef vmOptions= CFDictionaryGetValue(ar, CFSTR("VMOptions"));
					if (vmOptions != NULL) {
						char *vmo= createString(vmOptions, false);
						if (vmo != NULL) {
							char *ap, *s= vmo;
							int i= 0;
	           				while (ap= strsep(&s, " ")) {
	                  			if (*ap != '\0') { 
	                  				addOption(&vm_args, expandShell(strsave(ap), fgAppPackagePath, fgJavaRootPath));
	                  			}
	                  		}
						}
						free(vmo);
					}
			    }
			}
		}
		
    	/* working directory */	    
	    if (workingDir != NULL) {
	    	if (chdir(workingDir) != 0)
	    		fprintf(stderr, "couldn't chdir to %s\n", workingDir);
	    	free(workingDir);
	    	workingDir= NULL;
	    }

		argIndex= argc;
		
	} else {
   
		iconFile= getenv(DOCK_ICON);
		if (iconFile != NULL)
			unsetenv(DOCK_ICON);
		
	    for (argIndex= 1; argIndex < argc; argIndex++) {
	        
	        const char *arg= argv[argIndex];
	                
	        if (arg[0] != '-')	// JVM options start with '-'
	        	break;
	        	
			if (strcmp(arg, "-XX:debug") == 0)
				continue;
	
			if (strncmp(arg, "-Xdock:", 7) == 0) {	//	-Xdock:name=<application name>[:icon=<path to icon file>]
				//iconFile= &arg[7];
				continue;	
			}
	
			if (strncmp(arg, "-XXvm=", 6) == 0) {
				if (strstr(arg, "1.3") != NULL)
	     			vm_args.version= JNI_VERSION_1_2;	// on Mac OS X this indicates a 1.3.1 JVM
	     		else if (strstr(arg, "1.4") != NULL)
	     			vm_args.version= JNI_VERSION_1_4;	// on Mac OS X this indicates a 1.4.x JVM
	     		else
	     			fprintf(stderr, "%s: Unrecognized VM.\n", progName);
	     		continue;
	        }
	         
	        /* If the option string is '-cp' or '-classpath', replace it with '-Djava.class.path=' and append
	           the next option which contains the actual class path. */
			if (strcmp(arg, "-cp") == 0 || strcmp(arg, "-classpath") == 0) {
	            if (argIndex < argc) {
	            	arg= strconcat(CLASSPATH, argv[++argIndex]);
	             } else {
	                /* We shouldn't reach here unless the last arg was -cp */
	                fprintf(stderr, "%s: Error parsing class path.\n", progName);
	                continue;
	            }
	        }
	        
	        // arg is for VM
	        if (debug)
	        	fprintf(stderr, " vm_arg[%d]: %s\n", vm_args.nOptions, arg);
	        addOption(&vm_args, (char*)arg);
	     }
	    
	    /* If there are still args left lets get the main class */
	    if (argIndex < argc)
	    	mainClassName= (char*) argv[argIndex++];

	    javaMainArgs= calloc(argc+1, sizeof(char*));
        for (i= argIndex; i < argc; i++)
        	javaMainArgs[javaMainArgCount++]= (char*)argv[i];
    }
    
	if (debug) {	// print arguments to console
		char wd[300];
		fprintf(stderr, "  wd: %s\n", getcwd(wd, sizeof(wd)));
	}

	if (mainClassName == NULL) {
		fprintf(stderr, "%s: no main class specified.\n", progName);
	    exit(-1);
	}
	if (debug)
		fprintf(stderr, " mainclass: %s\n", mainClassName);
	if (dockName == NULL)	
		dockName= strsave(mainClassName);	// if no dock name has been set use copy of main class name
	/* Set our name for the Application Menu to our Main class */
	char a[32];
	pid_t id= getpid();
	sprintf(a, "APP_NAME_%ld", (long)id);
	setenv(a, mainClassName, 1);
	
    /* start a VM session */    
    result= JNI_CreateJavaVM(&theVM, (void**)&env, &vm_args);
    if (result != 0 && vm_args.version == JNI_VERSION_1_4) {
    	// fall back to 1.3.1
    	if (debug)
    		fprintf(stderr, "%s: 1.4.1 VM not available; falling back to 1.3.1\n", progName);
    	vm_args.version= JNI_VERSION_1_2;
    	result= JNI_CreateJavaVM(&theVM, (void**)&env, &vm_args);
    }

    if (result != 0) {
        fprintf(stderr, "%s: Error starting up VM.\n", progName);
        exit(result);
    }
      
    char c, *cp= mainClassName;
	/* Replace '.' with '/' in qualified main class name */
	for (; c= *cp; cp++)
		if (c == '.')
			*cp= '/';
    
	/* Find the main class */
    jclass mainClass= (*env)->FindClass(env, mainClassName);
    if (mainClass == NULL) {
        (*env)->ExceptionDescribe(env);
        result= -1;
        goto leave;
    }

    /* Get the application's main method */
    jmethodID mainID= (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");
    if (mainID == NULL) {
        if ((*env)->ExceptionOccurred(env)) {
            (*env)->ExceptionDescribe(env);
        } else {
            fprintf(stderr, "%s: No main method found in specified class.\n", progName);
        }
        result= -1;
        goto leave;
    }

    /* Build argument array */
    jarray mainArgs= NULL;
    jarray cls= (*env)->FindClass(env, "java/lang/String");
    if (cls != NULL) {
        mainArgs= (*env)->NewObjectArray(env, javaMainArgCount, cls, 0);
        if (mainArgs != NULL) {
            /* Add each of the c strings to the new array as UTF java.lang.String objects */
            for (i= 0; i < javaMainArgCount; i++) {
            	const char *arg= javaMainArgs[i];
        		if (debug)
        			fprintf(stderr, " arg[%d]: %s\n", i, arg);
                jstring str= (*env)->NewStringUTF(env, arg);
                if (str != NULL) {
                    (*env)->SetObjectArrayElement(env, mainArgs, i, str);
                    (*env)->DeleteLocalRef(env, str);
                } else
                    break;
            }
		} else {
       	 	(*env)->ExceptionDescribe(env);
        	goto leave;
		}
    }
    
	ProcessSerialNumber psn;
	status= GetCurrentProcess(&psn);
	if (status == noErr) {
		if (dockName != NULL)
			CPSSetProcessName(&psn, dockName);
		CPSEnableForegroundOperation(&psn, 0x03, 0x3C, 0x2C, 0x1103);
		SetFrontProcess(&psn);
	}
    
    if (!useInfoPlist) {
	    /* dock icon */
	    if (iconFile == NULL)
	    	iconFile= GENERIC_JAVA_ICON;
	    setDockIcon(iconFile);
	}
         
    /* Invoke main method passing in the argument object. */
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
    if ((*env)->ExceptionOccurred(env)) {
        (*env)->ExceptionDescribe(env);
        result= -1;
        goto leave;
    }
        
leave:
    (*theVM)->DestroyJavaVM(theVM);

	if (debug)
		fprintf(stderr, "exit code: %d\n", result);

 	exit(result);
}

/*
 * Returns NULL if sr is NULL
 */
static char *createString(CFStringRef sr, bool expand) {
	if (sr != NULL) {
		CFIndex argStringSize= CFStringGetMaximumSizeForEncoding(CFStringGetLength(sr), kCFStringEncodingUTF8) + 1;
		char *s= malloc(argStringSize);
		if (CFStringGetCString(sr, s, argStringSize, kCFStringEncodingUTF8)) {
			if (expand)
				return expandShell(s, fgAppPackagePath, fgJavaRootPath);
			return s;
		}
		free(s);
	}
	return NULL;
}

static void addOption(JavaVMInitArgs *vm_args, char *option) {
	//int n= vm_args->nOptions+1;
	//vm_args->options= realloc(vm_args->options, sizeof(JavaVMOption)*n);
	vm_args->options[vm_args->nOptions++].optionString= option;
}


void releaseData(void *info, const void *data, size_t size) {
	free((void*)data);
}

CGImageRef readImageRef(const char *path) {

	CFURLRef url= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8*)path, strlen(path), false);
	CGDataProviderRef provider= NULL;

	CGImageRef image= NULL;
	char *dot= rindex(path, '.');
	if (dot != NULL) {
		const char *extension= &dot[1];
		if (strcasecmp(extension, "png") == 0) {
			provider= CGDataProviderCreateWithURL(url);
			image= CGImageCreateWithPNGDataProvider(provider, NULL, true, kCGRenderingIntentDefault);
		} else if (strcasecmp(extension, "jpg") == 0) {
			provider= CGDataProviderCreateWithURL(url);
			image= CGImageCreateWithJPEGDataProvider(provider, NULL, true, kCGRenderingIntentDefault);
		} else if (strcasecmp(extension, "icns") == 0) {
			OSStatus status;
			FSRef ref;
			IconFamilyHandle iconFamily;
			
 			if (CFURLGetFSRef(url, &ref)) {
				FSSpec fileSpec;
  				status= FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, &fileSpec, NULL);
				if (status == noErr) {
					status= ReadIconFile(&fileSpec, &iconFamily);
					if (status == noErr) {
						int size= 128 * 128 * 4;
						Handle rawBitmapdata= NewHandle(size);
						status= GetIconFamilyData(iconFamily, kThumbnail32BitData, rawBitmapdata);
						if (status == noErr) {
						} else
							fprintf(stderr, "GetIconFamilyData1: %d\n", status);
							
						Handle rawMaskdata= NewHandle(128 * 128);
						status= GetIconFamilyData(iconFamily, kThumbnail8BitMask, rawMaskdata);
						if (status == noErr) {
						} else
							fprintf(stderr, "GetIconFamilyData2: %d\n", status);
							
						void *data= malloc(size);
						HLock(rawBitmapdata);
						HLock(rawMaskdata);
						// copy mask data into alpha channel
						const char *mask= (const char*) *rawMaskdata;
						const char *from= (const char*) *rawBitmapdata;
						char *to= data;
						int i;
						for (i= 0; i < 128*128; i++) {
							from++;
							*to++= *mask++;
							*to++= *from++;
							*to++= *from++;
							*to++= *from++;
						}
						HUnlock(rawBitmapdata);
						HUnlock(rawMaskdata);
						DisposeHandle(rawBitmapdata);
						DisposeHandle(rawMaskdata);
						
						provider= CGDataProviderCreateWithData(NULL, data, size, releaseData);
						
						CGColorSpaceRef cs= CGColorSpaceCreateDeviceRGB();
						
						image= CGImageCreate(	128,	// width
												128,	// height
        										8,		// Bits per component
        										32,		// Bits per pixel
        										4 * 128,	// Bytes per row
        										cs,
        										kCGImageAlphaFirst,
        										provider,
        										NULL,
        										0,
        										kCGRenderingIntentDefault);
        										
        				CGColorSpaceRelease(cs);
						
					} else
						fprintf(stderr, "ReadIconFile: %d\n", status);
				} else
					fprintf(stderr, "FSGetCatalogInfo: %d\n", status);
			} else {
				fprintf(stderr, "CFURLGetFSRef: can't convert %s\n", path);
			}
		} else
			fprintf(stderr, "can't convert %s to CGImageRef: unknown extension\n", path);
	}
	if (provider != NULL)
		CGDataProviderRelease(provider);
	CFRelease(url);
	
	return image;
}

static void setDockIcon(const char *dockIcon) {

	CGImageRef image= readImageRef(dockIcon);
	if (image != NULL) {
		// without the following two statements SetApplicationDockTileImage would not work
		CGrafPtr p= BeginQDContextForApplicationDockTile();
		if (p == NULL)
			return;
		EndQDContextForApplicationDockTile(p);
		// GetAvailableWindowPositioningBounds
	
		OSStatus status= SetApplicationDockTileImage(image);
		if (status == noErr) {
			EventRecord event;
			for (;;) {
				Boolean gotEvent= WaitNextEvent(everyEvent, &event, 32767L, nil);
				if (gotEvent) {
					switch (event.what) {
					case kHighLevelEvent:
						AEProcessAppleEvent(&event);
						break;
					default:
						fprintf(stderr, "other event\n");
						break;
					}
					break;
				}
			}
		}
		CGImageRelease(image);
	}
}
