//
//  main.m
//  JavaLaunch
//
//  Created by seraphy on 2019/01/02.
//  Copyright © 2019 seraphy. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>

#define JAVA_LAUNCH_ERROR "JavaLaunchError"
#define UNSPECIFIED_ERROR "An unknown error occurred."

#define APP_ROOT_PREFIX "$APP_ROOT"

NSString *FindJavaHome() {
    NSTask *task = [[NSTask alloc] init];
    NSPipe *pipe  = [NSPipe pipe];
    
    [task setLaunchPath:@"/usr/libexec/java_home"];
    [task setArguments: [NSArray arrayWithObjects: @"-v", nil]];
    [task setStandardOutput: pipe];
    [task launch];
    
    NSFileHandle *handle = [pipe fileHandleForReading];
    NSData *data = [handle readDataToEndOfFile];
    NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
    [task waitUntilExit];
    int exitCode = [task terminationStatus];
    if (exitCode != 0) {
        NSLog(@"not found system java_home (exit:%d)", exitCode);
        return nil;
    }
    result = [result stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
    NSLog(@"found system java_home=(%@)", result);
    return result;
}

int main(int argc, const char * argv[]) {
    int exitCode = 0;
    @autoreleasepool {
        @try {
            // メインバンドル
            NSBundle *mainBundle = [NSBundle mainBundle];
            
            // *.appのディレクトリを取得
            NSString *mainBundlePath = [mainBundle bundlePath];
            NSLog(@"mainBundlePath=%@", mainBundlePath);
            
            // 実行可能jarの場所
            NSString *jarPath = [[mainBundlePath stringByAppendingPathComponent:@"Contents"] stringByAppendingPathComponent: [[mainBundle infoDictionary] objectForKey:@"ExecutableJar"]];
            NSLog(@"jarPath=%@", jarPath);
            
            // バンドルJREの場所を探索する
            NSString *pluginJreDir = [mainBundlePath stringByAppendingPathComponent:@"Contents/Plugins/JRE"];
            NSLog(@"pluginJreDir=%@", pluginJreDir);
            
            NSString *jreDir = pluginJreDir;
            
            NSFileManager *fileManager = [NSFileManager defaultManager];
            NSString *path = [pluginJreDir stringByAppendingPathComponent:@"Home/bin/java"];
            if ([fileManager fileExistsAtPath: path]) {
                // パターン1 Home/bin/java
                jreDir = [pluginJreDir stringByAppendingPathComponent:@"Home"];
            } else {
                // パターン2 bin/java
                path = [pluginJreDir stringByAppendingPathComponent:@"bin/java"];
                if (![fileManager fileExistsAtPath: path]) {
                    // バンドルJREがない場合は環境変数JAVA_HOMEがあれば、それを優先する
                    jreDir = [[[NSProcessInfo processInfo]environment]objectForKey:@"JAVA_HOME"];
                    if (jreDir == nil) {
                        // 環境変数JAVA_HOMEがなければ/usr/libexec/java_homeで問い合わせる
                        jreDir = FindJavaHome();
                    }
                }   
            }
            NSLog(@"JreDir=%@", jreDir);
            if (jreDir == nil) {
                // JAVA_HOMEを特定できなければエラーダイアログ表示
                NSString *title = [mainBundle localizedStringForKey: @"JavaDirectoryNotFound" value:@"JRE Not Found Error" table:@"Localizable"];
                [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR reason:title userInfo:nil] raise];
            }
            
            // アプリケーション名を取得(ローカライズされたもの)
            NSString *displayAppName;
            displayAppName = [[mainBundle localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"];
            NSLog(@"displayAppName=%@", displayAppName);
            printf("%s\n", [displayAppName UTF8String]);
            
            // JVMオプションの取得
            NSMutableArray *jvmOptions = [NSMutableArray array];
            NSArray *jvmOptionsFixed = [[mainBundle infoDictionary] objectForKey:@"JVMOptions"];
            if (jvmOptionsFixed != nil) {
                for (NSString *option in jvmOptionsFixed) {
                    NSString *jvmOption = [option stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]]; // $APP_ROOTという文字列をバンドルパスに置換する
                    [jvmOptions addObject: jvmOption];
                }
            }

            // ユーザー定義オプションの読み込み
            NSString *bundleName = [[mainBundle infoDictionary] objectForKey:@"CFBundleName"];
            NSString *userJvmOptionPath = [NSHomeDirectory() stringByAppendingPathComponent: [NSString stringWithFormat:@"/Library/%@/jvm_options", bundleName]];
            NSLog(@"use define jvmoptions path=%@", userJvmOptionPath);
            if ([fileManager fileExistsAtPath: userJvmOptionPath]) {
                NSString* fileContents = [NSString stringWithContentsOfFile:userJvmOptionPath
                                                                   encoding:NSUTF8StringEncoding error:nil];
                NSArray* allLinedStrings = [fileContents componentsSeparatedByCharactersInSet:
                                            [NSCharacterSet newlineCharacterSet]];
                // 同一のキーが指定されている場合はRightMostとなることを期待している。(厳密には仕様に定義されていない。)
                for (id line in allLinedStrings) {
                    NSString *option = [line stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
                    if (![option hasPrefix:@"#"] && [option length] > 0) {
                        option = [option stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
                        NSLog(@"option: %@", option);
                        [jvmOptions addObject: option];
                    }
                }
            }
            
            // Java起動引数の組み立て
            NSMutableArray *launchArgs = [NSMutableArray arrayWithObjects:[@"-Xdock:name=" stringByAppendingString:displayAppName], nil];
            [launchArgs addObjectsFromArray: jvmOptions];
            [launchArgs addObject:@"-jar"];
            [launchArgs addObject:jarPath];
            // javaを起動する
            NSPipe *pipe  = [NSPipe pipe];
            NSTask *javaTask = [[NSTask alloc] init];
            [javaTask setLaunchPath: [jreDir stringByAppendingPathComponent: @"bin/java"]];
            [javaTask setArguments: launchArgs];
            [javaTask setStandardError: pipe];
            
            NSTimeInterval beginTime = [NSDate timeIntervalSinceReferenceDate];
            [javaTask launch];
            
            NSFileHandle *handle = [pipe fileHandleForReading];
            NSData *data = [handle readDataToEndOfFile];
            NSString *javaError = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            if ([javaError length] > 0) {
                NSLog(@"Error Message=%@", javaError);
            }
            
            [javaTask waitUntilExit];
            NSTimeInterval endTime = [NSDate timeIntervalSinceReferenceDate];
            
            exitCode = [javaTask terminationStatus];
            NSLog(@"exitCode=%d", exitCode);
            
            NSTimeInterval span = endTime - beginTime;
            if (span < 5 && exitCode != 0) {
                // Javaの実行直後にエラーで終了している場合は、クラスロードエラーの可能性が高い。
                NSString *title = [mainBundle localizedStringForKey: @"JRELoadError" value:@"JRE Load Error" table:nil];
                NSString *reason = [NSString stringWithFormat:@"%@\n%@", title, javaError];
                [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR reason: reason userInfo:nil] raise];
            }
            
        } @catch(NSException *exception) {
            NSLog(@"error=%@", [exception reason]);
            NSAlert *alert = [[NSAlert alloc] init];
            [alert setAlertStyle:NSCriticalAlertStyle];
            [alert setMessageText:[exception reason]];
            [alert runModal];
            exitCode = 1;
        }
    }
    return exitCode;
}
