//
//  FSAApp.m
//  F-Script Anywhere
//
//  Created by Nicholas Riley on Fri Feb 01 2002.
//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
//

/*

 F-Script Anywhere is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 F-Script Anywhere 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 for more details.

 You should have received a copy of the GNU General Public License
 along with F-Script Anywhere; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#import <PatchCocoa.h>
#import "FSAApp.h"
#import "FSAnywhere.h"

NSString * const PatchBundleIdentifier = @"net.sabi.FScriptAnywhere";

typedef struct {
    OSStatus status;
    NSString * const desc;
} errRec, errList[];

// These are just educated guesses based on the errors I've seen; I haven't seen these documented anywhere!
static errList ERRS = {
    { 5, @"F-Script Anywhere must be installed in a Cocoa application running as the current user.\n\nYou may be attempting to install in a setuid application, which is not supported" },
    { 11, @"F-Script Anywhere cannot be installed in itself.\n\nIf you wish to install F-Script Anywhere in itself, make a copy of the F-Script Anywhere application and install one copy into the other" },
    { smUnExBusErr, @"a bus error occurred.\n\nTry switching to the application first then using F-Script AnywhereÕs dock menu to install" },
    { fnfErr, @"F-Script Anywhere was unable to locate its component to install in the application.  Please try reinstalling F-Script Anywhere" },
    { cfragDupRegistrationErr, @"another running copy of F-Script Anywhere is already installed in the application"},
    { 0, nil }
};

NSMutableDictionary *FSA_errors;

NSString * FSA_descriptionForOSStatus(OSStatus status)
{
    NSString *desc = nil;
    
    if (FSA_errors != nil)
        desc = [FSA_errors objectForKey: [NSNumber numberWithLong: status]];
    
    if (desc != nil)
        return desc;
    
    return [NSString stringWithFormat: @"an error of type %ld occurred", status];
}


@implementation FSAApp

+ (void)initialize;
{
    errRec *rec;

    FSA_errors = [[NSMutableDictionary alloc] init];
    for (rec = &(ERRS[0]) ; rec->status != 0 ; rec++)
        [FSA_errors setObject: rec->desc forKey: [NSNumber numberWithLong: rec->status]];
}

- (void)finishLaunching
{
    // yes, someone could move the framework while the app is running, but they deserve what they get.
    NSFileManager *fileMgr = [NSFileManager defaultManager];
    NSArray *libraryDirectories =
        NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, TRUE);
    NSEnumerator *e = [libraryDirectories objectEnumerator];
    NSString *libPath;
    NSString *frameworkPath;
    BOOL isDirectory;
    BOOL found = NO;

    while ( (libPath = [e nextObject]) != nil) {
        frameworkPath = [libPath stringByAppendingPathComponent: @"Frameworks"];
        if ([fileMgr fileExistsAtPath: [frameworkPath stringByAppendingPathComponent: @"FScript.framework"]
                          isDirectory: &isDirectory] && isDirectory) {
            found = YES;
        }
    }
    [super finishLaunching];
    if (!found) {
        int result = NSRunInformationalAlertPanel(@"F-Script Framework Not Found", @"F-Script Anywhere requires the F-Script framework be installed in a Frameworks directory, such as ~/Library/Frameworks or /Library/Frameworks.\n\nTo download F-Script, please visit its Web site %@.\n\nIf you believe this message is in error, click ÒContinueÓ.", @"Quit", @"Get F-Script", @"Continue", FSA_FScriptURL);
        switch (result) {
            case NSAlertOtherReturn:
                break;
            case NSAlertAlternateReturn:
                [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FSA_FScriptURL]];
            case NSAlertDefaultReturn:
            default:
                [self terminate: self];
                return;
        }
    }
    [[PatchController sharedPatchController] setDelegate: self];
    // In Jaguar 6C115, libPatch no longer delivers process death notifications to the application unless it registers for NSWorkspaceDidTerminateApplicationNotification.  (Process launch notifications are still received.)
    [[[NSWorkspace sharedWorkspace] notificationCenter]
        addObserver: self
           selector: @selector(applicationDidTerminate:)
               name: NSWorkspaceDidTerminateApplicationNotification
             object: nil];
}

// This method causes the selected applications to quit, because the Objective-C runtime in Mac OS X does not support unloading of classes.
- (void)unloadBundles:(id)sender
{
    UInt32	index,count;
    NSArray	*pids;

    
    pids = [[PatchController sharedPatchController] processPIDsForPatchWithIdentifier:PatchBundleIdentifier];
    count = [pids count];
    for (index = 0 ; index < count ; index ++)
    {
        [[PatchController sharedPatchController] messagePatchForProcessWithPID:[[pids objectAtIndex:index] longValue]
                                                                    identifier:PatchBundleIdentifier message:'bye ' data:NULL length:0];

        [[PatchController sharedPatchController] unloadPatchForProcessWithPID:[[pids objectAtIndex:index] longValue]
                                                                   identifier:PatchBundleIdentifier];
    }

}

- (void)installBundleInAppWithPID:(pid_t)pid;
{
    OSStatus err;

    if (pid == -1) return;

    // XXX Switch to application first, resolves crash in Project Builder.
    // XXX Fix window closing problem.
    
    err = [[PatchController sharedPatchController] loadPatchForProcessWithPID:pid identifier:PatchBundleIdentifier
                                                                         path:[NSString stringWithFormat:@"%@/F-Script Anywhere",
                                                                             [[NSBundle mainBundle] resourcePath]]
                                                                     detached:NO];
    if (err == noErr) {
        [[PatchController sharedPatchController] messagePatchForProcessWithPID:pid identifier:PatchBundleIdentifier
                                                                       message:'helo' data:NULL length:0];
        [appList didPatchProcessID: pid];
    }
    else {
        NSBeginAlertSheet(@"Installation failed", @"OK", nil, nil, appListPanel, self, nil, nil, nil,
                          @"F-Script Anywhere was unable to install itself in the selected application (process ID %d), because %@.\n\nThe application may have crashed; restart it if needed.", pid, FSA_descriptionForOSStatus(err));
    }
}

- (IBAction)installBundleInSelectedApp:(id)sender;
{
    [self installBundleInAppWithPID: [appList selectedProcessID]];
}

// from dock menu only!
- (IBAction)installBundleInFrontmostApp:(id)sender;
{
    NSAssert1([sender tag] > 0, @"Unable to determine frontmost application from %@", sender);
    [self installBundleInAppWithPID: (pid_t)[sender tag]];
}

@end

@implementation FSAApp (NSWorkspaceNotifications)

- (void)applicationDidTerminate:(NSNotification *)notification;
{
    NSDictionary *userInfo = [notification userInfo];
    FSALog(@"Process terminated (pid %6d): %@\n",
           [[userInfo objectForKey: @"NSApplicationProcessIdentifier"] intValue],
           [userInfo objectForKey: @"NSApplicationName"]);
    // Don't do anything here, this is a dummy method triggered by a NSWorkspaceDidTerminateApplicationNotification
}

@end

@implementation FSAApp (PatchControllerDelegate)

- (void) notifyProcessLaunch:(pid_t)pid info:(NSDictionary*)info
{
    [appList applicationLaunchedWithProcessID: pid];
}

// this delegate method is never invoked in Mac OS X 10.2 unless I register for NSWorkspaceDidTerminateApplicationNotification
- (void) notifyProcessDeath:(pid_t)pid info:(NSDictionary*)info
{
    FSALog(@"Process exited (pid %6d): %@\n", pid, [info objectForKey: PatchControllerNameKey]);
    [appList applicationQuitWithProcessID: pid];
}

@end