#import "AqCoreController.h"
#import "AqCore(Dispatch).h"
#import "AqDownloadsController.h"
#import "AqLibraryController.h"
#import "AqNetworkController.h"
#import "AqUploadsController.h"
#import "DWUtil.h"
#import "NSFileHandleExtensions.h"
#import "NSStringExtensions.h"
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>


@implementation AqCoreController

/* Instance */

    + (id) instance; {
        static id instance = nil;
        if (!instance) instance = [[AqCoreController alloc] init];
        return instance;
    }


/* Initialization */
    
    - (id) init; 
    {
        /* alloc */

            self = [super init];
            l1 = [[NSLock alloc] init];
            l2 = [[NSLock alloc] init];
        
        /* lanch JVM process */
            
            #if 1
            if ([self isOS102OrHigher]) {
                [self launchCore];
            }
        
        /* thread */

            [NSThread detachNewThreadSelector: @selector(incomingPipeThread:) toTarget: self withObject: nil];
            #endif

        return self;
    }


#pragma mark -
/* Java */

    #define fm [NSFileManager defaultManager]
    #define fileSize(A) [[[fm fileAttributesAtPath: A traverseLink: NO] objectForKey: NSFileSize] intValue]

    #define kAqJava141Path @"/System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Commands/java"
    #define kAqJava142Path @"/System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Commands/java"
    #define kAqClass141Path @"/System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Classes/classes.jar"
    #define kAqClass142Path @"/System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Classes/classes.jar"
    
    #define AqJavaResource(A) [NSString stringWithFormat: @"Java/%@.jar", A]
    #define AqJavaResourceFullPath(A) [[NSBundle mainBundle] pathForResource: A ofType: @"jar" inDirectory: @"Java"]
    #define AqResourceFullPath(A) [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"Contents/Resources/English.lproj"] stringByAppendingPathComponent: A]
        
    + (BOOL) hasJava141; {
        return ([[NSFileManager defaultManager] fileExistsAtPath: kAqJava141Path] && 
                [[NSFileManager defaultManager] fileExistsAtPath: kAqClass141Path]);
    }

    + (BOOL) hasJava142; {
        return ([[NSFileManager defaultManager] fileExistsAtPath: kAqJava142Path] && 
                [[NSFileManager defaultManager] fileExistsAtPath: kAqClass142Path]);
    }

    + (BOOL) coreFilesAreValid; {
        if (![fm fileExistsAtPath: AqJavaResourceFullPath(@"AcquisitionCore")]) return NO;
        if (![fm fileExistsAtPath: AqJavaResourceFullPath(@"xerces")])		return NO;
        if (![fm fileExistsAtPath: AqJavaResourceFullPath(@"logicrypto")]) 	return NO;
        #if 0
        if (fileSize(AqJavaResourceFullPath(@"AcquisitionCore")) != 27667)              return NO;
        if (fileSize(AqJavaResourceFullPath(@"xerces")) != 1812019)			return NO;
        if (fileSize(AqResourceFullPath(@"Preferences.nib/classes.nib")) != 5658) 	return NO;
        if (fileSize(AqResourceFullPath(@"Preferences.nib/info.nib")) != 1121) 		return NO;
        if (fileSize(AqResourceFullPath(@"Preferences.nib/objects.nib")) != 25989) 	return NO;
        #endif
        return YES;
    }

    - (void) launchCore; 
    {
        if (ncrashes++ > 20) return;
        
        NS_DURING
        
        /* adjust process resources (inherited by children) */
        
            struct rlimit rlp;
            rlp.rlim_cur = 1024;
            rlp.rlim_max = 1024;
            setrlimit(RLIMIT_NOFILE, &rlp);

            /*
            getrlimit(RLIMIT_NOFILE, &rlp);
            NSLog(@"limit: %i, %i", rlp.rlim_cur, rlp.rlim_max);
            */

        /* launch java */

            java      = [[NSTask alloc] init];
            readPipe  = [[NSPipe alloc] init];
            writePipe = [[NSPipe alloc] init];
            
            id javaVersion = ([self isOS103OrHigher]) ? @"1.4.2" : @"1.4.1";
            id javaPath = [NSString stringWithFormat: @"/System/Library/Frameworks/JavaVM.framework/Versions/%@/Commands/java", javaVersion];
            id classPath = [NSString stringWithFormat: @"/System/Library/Frameworks/JavaVM.framework/Versions/%@/Classes/classes.jar", javaVersion];
            
            [java setLaunchPath: javaPath];
            [java setCurrentDirectoryPath: [[NSBundle mainBundle] resourcePath]];
            [java setArguments: 
                [NSArray arrayWithObjects: @"-cp", 
                [[NSArray arrayWithObjects:
                    classPath,
                    //@"/System/Library/Java/",
                    AqJavaResource(@"AcquisitionCore"), 
                    AqJavaResource(@"xerces"), 
                    AqJavaResource(@"logicrypto"), 
                nil] componentsJoinedByString: @":"],
                @"-showversion",
                @"-Dfile.encoding=UTF-8",
                //@"-Xprof",
                //@"-Xrunhprof",
                //@"-verbose:class",
                //@"-verbose:gc",
                //@"-verbose:jni",
                /*
                @"-Xdebug",
                @"-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n", 
                */
                @"AqMain", 
                nil]
            ];
            
            //NSLog(@"%@", [[java arguments] componentsJoinedByString: @" "]);
            
            [java setStandardInput: writePipe];
            [java setStandardOutput: readPipe];
            
            writer = [writePipe fileHandleForWriting];
            reader = [readPipe  fileHandleForReading];
            
            [self listenForNotification: NSTaskDidTerminateNotification selector: @selector(taskDidTerminate:) object: java];
            
            [java launch];
                    
        /* notification */
        
            [self postNotificationAboutSelfLater: @"AqCoreControllerInitComplete"];

        /* renice java (lower priority) */
            
            #define kAqRenicePath @"/usr/bin/renice"

            if ([[NSFileManager defaultManager] fileExistsAtPath: kAqRenicePath]) 
            {
                NSTask* renice = [[[NSTask alloc] init] autorelease];
                [renice setLaunchPath: kAqRenicePath];
                
                [renice setArguments: 
                    [NSArray arrayWithObjects: 
                        @"20", 
                        [NSString stringWithFormat: @"%i", [java processIdentifier]], 
                    nil]
                ];
                
                [renice launch];
            }
            
        NS_HANDLER
        
            NSLog(@"Fatal error during java initialization. Giving up.");
            NSLog(@"%@", localException);
        
        NS_ENDHANDLER
    }
    
    - (void) terminateCore; {
        //applicationIsTerminating = YES;
        //kill(SIGTERM, [java processIdentifier]);
    }
    
    - (void) taskDidTerminate: (id) notification; 
    {
        [l2 lock];
        NSLog(@"AqCoreController: taskDidTerminate (java core crash)");
        
        /* release local objects */
            
            [writePipe release];
            [readPipe release];
            writePipe = nil;
            readPipe = nil;
    
            [writer closeFile];
            [reader closeFile];
            
            [writer release];
            [reader release];
            
            writer = nil;
            reader = nil;
    
            java = nil;
            
        /* release global objects */
        
            [[AqNetworkController   instance] removeAllObjects];
            [[AqUploadsController   instance] removeAllObjects];
            [[AqDownloadsController instance] removeAllObjects];
            [[AqQueryController     instance] coreDidTerminate];
            
            int i, count = [AqQC count];
            for (i=0; i<count; i++) {
                [[AqQC queryAtIndex: i] removeAllObjects];
            }
            
            [[AqDrawerController instance] resetAllFileCounts];

        /* relaunch java */
        
            if (!applicationIsTerminating)
                [self launchCore];
            
        [l2 unlock];
    }
    
    
#pragma mark -
/* Outgoing */

    + (void) writeString: (id) theString; {
        [[AqCoreController instance] writeString: theString];
        
    }

    - (void) writeString: (id) theString; {
        [l1 lock];
        NS_DURING
            [writer writeString: theString];
        NS_HANDLER
            NSLog(@"%@", localException);
        NS_ENDHANDLER
        [l1 unlock];
    }


#pragma mark -
/* Incoming */

    - (void) incomingPipeThread: (id) context; 
    {
        NSMutableString* chunk = nil;
        NSData* data = nil;

        while (1) 
        {
            NSAutoreleasePool* p = [[NSAutoreleasePool alloc] init];
            if (ncrashes > 20) break;
            
            [l2 lock];
            
            NS_DURING
                
                if (!chunk) chunk = [[NSMutableString alloc] init];
                
                data = [reader availableData]; /* blocking */
                [chunk appendString: [NSString stringWithData: data]];
                
                if ([chunk hasSuffix: @"\n"]) 
                {
                    NSArray* lines = [chunk componentsSeparatedByString: @"\n"];
                    
                    int i, count = [lines count];
                    
                    for (i=0; i<count; i++) 
                    {
                        NSString* line = [lines objectAtIndex: i];
                        if (![line length]) continue;
                        
                        /*
                        line = [NSMutableString stringWithString: line];
                        [line replaceOccurrencesOfString: @"&apos;" withString: @"'" options: range: (NSRange){0,[line length]}];
                        */
                        
                        id frags = [line componentsSeparatedByString: @"<aq/>"];
                        [self performSelectorOnMainThread: @selector(dispatchCommand:) withObject: frags waitUntilDone: NO];
                    }
                    
                    [chunk release];
                    chunk = nil;
                }
            
            NS_HANDLER
                
                NSLog(@"%@", localException);
            
            NS_ENDHANDLER

            [l2 unlock];
            [p release];
        }
    }

@end