/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */

#import "SparkleView.h"

#import <sys/stat.h>
#import <sys/time.h>
#import <stdio.h>

#define KEYWORD_VMARGUMENTS			@"vmarguments"
#define KEYWORD_LIBRARIESPATH		@"librariespath"
#define KEYWORD_CLASSESPATH			@"classespath"
#define KEYWORD_CLASSES				@"classes"
#define KEYWORD_MAINCLASS			@"mainclass"
#define KEYWORD_MAINCLASSARGUMENTS	@"mainclassarguments"

#define USE_METHOD_CALL_DEBUG 0
#if (USE_METHOD_CALL_DEBUG == 1)
	#define PDEBUG(format, ...) NSLog( @"Plug-in: File=%s line=%d proc=%s " format, strrchr("/" __FILE__,'/')+1, __LINE__, __PRETTY_FUNCTION__, ## __VA_ARGS__ )
#else
	#define PDEBUG(format, ...)
#endif

#define jlong_to_ptr(value) (intptr_t)value
#define ptr_to_jlong(value) (jlong)((intptr_t)value)

#define ONE_SEC_MILLISECS 1000000
static inline uint64_t timeMillis(struct timeval *t)
{
	return (t->tv_usec + (ONE_SEC_MILLISECS * t->tv_sec));
}
static inline uint64_t getTimeMillis()
{
	struct timeval now;
	gettimeofday(&now, NULL);
	return timeMillis(&now);
}

static inline void appendEnvPath(char *env, char *value)
{
	//NSLog(@"appendEnvPath env:%s, value:%s", env, value);
	char *existing = getenv(env);
	if (existing != NULL)
	{
		char *newValue = malloc(strlen(existing)+1+strlen(value)+1);
		if (newValue != NULL)
		{
			strcpy(newValue, existing);
			strcat(newValue, ":");
			strcat(newValue, value);
			setenv(env, newValue, 1);
			free(newValue);
		}
	}
	else
	{
		setenv(env, value, 1);
	}
}

static inline void setJavaEnvironment(jint version, NSString *libPath)
{
	CFStringRef targetJVM = CFSTR("1.6");
	
	// Look for the JavaVM bundle using its identifier
	CFBundleRef JavaVMBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.JavaVM"));
	if (JavaVMBundle != NULL)
	{
		// Get a path for the JavaVM bundle
		CFURLRef JavaVMBundleURL = CFBundleCopyBundleURL(JavaVMBundle);
		//CFShow(JavaVMBundleURL);
		CFRelease(JavaVMBundle);
		
		if (JavaVMBundleURL != NULL)
		{
			// Append to the path the Versions Component
			CFURLRef JavaVMBundlerVersionsDirURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, JavaVMBundleURL, CFSTR("Versions"), true);
			//CFShow(JavaVMBundlerVersionsDirURL);
			CFRelease(JavaVMBundleURL);
			
			if (JavaVMBundlerVersionsDirURL != NULL)
			{
				// Append to the path the target JVM's Version
				CFURLRef TargetJavaVM = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, JavaVMBundlerVersionsDirURL, targetJVM, true);
				//CFShow(TargetJavaVM);
				CFRelease(JavaVMBundlerVersionsDirURL);
				
				if (TargetJavaVM != NULL)
				{
					UInt8 pathToTargetJVM[PATH_MAX] = "\0";
					if (CFURLGetFileSystemRepresentation (TargetJavaVM, true, pathToTargetJVM, PATH_MAX))
					{
						// Check to see if the directory, or a sym link for the target JVM directory exists, and if so set the
						// environment variable JAVA_JVM_VERSION to the target JVM.
						struct stat sbuf;
						if (stat((char*)pathToTargetJVM, &sbuf) == 0)
						{
							// Ok, the directory exists, so now we need to set the environment var JAVA_JVM_VERSION to the CFSTR targetJVM
							// We can reuse the pathToTargetJVM buffer to set the environement var.
							if (CFStringGetCString(targetJVM, (char*)pathToTargetJVM, PATH_MAX, kCFStringEncodingUTF8))
							{
								setenv("JAVA_JVM_VERSION", (char*)pathToTargetJVM, 1);
							}
						}
					}
					CFRelease(TargetJavaVM);
				}
			}
		}
	}
	
	appendEnvPath("DYLD_LIBRARY_PATH", (char*)[libPath UTF8String]);
}

@interface SparkleView (Internal)

- (id)initWithArguments:(NSDictionary *)arguments;

@end

@implementation SparkleView (Internal)

- (id)initWithArguments:(NSDictionary *)arguments
{
	PDEBUG();
	
	self = [super init];
	
	self->jvmVersion = JNI_VERSION_1_6;
	
	self->startTime = getTimeMillis()/1000;
	
	//NSLog(@"arguments: %@", arguments);
	self->basePath = [[[arguments objectForKey:WebPlugInBaseURLKey] path] stringByDeletingLastPathComponent];
	
	NSDictionary *webPlugInAttributesKey = [arguments objectForKey:WebPlugInAttributesKey];
	self->keyVmArguments = [webPlugInAttributesKey objectForKey:KEYWORD_VMARGUMENTS];
	self->keyLibrariesPath = [[webPlugInAttributesKey objectForKey:KEYWORD_LIBRARIESPATH] retain];
	self->keyClassesPath = [[webPlugInAttributesKey objectForKey:KEYWORD_CLASSESPATH] retain];
	self->keyClasses = [[webPlugInAttributesKey objectForKey:KEYWORD_CLASSES] retain];
	self->keyMainClass = [[webPlugInAttributesKey objectForKey:KEYWORD_MAINCLASS] retain];
	self->keyMainClassArguments = [[webPlugInAttributesKey objectForKey:KEYWORD_MAINCLASSARGUMENTS] retain];
	
	[self setFrame:NSMakeRect(0, 0, [[webPlugInAttributesKey objectForKey:@"width"] floatValue], [[webPlugInAttributesKey objectForKey:@"height"] floatValue])];
	
#if 0
	NSLog(@"Applet info:");
	NSLog(@"	self->startTime: [%llu]", self->startTime);
	NSLog(@"	self->basePath: [%@]", self->basePath);
	NSLog(@"	self->keyVmArguments: [%@]", self->keyVmArguments);
	NSLog(@"	self->keyLibrariesPath: [%@]", self->keyLibrariesPath);
	NSLog(@"	self->keyClassesPath: [%@]", self->keyClassesPath);
	NSLog(@"	self->keyClasses: [%@]", self->keyClasses);
	NSLog(@"	self->keyMainClass: [%@]", self->keyMainClass);
	NSLog(@"	self->keyMainClassArguments: [%@]", self->keyMainClassArguments);
	NSLog(@"\n");
#endif
	
	self->libPath = @"";
	if ([self->keyLibrariesPath characterAtIndex:0] != '/')
	{
		self->libPath = [[NSString stringWithFormat:@"%@/%@", self->basePath, self->keyLibrariesPath] retain];
	}
	else
	{
		self->libPath = self->keyLibrariesPath;
	}
	
	self->classPath = @"";
	NSArray *classes = [self->keyClasses componentsSeparatedByString:@":"];
	for (int i=0; i<[classes count]; i++)
	{
		NSString *class = [classes objectAtIndex:i];
		if ([class length] > 0)
		{
			if ([class characterAtIndex:0] != '/')
			{
				self->classPath = [self->classPath stringByAppendingFormat:@"%@/%@/%@", self->basePath, self->keyClassesPath, class];
			}
			else
			{
				self->classPath = [self->classPath stringByAppendingFormat:@"%@", class];
			}
			
			if (i < ([classes count]-1))
			{
				self->classPath = [self->classPath stringByAppendingString:@":"];
			}
		}
	}
	
	self->startOnFirstThread = NO;
	NSArray *args = [self->keyVmArguments componentsSeparatedByString:@","];
	self->vmArg = @"";
	for (int i=0; i<[args count]; i++)
	{
		NSString *arg = [args objectAtIndex:i];
		if ([arg isEqual:@"-XstartOnFirstThread"] == YES)
		{
			self->startOnFirstThread = YES;
		}
		
		self->vmArg = [self->vmArg stringByAppendingString:[NSString stringWithFormat:@"%@ ", arg]];
	}
#if 0
	self->vmArg = [self->vmArg stringByAppendingString:[NSString stringWithFormat:@"-Dsparkle.plugin=%llu -Dsparkle.time=%llu", ptr_to_jlong(NULL), self->startTime]];
#else
	self->vmArg = [self->vmArg stringByAppendingString:[NSString stringWithFormat:@"-Dsparkle.plugin=%llu -Dsparkle.time=%llu", ptr_to_jlong(self), self->startTime]];
#endif
	self->libPathArg = [[NSString stringWithFormat:@"-Djava.library.path=%@", self->libPath] retain];
	self->classPathArg = [[NSString stringWithFormat:@"-Djava.class.path=%@", self->classPath] retain];
	
#if 0
	NSLog(@"Applet derived info:");
	NSLog(@"	self->startOnFirstThread: [%d]", self->startOnFirstThread);
	NSLog(@"	self->libPath: [%@]", self->libPath);
	NSLog(@"	self->classPath: [%@]", self->classPath);
	NSLog(@"	self->vmArg: [%@]", self->vmArg);
	NSLog(@"	self->libPathArg: [%@]", self->libPathArg);
	NSLog(@"	self->classPathArg: [%@]", self->classPathArg);
	NSLog(@"\n");
#endif
	
    return self;
}

@end

@implementation SparkleView

// WebPlugInViewFactory protocol
// The principal class of the plug-in bundle must implement this protocol.

+ (NSView *)plugInViewWithArguments:(NSDictionary *)newArguments
{
	PDEBUG();
	
    return [[[self alloc] initWithArguments:newArguments] autorelease];
}

- (BOOL)isOpaque
{
    return NO;
}

- (BOOL)becomeFirstResponder
{
    return NO;
}

- (BOOL)acceptsFirstResponder
{
    return NO;
}

- (BOOL)canBecomeKeyView
{
    return NO;
}

- (void)markJavaVmReady
{
	self->jvmReady = YES;
}

- (void)destroyJavaVm
{
	if (self->jvm != NULL)
	{
		jint error = (*self->jvm)->DestroyJavaVM(self->jvm);
		if (error != 0)
		{
			NSLog(@"		error:%d", error);
		}
		self->jvm = NULL;
	}
	self->jvmReady = NO;
}

- (void)launchJavaVm
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	{
#if 0
		fprintf(stderr, "\n\njava %s %s %s %s %s\n\n", [self->vmArg UTF8String], [self->libPathArg UTF8String], [self->classPathArg UTF8String], [self->keyMainClass UTF8String], [self->keyMainClassArguments UTF8String]);
#endif
		
		setJavaEnvironment(self->jvmVersion, self->libPath);
		
		
		NSArray *vmArguments = [self->vmArg componentsSeparatedByString:@" "];
		uint nrOfVmArguments = [vmArguments count];
		
		uint nrOfOptions = nrOfVmArguments+2;
		JavaVMOption options[nrOfOptions];
		for (uint i=0; i<nrOfVmArguments; i++)
		{
			NSString *argument = [vmArguments objectAtIndex:i];
			options[i].optionString = (char*)[argument UTF8String];
		}
		options[(nrOfVmArguments+0)].optionString = (char*)[self->libPathArg UTF8String];
		options[(nrOfVmArguments+1)].optionString = (char*)[self->classPathArg UTF8String];
		
		JNIEnv *env = NULL;
		JavaVMInitArgs vm_args;
		vm_args.version	= self->jvmVersion;
		vm_args.options	= options;
		vm_args.nOptions = nrOfOptions;
		vm_args.ignoreUnrecognized = JNI_TRUE;
		if (JNI_CreateJavaVM(&self->jvm, (void**)&env, &vm_args) == 0)
		{
			NSString *mainClassName = [self->keyMainClass stringByReplacingOccurrencesOfString:@"." withString:@"/"];
			jclass mainClass = (*env)->FindClass(env, [mainClassName UTF8String]);
			if (mainClass != NULL)
			{
				jmethodID mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");
				if (mainID != NULL)
				{
					jobjectArray jargs = NULL;
					
					NSArray *arguments = [self->keyMainClassArguments componentsSeparatedByString:@","];
					if ([arguments count] > 0)
					{
						jargs = (*env)->NewObjectArray(env, [arguments count], (*env)->FindClass(env, "java/lang/String"), NULL);
						if (jargs != NULL)
						{
							for (int i=0; i<[arguments count]; i++)
							{
								NSString *argument = [arguments objectAtIndex:i];
								(*env)->SetObjectArrayElement(env, jargs, i, (*env)->NewStringUTF(env, [argument UTF8String])); 
							}
						}
					}
					
					(*env)->CallStaticVoidMethod(env, mainClass, mainID, jargs);
					if ((*env)->ExceptionOccurred(env))
					{
						(*env)->ExceptionDescribe(env);
					}
				}
				else
				{
					if ((*env)->ExceptionOccurred(env))
					{
						(*env)->ExceptionDescribe(env);
					}
					else
					{
						NSLog(@"Sparkle: No main method found in specified class");
					}
				}
			}
			else
			{
				if ((*env)->ExceptionOccurred(env))
				{
					(*env)->ExceptionDescribe(env);
				}
				else
				{
					NSLog(@"Sparkle: %@ could not be found", mainClassName);
				}
			}
		}
		else
		{
			NSLog(@"Sparkle: Could not create JavaVM");
		}
		
		[self performSelectorOnMainThread:@selector(markJavaVmReady) withObject:nil waitUntilDone:YES];
	}
	[pool drain];
	
	if (self->startOnFirstThread == NO)
	{
		[self destroyJavaVm];
	}
}

//- (void)drawRect:(NSRect)rect
//{
//	NSLog(@"Sparkle: drawRect: [%@] %.2f,%.2f %.2fx%.2f,", self, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
//	NSLog(@"Sparkle: frame: [%@] %.2f,%.2f %.2fx%.2f,", self, [self frame].origin.x, [self frame].origin.y, [self frame].size.width, [self frame].size.height);
//	NSLog(@"Sparkle: bounds: [%@] %.2f,%.2f %.2fx%.2f,", self, [self bounds].origin.x, [self bounds].origin.y, [self bounds].size.width, [self bounds].size.height);
//	[[NSColor redColor] set];
//	NSRectFill([self bounds]);
//}

// WebPlugIn informal protocol

- (void)webPlugInInitialize
{
	PDEBUG();
    // This method will be only called once per instance of the plug-in object, and will be called
    // before any other methods in the WebPlugIn protocol.
    // You are not required to implement this method.  It may safely be removed.
	
	if (self->startOnFirstThread == YES)
	{
		[self launchJavaVm];
		self->jvmReady = YES;
	}
	else
	{
		self->jvmReady = NO;
		[self performSelectorInBackground:@selector(launchJavaVm) withObject:nil];
		
		// we must not block the main thread (Java code might need to call back on main thread), but we have to wait until jvm is ready and the Java main method has been called
		while (jvmReady == NO)
		{
			CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.01, false);
		}
	}
	
	void *ptr = NULL;
	JavaVMAttachArgs args = {0};
	(*self->jvm)->AttachCurrentThreadAsDaemon(self->jvm, &ptr, &args);
	
	NSArray *subviews = [self subviews];
	if ([subviews count] > 0)
	{
		self->subviewHost = [[subviews objectAtIndex:0] retain];
		//NSLog(@"Sparkle: self->subviewHost bounds: [%@] %.2f,%.2f %.2fx%.2f,", self->subviewHost, [self->subviewHost bounds].origin.x, [self->subviewHost bounds].origin.y, [self->subviewHost bounds].size.width, [self->subviewHost bounds].size.height);
		//NSLog(@"Sparkle: self->subviewHost frame: [%@] %.2f,%.2f %.2fx%.2f,", self->subviewHost, [self->subviewHost frame].origin.x, [self->subviewHost frame].origin.y, [self->subviewHost frame].size.width, [self->subviewHost frame].size.height);
	}
	
	subviews = [self->subviewHost subviews];
	if ([subviews count] > 0)
	{
		self->subviewTarget = [[subviews objectAtIndex:0] retain];
		//NSLog(@"Sparkle: self->subviewTarget bounds: [%@] %.2f,%.2f %.2fx%.2f,", self->subviewTarget, [self->subviewTarget bounds].origin.x, [self->subviewTarget frame].origin.y, [self->subviewTarget bounds].size.width, [self->subviewTarget bounds].size.height);
		//NSLog(@"Sparkle: self->subviewTarget frame: [%@] %.2f,%.2f %.2fx%.2f,", self->subviewTarget, [self->subviewTarget frame].origin.x, [self->subviewTarget frame].origin.y, [self->subviewTarget frame].size.width, [self->subviewTarget frame].size.height);
	}
	
	// with other normal html tabs Safari crashes with objc_class_name_AppletWindowBadge some time later
}

- (void)webPlugInStart
{
	PDEBUG();
    // The plug-in usually begins drawing, playing sounds and/or animation in this method.
    // You are not required to implement this method.  It may safely be removed.
	
	[[self window] setInitialFirstResponder:self->subviewTarget];
	[[self window] makeFirstResponder:self->subviewTarget];
	[self->subviewTarget becomeFirstResponder];
}

- (void)webPlugInStop
{
	PDEBUG();
    // The plug-in normally stop animations/sounds in this method.
    // You are not required to implement this method.  It may safely be removed.
	
	[self->subviewTarget resignFirstResponder];
}

- (void)webPlugInDestroy
{
	PDEBUG();
    // Perform cleanup and prepare to be deallocated.
    // You are not required to implement this method.  It may safely be removed.
	
	[self->subviewHost removeFromSuperviewWithoutNeedingDisplay]; // detach, so that we don't cause any more events
	CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, false);
	
	jint error = (*self->jvm)->DetachCurrentThread(self->jvm);
	if (error != 0)
	{
		NSLog(@"	error:%d", error);
	}
	
	if (self->startOnFirstThread == YES)
	{
		[self destroyJavaVm];
	}
	
	self->jvmReady = NO;
	
	[self->subviewTarget release];
	[self->subviewHost release];
}

- (void)webPlugInSetIsSelected:(BOOL)isSelected
{
	PDEBUG();
    // This is typically used to allow the plug-in to alter its appearance when selected.
    // You are not required to implement this method.  It may safely be removed.
}

- (id)objectForWebScript
{
	PDEBUG();
    // Returns the object that exposes the plug-in's interface.  The class of this object can implement
    // methods from the WebScripting informal protocol.
    // You are not required to implement this method.  It may safely be removed.
    return self;
}

//- (void)setFrameOrigin:(NSPoint)newOrigin
//{
//	NSLog(@"GlassHostView: setFrameOrigin: [%@] %.2f,%.2f,", self, newOrigin.x, newOrigin.y);
//	[super setFrameOrigin:newOrigin];
//	NSLog(@"	Sparkle: frame: [%@] %.2f,%.2f %.2fx%.2f,", self, [self frame].origin.x, [self frame].origin.y, [self frame].size.width, [self frame].size.height);
//	NSLog(@"	Sparkle: bounds: [%@] %.2f,%.2f %.2fx%.2f,", self, [self bounds].origin.x, [self bounds].origin.y, [self bounds].size.width, [self bounds].size.height);
//}
//
//- (void)setFrameSize:(NSSize)newSize
//{
//	NSLog(@"Sparkle: setFrameSize: [%@] %.2fx%.2f,", self, newSize.width, newSize.height);
//	[super setFrameSize:newSize];
//	NSLog(@"	Sparkle: frame: [%@] %.2f,%.2f %.2fx%.2f,", self, [self frame].origin.x, [self frame].origin.y, [self frame].size.width, [self frame].size.height);
//	NSLog(@"	Sparkle: bounds: [%@] %.2f,%.2f %.2fx%.2f,", self, [self bounds].origin.x, [self bounds].origin.y, [self bounds].size.width, [self bounds].size.height);
//}
//
//- (void)setFrame:(NSRect)frameRect
//{
//	NSLog(@"Sparkle: setFrame: [%@] %.2f,%.2f %.2fx%.2f,", self, frameRect.origin.x, frameRect.origin.y, frameRect.size.width, frameRect.size.height);
//	[super setFrame:frameRect];
//	NSLog(@"	Sparkle: frame: [%@] %.2f,%.2f %.2fx%.2f,", self, [self frame].origin.x, [self frame].origin.y, [self frame].size.width, [self frame].size.height);
//	NSLog(@"	Sparkle: bounds: [%@] %.2f,%.2f %.2fx%.2f,", self, [self bounds].origin.x, [self bounds].origin.y, [self bounds].size.width, [self bounds].size.height);
//}
//
//- (void)setBoundsOrigin:(NSPoint)newOrigin
//{
//	NSLog(@"Sparkle: setBoundsOrigin: [%@] %.2f,%.2f,", self, newOrigin.x, newOrigin.y);
//	[super setBoundsOrigin:newOrigin];
//	NSLog(@"	Sparkle: frame: [%@] %.2f,%.2f %.2fx%.2f,", self, [self frame].origin.x, [self frame].origin.y, [self frame].size.width, [self frame].size.height);
//	NSLog(@"	Sparkle: bounds: [%@] %.2f,%.2f %.2fx%.2f,", self, [self bounds].origin.x, [self bounds].origin.y, [self bounds].size.width, [self bounds].size.height);
//}
//
//- (void)setBoundsSize:(NSSize)newSize
//{
//	NSLog(@"Sparkle: setBoundsSize: [%@] %.2fx%.2f,", self, newSize.width, newSize.height);
//	[super setBoundsSize:newSize];
//	NSLog(@"	Sparkle: frame: [%@] %.2f,%.2f %.2fx%.2f,", self, [self frame].origin.x, [self frame].origin.y, [self frame].size.width, [self frame].size.height);
//	NSLog(@"	Sparkle: bounds: [%@] %.2f,%.2f %.2fx%.2f,", self, [self bounds].origin.x, [self bounds].origin.y, [self bounds].size.width, [self bounds].size.height);
//}
//
//- (void)setBounds:(NSRect)boundsRect
//{
//	NSLog(@"Sparkle: setBounds: [%@] %.2f,%.2f %.2fx%.2f,", self, boundsRect.origin.x, boundsRect.origin.y, boundsRect.size.width, boundsRect.size.height);
//	[super setBounds:boundsRect];
//	NSLog(@"	Sparkle: frame: [%@] %.2f,%.2f %.2fx%.2f,", self, [self frame].origin.x, [self frame].origin.y, [self frame].size.width, [self frame].size.height);
//	NSLog(@"	Sparkle: bounds: [%@] %.2f,%.2f %.2fx%.2f,", self, [self bounds].origin.x, [self bounds].origin.y, [self bounds].size.width, [self bounds].size.height);
//}

@end

