//
//  pithos_macosAppDelegate.m
//  pithos-macos
//
// Copyright 2011-2012 GRNET S.A. All rights reserved.
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the following
// conditions are met:
// 
//   1. Redistributions of source code must retain the above
//      copyright notice, this list of conditions and the following
//      disclaimer.
// 
//   2. Redistributions in binary form must reproduce the above
//      copyright notice, this list of conditions and the following
//      disclaimer in the documentation and/or other materials
//      provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// 
// The views and conclusions contained in the software and
// documentation are those of the authors and should not be
// interpreted as representing official policies, either expressed
// or implied, of GRNET S.A.

#import "pithos_macosAppDelegate.h"
#import "PithosAccount.h"
#import "PithosBrowserController.h"
#import "PithosPreferencesController.h"
#import "PithosSyncDaemon.h"
#import "ASIPithosRequest.h"
#import "ASIPithos.h"
#import "ASIDownloadCache.h"
#import "LastCompletedSyncTransformer.h"

@implementation pithos_macosAppDelegate
@synthesize pithosBrowserController, alwaysNo, aboutVersion, activated, currentPithosAccount, pithosAccounts, pithosAccountsDictionary, syncPithosAccount;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self 
                                                       andSelector:@selector(handleAppleEvent:withReplyEvent:) 
                                                     forEventClass:kInternetEventClass 
                                                        andEventID:kAEGetURL];
    
    userDefaults = [[NSUserDefaults standardUserDefaults] retain];

    NSString *stringURL = [userDefaults stringForKey:@"aboutURL"];
    NSURL *testURL = (stringURL) ? [NSURL URLWithString:stringURL] : nil;
    if (!testURL || !testURL.scheme || !testURL.host) {
        [userDefaults setObject:@"https://pithos.dev.grnet.gr/docs/pithos" forKey:@"aboutURL"];
        [userDefaults synchronize];
    }
    
    syncTimeInterval = [userDefaults doubleForKey:@"syncTimeInterval"];
    if (syncTimeInterval <= 0.0) {
        syncTimeInterval = 180.0;
        [userDefaults setDouble:syncTimeInterval forKey:@"syncTimeInterval"];
        [userDefaults synchronize];
    }
    
    NSData *tmpData = [userDefaults objectForKey:@"pithosAccounts"];
    NSArray *tmpArray;
    if (tmpData && (tmpArray = [NSKeyedUnarchiver unarchiveObjectWithData:tmpData]))
        self.pithosAccounts = [NSMutableArray arrayWithArray:tmpArray];
    else
        self.pithosAccounts = [NSMutableArray array];
    
    if (![pithosAccounts count]) {
        [pithosAccounts addObject:[PithosAccount pithosAccount]];
        self.pithosAccounts = self.pithosAccounts;
    } else {
        self.activated = YES;
    }
    
    pithosAccountsDictionary = [[NSMutableDictionary alloc] initWithCapacity:[pithosAccounts count]];
    for (PithosAccount *pithosAccount in pithosAccounts) {
        [pithosAccountsDictionary setObject:pithosAccount forKey:pithosAccount.name];
        if (!currentPithosAccount && pithosAccount.active)
            currentPithosAccount = [pithosAccount retain];
    }
    if (!currentPithosAccount)
        self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
    
    if (currentPithosAccount.active) {
        [self savePithosAccounts:self];
        [self showPithosBrowser:self];
        pithosBrowserController.pithos = currentPithosAccount.pithos;
    } else {
        // XXX maybe call specifically to go to new account tab
        [self showPithosPreferences:self];
    }

    syncTimer = [[NSTimer scheduledTimerWithTimeInterval:syncTimeInterval 
                                                  target:self 
                                                selector:@selector(sync) 
                                                userInfo:nil 
                                                 repeats:YES] retain];
    [syncTimer fire];
}

// Based on: http://cocoatutorial.grapewave.com/2010/01/creating-a-status-bar-application/
// and: http://www.cocoadev.com/index.pl?ThumbnailImages
- (void)awakeFromNib {
    NSImage *sourceImage = [NSImage imageNamed:@"pithos-large.png"];
    
    NSImage *smallImage = [[[NSImage alloc] initWithSize:NSMakeSize(18, 18)] autorelease];
    [smallImage lockFocus];
    [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
    [sourceImage setSize:NSMakeSize(18, 18)];
    [sourceImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy];
    [smallImage unlockFocus];
    
    statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
    [statusItem setMenu:statusMenu];
    [statusItem setImage:sourceImage];
    [statusItem setHighlightMode:YES];
    
    self.alwaysNo = NO;
}

- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent {
    NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
    NSString *host = [url host];
	NSString *query = [url query];
    PithosAccount *pithosAccount = [pithosAccountsDictionary objectForKey:[url lastPathComponent]];
    NSProcessInfo *processInfo = [NSProcessInfo processInfo];
    if ([host isEqualToString:[NSString stringWithFormat:@"%@_%d", [processInfo processName], [processInfo processIdentifier]]] && 
        pithosAccount && query) {
        // user=
        NSString *authUser;
        NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
        if (userRange.length == 0)
            // XXX maybe show an error message?
            return;
        NSUInteger authUserStartLocation = userRange.location + userRange.length;
        NSRange userEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
                                              range:NSMakeRange(authUserStartLocation, [query length] - authUserStartLocation)];
        if (userEndRange.length) {
            authUser = [[query substringWithRange:NSMakeRange(authUserStartLocation, userEndRange.location - authUserStartLocation)]
                        stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        } else {
            authUser = [[query substringFromIndex:authUserStartLocation]
                        stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        }
        // token=
        NSString *authToken;
        NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
        if (tokenRange.length == 0)
            // XXX maybe show an error message?
            return;
        NSUInteger authTokenStartLocation = tokenRange.location + tokenRange.length;
        NSRange tokenEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
                                              range:NSMakeRange(authTokenStartLocation, [query length] - authTokenStartLocation)];
        if (tokenEndRange.length) {
            authToken = [[query substringWithRange:NSMakeRange(authTokenStartLocation, tokenEndRange.location - authTokenStartLocation)]
                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        } else {
            authToken = [[query substringFromIndex:authTokenStartLocation]
                         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        }
        
        NSLog(@"query authUser: '%@', authToken: '%@'", authUser, authToken);
        if ([authUser length] && [authToken length]) {
            [pithosAccount authenticateWithServerURL:nil authUser:authUser authToken:authToken];
            [self savePithosAccounts:self];
            if (pithosPreferencesController && [pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount]) {
                pithosPreferencesController.authUser = pithosAccount.authUser;
                pithosPreferencesController.authToken = pithosAccount.authToken;
            }
            self.activated = YES;
            if ([pithosAccount isEqualTo:currentPithosAccount]) {
                [self showPithosBrowser:self];
                pithosBrowserController.pithos = pithosAccount.pithos;
            }
        }
        // XXX else maybe show an error message?
    }
    // XXX else maybe show an error message?
}

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
    [self savePithosAccounts:self];
    if ([self.pithosBrowserController operationsPending]) {
        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
        [alert setMessageText:@"Pending Operations"];
        [alert setInformativeText:@"There are pending operations in the browser, do you want to quit and cancel them?"];
        [alert addButtonWithTitle:@"OK"];
        [alert addButtonWithTitle:@"Cancel"];
        NSInteger choice = [alert runModal];
        if (choice == NSAlertSecondButtonReturn) 
            return NSTerminateCancel;
    }
    return NSTerminateNow;
}

#pragma mark -
#pragma mark Properties

- (NSString *)aboutVersion {
    return [NSString stringWithFormat:@"About Pithos+ %@ (%@)", 
            [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"], 
            [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]];
}

#pragma mark -
#pragma mark NSMenuDelegate

- (void)menuNeedsUpdate:(NSMenu *)menu {
    NSMenuItem *menuItem;
    [menu removeAllItems];
    if ([menu isEqualTo:accountsMenu]) {
        [menu setAutoenablesItems:NO];
        for (PithosAccount *pithosAccount in pithosAccounts) {
            menuItem = [[[NSMenuItem alloc] initWithTitle:pithosAccount.name 
                                                   action:@selector(menuChangePithosAccount:) 
                                            keyEquivalent:@""] autorelease];
            [menuItem setRepresentedObject:pithosAccount];
            [menuItem setEnabled:pithosAccount.active];
            [menuItem setState:((pithosAccount.active && [currentPithosAccount isEqualTo:pithosAccount]) ? NSOnState : NSOffState)];
            [menu addItem:menuItem];
        }
    } else if ([menu isEqualTo:lastSyncMenu]) {
        NSString *menuItemTitle;
        [menu setAutoenablesItems:NO];
        for (PithosAccount *pithosAccount in pithosAccounts) {
            menuItemTitle = [NSString stringWithFormat:@"%@: %@", 
                             pithosAccount.name, 
                             [[[[LastCompletedSyncTransformer alloc] init] autorelease] transformedValue:pithosAccount.syncLastCompleted]];
            if ([pithosAccount isEqualTo:syncPithosAccount] && [pithosAccount.syncDaemon isSyncing])
                menuItemTitle = [menuItemTitle stringByAppendingString:@" (syncing)"];
            menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle 
                                                   action:@selector(menuChangeSyncActive:) 
                                            keyEquivalent:@""] autorelease];
            [menuItem setRepresentedObject:pithosAccount];
            [menuItem setEnabled:pithosAccount.active];
            [menuItem setState:((pithosAccount.active && pithosAccount.syncActive) ? NSOnState : NSOffState)];
            [menu addItem:menuItem];
        }
        [menu addItem:[NSMenuItem separatorItem]];
        [menu addItem:[[[NSMenuItem alloc] initWithTitle:@"Next Sync" 
                                                  action:@selector(sync) 
                                           keyEquivalent:@""] autorelease]];
    }
}

#pragma mark -
#pragma mark Actions

- (IBAction)showPithosBrowser:(id)sender {
    if (!activated)
        return;
    [pithosBrowserController showWindow:sender];
    [[pithosBrowserController window] makeKeyAndOrderFront:sender];
    [NSApp activateIgnoringOtherApps:YES];
}

- (IBAction)showPithosPreferences:(id)sender {
    [pithosPreferencesController showWindow:sender];
    [[pithosPreferencesController window] makeKeyAndOrderFront:sender];
    [NSApp activateIgnoringOtherApps:YES];
}

- (IBAction)aboutPithos:(id)sender {
    [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[userDefaults stringForKey:@"aboutURL"]]];
}

- (void)sync {
    if (!activated || ![pithosAccounts count])
        return;
    NSUInteger syncIndex;
    BOOL syncPithosAccountFound = [pithosAccounts containsObject:syncPithosAccount];
    if (syncPithosAccountFound)
         syncIndex = [pithosAccounts indexOfObject:syncPithosAccount];
    
    PithosAccount *singleSyncPithosAccount = nil;
    for (PithosAccount *pithosAccount in pithosAccounts) {
        if (!singleSyncPithosAccount && pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
            singleSyncPithosAccount = pithosAccount;
        } else if (singleSyncPithosAccount && pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
            singleSyncPithosAccount = nil;
            break;
        }
    }
    
    if (syncPithosAccount && syncPithosAccount.active && syncPithosAccount.syncActive && syncPithosAccount.syncDaemon) {
        // An active syncDaemon was previously syncing
        if (singleSyncPithosAccount && [singleSyncPithosAccount isEqualTo:syncPithosAccount]) {
            // It's the only one, sync again
            [syncPithosAccount.syncDaemon startDaemon];
            [syncPithosAccount.syncDaemon sync];
            return;
        } else if ([syncPithosAccount.syncDaemon isSyncing]) {
            // It's still syncing, mark it as late and return
            [syncPithosAccount.syncDaemon syncLate];
            return;
        }
    }
    PithosAccount *newSyncPithosAccount = nil;
    if (syncPithosAccountFound) {
        for (PithosAccount *pithosAccount in [pithosAccounts objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(syncIndex + 1, [pithosAccounts count] - syncIndex - 1)]]) {
            if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
                newSyncPithosAccount = pithosAccount;
                break;
            }
        }
        if (!newSyncPithosAccount) {
            for (PithosAccount *pithosAccount in [pithosAccounts objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, syncIndex)]]) {
                if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
                    newSyncPithosAccount = pithosAccount;
                    break;
                }
            }
        }
    } else {
        for (PithosAccount *pithosAccount in pithosAccounts) {
            if (pithosAccount.active && pithosAccount.syncActive && pithosAccount.syncDaemon) {
                newSyncPithosAccount = pithosAccount;
                break;
            }
        }
    }        
    if (newSyncPithosAccount) {
        // A different syncDaemon is found, sync it
        self.syncPithosAccount = newSyncPithosAccount;
        [syncPithosAccount.syncDaemon startDaemon];
        [syncPithosAccount.syncDaemon sync];
    } else if (syncPithosAccountFound && syncPithosAccount && syncPithosAccount.active && syncPithosAccount.syncActive && syncPithosAccount.syncDaemon) {
        [syncPithosAccount.syncDaemon startDaemon];
        [syncPithosAccount.syncDaemon sync];
    } else {
        self.syncPithosAccount = nil;
    }
}

- (void)savePithosAccounts:(id)sender {
    [userDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:pithosAccounts] forKey:@"pithosAccounts"];
    [userDefaults synchronize];
}

- (void)removedPithosAccount:(PithosAccount *)removedPithosAccount {
    if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
        for (PithosAccount *pithosAccount in pithosAccounts) {
            if (pithosAccount.active) {
                self.currentPithosAccount = pithosAccount;
                pithosBrowserController.pithos = currentPithosAccount.pithos;
                break;
            }
        }
        if ([self.currentPithosAccount isEqualTo:removedPithosAccount]) {
            self.activated = NO;
            [pithosBrowserController.window close];
            [pithosBrowserController resetBrowser];
            self.currentPithosAccount = [pithosAccounts objectAtIndex:0];
        }
    }
    if ([self.syncPithosAccount isEqualTo:removedPithosAccount])
        self.syncPithosAccount = nil;
}

#pragma mark -
#pragma mark Menu Actions

- (void)menuChangePithosAccount:(NSMenuItem *)sender {
    PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
    if (!pithosAccount.active)
        return;
    if (![currentPithosAccount isEqualTo:pithosAccount] && [pithosAccounts containsObject:pithosAccount]) {
        if ([self.pithosBrowserController operationsPending]) {
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
            [alert setMessageText:@"Pending Operations"];
            [alert setInformativeText:@"There are pending operations in the browser, do you want to change accounts and cancel them?"];
            [alert addButtonWithTitle:@"OK"];
            [alert addButtonWithTitle:@"Cancel"];
            NSInteger choice = [alert runModal];
            if (choice == NSAlertSecondButtonReturn) 
                return;
        }
        self.currentPithosAccount = pithosAccount;
        [self showPithosBrowser:self];
        pithosBrowserController.pithos = currentPithosAccount.pithos;
    }
}

- (void)menuChangeSyncActive:(NSMenuItem *)sender {
    PithosAccount *pithosAccount = (PithosAccount *)[sender representedObject];
    if (!pithosAccount.active)
        return;
    pithosAccount.syncActive = !pithosAccount.syncActive;
    if (pithosPreferencesController && [pithosPreferencesController.selectedPithosAccount isEqualTo:pithosAccount])
        pithosPreferencesController.syncActive = pithosAccount.syncActive;
    [self savePithosAccounts:self];
}

@end
