//
//  PithosPreferencesController.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 "PithosPreferencesController.h"
#import "PithosBrowserController.h"
#import "PithosAccountNode.h"
#import "PithosContainerNode.h"
#import "PithosSubdirNode.h"
#import "PithosObjectNode.h"
#import "PithosEmptyNode.h"
#import "PithosAccount.h"
#import "pithos_macosAppDelegate.h"

#import "ImageAndTextCell.h"
@interface PithosPreferencesSyncOutlineViewCell : ImageAndTextCell {}
@end

@implementation PithosPreferencesSyncOutlineViewCell

- (void)setObjectValue:(id)object {
    if ([object isKindOfClass:[PithosNode class]]) {
        PithosNode *node = (PithosNode *)object;
        [self setStringValue:node.displayName];
        [self setImage:node.icon];
        [self setEditable:NO];
    } else {
        [super setObjectValue:object];
    }
}

@end

@implementation PithosPreferencesController
@synthesize selectedPithosAccount;
@synthesize accountsArrayController;
@synthesize accountRemoveEnable;
@synthesize serverURL, authUser, authToken, manual, loginEnable, loginCancelEnable;
@synthesize syncActive, syncDirectoryPath, syncContainersDictionary, syncApplyEnable, syncCancelEnable, 
            syncContainersOutlineView, syncContainersRootFilesNodes;
@synthesize groupsDictionaryController, selectedGroupMembersDictionaryController;

#pragma mark -
#pragma mark Object Lifecycle

- (id)init {
    return [super initWithWindowNibName:@"PithosPreferencesController"];
}

- (void)windowDidLoad {
    [super windowDidLoad];
    
    NSWindow *window = [self window];
    [window setHidesOnDeactivate:NO];
    [window setExcludedFromWindowsMenu:YES];
    
//	// Select the first tab when the window is loaded for the first time.
//	[[window valueForKeyPath:@"toolbar"] setSelectedItemIdentifier:@"0"];
    
    [[[syncContainersOutlineView tableColumns] objectAtIndex:1] setDataCell:[[[PithosPreferencesSyncOutlineViewCell alloc] init] autorelease]];
    
    [groupsDictionaryController setInitialKey:@"group"];
    [groupsDictionaryController setInitialValue:@"user"];
    [selectedGroupMembersDictionaryController setInitialKey:@"user"];
    [selectedGroupMembersDictionaryController setInitialValue:@""];

    [window setDelegate:self];
    
    self.selectedPithosAccount = [[accountsArrayController selectedObjects] objectAtIndex:0];
    [accountsArrayController addObserver:self forKeyPath:@"selection" options:NSKeyValueObservingOptionNew context:NULL];
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(selectedPithosAccountNodeChildrenUpdated:) 
                                                 name:@"SelectedPithosAccountNodeChildrenUpdated" 
                                               object:nil];
}

- (BOOL)windowShouldClose:(id)sender {
    return [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] activated];
}

//- (void)windowWillClose:(NSNotification *)notification {
//}

//- (IBAction)toolbarItemSelected:(id)sender {
//}

#pragma mark -
#pragma Observers

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([object isEqualTo:accountsArrayController] && 
        [keyPath isEqualToString:@"selection"] && 
        [[accountsArrayController selectedObjects] count]) {
        self.selectedPithosAccount = [[accountsArrayController selectedObjects] objectAtIndex:0];
    }
}

- (void)selectedPithosAccountNodeChildrenUpdated:(NSNotification *)notification {
    [syncContainersOutlineView reloadData];
    [syncContainersOutlineView expandItem:nil expandChildren:YES];
}

#pragma mark -
#pragma Update

- (void)updateAccounts {
    pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
    self.accountRemoveEnable = (delegate.activated && ([delegate.pithosAccounts count] > 1));
}

- (void)updateLogin {
    self.loginEnable = ([selectedPithosAccount urlIsValid:serverURL] && (!manual || ([authUser length] && [authToken length])));
    self.loginCancelEnable = (![selectedPithosAccount.serverURL isEqualToString:serverURL] || 
                              (selectedPithosAccount.authUser && ![selectedPithosAccount.authUser isEqualToString:authUser]) || 
                              (selectedPithosAccount.authToken && ![selectedPithosAccount.authToken isEqualToString:authToken]));
}

- (void)updateSync {
    BOOL isDirectory;
    self.syncApplyEnable = (selectedPithosAccount.active && 
                            ((selectedPithosAccount.syncActive != syncActive) || 
                             (![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] && 
                              (![[NSFileManager defaultManager] fileExistsAtPath:syncDirectoryPath isDirectory:&isDirectory] || 
                               isDirectory)) ||
                             ![selectedPithosAccount.syncContainersDictionary isEqualToDictionary:syncContainersDictionary]));
    self.syncCancelEnable = (selectedPithosAccount.active && 
                             ((selectedPithosAccount.syncActive != syncActive) || 
                              ![selectedPithosAccount.syncDirectoryPath isEqualToString:syncDirectoryPath] ||
                              ![selectedPithosAccount.syncContainersDictionary isEqualToDictionary:syncContainersDictionary]));
}

#pragma mark -
#pragma Properties

- (void)setSelectedPithosAccount:(PithosAccount *)aSelectedPithosAccount {
    if (aSelectedPithosAccount && ![aSelectedPithosAccount isEqualTo:selectedPithosAccount]) {
        selectedPithosAccount.accountNode.childrenUpdatedNotificationName = nil;
        [selectedPithosAccount release];
        selectedPithosAccount = [aSelectedPithosAccount retain];
        selectedPithosAccount.accountNode.childrenUpdatedNotificationName = [NSString stringWithString:@"SelectedPithosAccountNodeChildrenUpdated"];
        
        [self updateAccounts];
        [self loginCancel:self];
        [self syncCancel:self];
        [self groupsRevert:self];
    }
}

#pragma Login Properties

- (void)setServerURL:(NSString *)aServerURL {
    [serverURL release];
    serverURL = [aServerURL copy];
    [self updateLogin];
}

- (void)setAuthUser:(NSString *)anAuthUser {
    [authUser release];
    authUser = [anAuthUser copy];
    [self updateLogin];
}

- (void)setAuthToken:(NSString *)anAuthToken {
    [authToken release];
    authToken = [anAuthToken copy];
    [self updateLogin];
}

- (void)setManual:(BOOL)aManual {
    manual = aManual;
    [self updateLogin];
    if (!manual) {
        self.authUser = selectedPithosAccount.authUser;
        self.authToken = selectedPithosAccount.authToken;
    }
}

#pragma Sync Properties

- (void)setSyncActive:(BOOL)aSyncActive {
    syncActive = aSyncActive;
    [self updateSync];
}

- (void)setSyncDirectoryPath:(NSString *)aSyncDirectoryPath {
    [syncDirectoryPath release];
    syncDirectoryPath = [aSyncDirectoryPath copy];
    [self updateSync];
}

- (void)setSyncContainersDictionary:(NSMutableDictionary *)aSyncContainersDictionary {
    [syncContainersDictionary release];
    syncContainersDictionary = [[NSMutableDictionary alloc] initWithCapacity:[aSyncContainersDictionary count]];
    for (NSString *containerName in aSyncContainersDictionary) {
        [syncContainersDictionary setObject:[NSMutableArray arrayWithArray:[aSyncContainersDictionary objectForKey:containerName]] 
                                                                    forKey:containerName];
    }
    [self updateSync];
}

#pragma mark -
#pragma Actions

- (IBAction)addAccount:(id)sender {
    [accountsArrayController addObject:[PithosAccount pithosAccount]];
    [self updateAccounts];
    pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
    [delegate.pithosAccountsDictionary setObject:selectedPithosAccount forKey:selectedPithosAccount.name];
    [delegate savePithosAccounts:self];
}

- (IBAction)removeAccount:(id)sender {
    [self updateAccounts];
    if (!accountRemoveEnable)
        return;
    PithosAccount *removedPithosAccount = [selectedPithosAccount retain];
    pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
    if ([delegate.currentPithosAccount isEqualTo:removedPithosAccount] && [delegate.pithosBrowserController operationsPending]) {
        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
        [alert setMessageText:@"Operations Pending"];
        [alert setInformativeText:@"There are pending operations in the browser, do you want to remove the account and cancel them?"];
        [alert addButtonWithTitle:@"OK"];
        [alert addButtonWithTitle:@"Cancel"];
        NSInteger choice = [alert runModal];
        if (choice == NSAlertSecondButtonReturn) {
            [removedPithosAccount release];
            return;
        }
    }
    [accountsArrayController removeObject:selectedPithosAccount];
    [delegate.pithosAccountsDictionary removeObjectForKey:removedPithosAccount.name];
    [delegate removedPithosAccount:removedPithosAccount];
    [delegate savePithosAccounts:self];
    [removedPithosAccount release];
    [self updateAccounts];
}

#pragma Login Actions

- (IBAction)login:(id)sender {
    if (!manual) {
        [selectedPithosAccount loginWithServerURL:serverURL];
    } else {
        [selectedPithosAccount authenticateWithServerURL:serverURL authUser:authUser authToken:authToken];
        self.manual = NO;
        pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
        [delegate savePithosAccounts:self];
        if (!delegate.activated) {
            delegate.activated = YES;
            [delegate showPithosBrowser:self];
        }
        if ([selectedPithosAccount isEqualTo:delegate.currentPithosAccount])
            delegate.pithosBrowserController.pithos = selectedPithosAccount.pithos;
    }
}

- (IBAction)loginCancel:(id)server {
    self.serverURL = selectedPithosAccount.serverURL;
    self.authUser = selectedPithosAccount.authUser;
    self.authToken = selectedPithosAccount.authToken;
}

#pragma Sync Actions

- (IBAction)syncApply:(id)sender {
    [selectedPithosAccount updateSyncWithSyncActive:syncActive 
                                  syncDirectoryPath:syncDirectoryPath 
                           syncContainersDictionary:syncContainersDictionary];
    [self updateSync];
    pithos_macosAppDelegate *delegate = (pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate];
    [delegate savePithosAccounts:self];
    [delegate sync];
}

- (IBAction)syncCancel:(id)sender {
    self.syncActive = selectedPithosAccount.syncActive;
    self.syncDirectoryPath = selectedPithosAccount.syncDirectoryPath;
    self.syncContainersDictionary = selectedPithosAccount.syncContainersDictionary;
    self.syncContainersRootFilesNodes = [NSMutableDictionary dictionary];
    [self selectedPithosAccountNodeChildrenUpdated:nil];
}

- (IBAction)syncRefresh:(id)sender {
    selectedPithosAccount.accountNode.forcedRefresh = YES;
    [selectedPithosAccount.accountNode invalidateChildrenRecursive];
    if (selectedPithosAccount.accountNode.children) {
    }
}

#pragma mark Groups Actions

- (IBAction)groupsApply:(id)sender {
    [[self window] makeFirstResponder:nil];
    if (selectedPithosAccount.active)
        [selectedPithosAccount.accountNode applyInfo];
}

- (IBAction)groupsRevert:(id)sender {
    if (selectedPithosAccount.active && selectedPithosAccount.accountNode)
        [selectedPithosAccount.accountNode refreshInfo];
}

#pragma mark -
#pragma mark NSOutlineViewDataSource

- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
    if (!selectedPithosAccount.active)
        return 0;
    if (outlineView == syncContainersOutlineView) {
        if (item == nil) {
            if (selectedPithosAccount.accountNode.children)
                return selectedPithosAccount.accountNode.children.count;
        } else if ([item class] == [PithosContainerNode class]) {
            // We add 1 for the root files node
            NSInteger subdirCount = 1;
            for (PithosNode *node in [(PithosContainerNode *)item children]) {
                if ([node class] == [PithosSubdirNode class])
                    subdirCount++;
            }
            return subdirCount;
        }
    }
    return 0;
}

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
    if (outlineView == syncContainersOutlineView) {
        if (item == nil)
            return [selectedPithosAccount.accountNode.children objectAtIndex:index];
        if ([item class] == [PithosContainerNode class]) {
            PithosContainerNode *containerNode = (PithosContainerNode *)item;
            NSInteger currentSubdirIndex = -1;
            for (PithosNode *node in containerNode.children) {
                if ([node class] == [PithosSubdirNode class]) {
                    currentSubdirIndex++;
                    if (currentSubdirIndex == index)
                        return node;
                }
            }
            if (++currentSubdirIndex == index) {
                PithosEmptyNode *rootFilesNode = [syncContainersRootFilesNodes objectForKey:containerNode.displayName];
                if (!rootFilesNode) {
                    rootFilesNode = [[[PithosEmptyNode alloc] initWithDisplayName:@"<root files>" 
                                                                             icon:[[NSWorkspace sharedWorkspace] iconForFileType:@""]] 
                                     autorelease];
                    rootFilesNode.parent = containerNode;
                    [syncContainersRootFilesNodes setObject:rootFilesNode forKey:containerNode.displayName];
                }
                return rootFilesNode;
            }
        }
    }
    return nil;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
    if (outlineView == syncContainersOutlineView) {
        if ([item class] == [PithosContainerNode class])
            return YES;
    }
    return NO;
}

- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
    if (outlineView == syncContainersOutlineView) {
        if ([[tableColumn identifier] isEqualToString:@"sync"]) {
            if ([item class] == [PithosContainerNode class]) {
                // A container is 
                // off if not in dictionary
                // mixed if in dictionary with exclusions
                // on if in dictionary without exclusions
                PithosContainerNode *node = (PithosContainerNode *)item;
                NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.displayName];
                if (containerExcludedDirectories) {
                    if ([containerExcludedDirectories count])
                        return [NSNumber numberWithUnsignedInteger:NSMixedState];
                    else
                        return [NSNumber numberWithUnsignedInteger:NSOnState];
                }
                return [NSNumber numberWithUnsignedInteger:NSOffState];
            } else if ([item class] == [PithosSubdirNode class]) {
                // Root files are off if parent container not in dictionary or if excluded
                // displayName should be localized and lowercased
                PithosSubdirNode *node = (PithosSubdirNode *)item;
                NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
                NSString *directoryName = [[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
                if (!containerExcludedDirectories || [containerExcludedDirectories containsObject:directoryName])
                    return [NSNumber numberWithUnsignedInteger:NSOffState];
                else
                    return [NSNumber numberWithUnsignedInteger:NSOnState];
            } else if ([item class] == [PithosEmptyNode class]) {
                // Root files are off if parent container not in dictionary or if excluded
                PithosEmptyNode *node = (PithosEmptyNode *)item;
                NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
                if (!containerExcludedDirectories || [containerExcludedDirectories containsObject:@""])
                    return [NSNumber numberWithUnsignedInteger:NSOffState];
                else
                    return [NSNumber numberWithUnsignedInteger:NSOnState];
            }
            return [NSNumber numberWithUnsignedInteger:NSOffState];
        } else if ([[tableColumn identifier] isEqualToString:@"path"]) {
            return (PithosNode *)item;
        }
    }
    return nil;
}

- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
    if (outlineView == syncContainersOutlineView) {
        if ([[tableColumn identifier] isEqualToString:@"sync"]) {
            NSCellStateValue newState = [object unsignedIntegerValue];
            if ([item class] == [PithosContainerNode class]) {
                // If new state is
                // mixed/on include container with no excluded directories
                // off exclude container
                PithosContainerNode *node = (PithosContainerNode *)item;
                if ((newState == NSOnState) || (newState == NSMixedState)) {
                    [syncContainersDictionary setObject:[NSMutableArray array] forKey:node.displayName];
                } else {
                    [syncContainersDictionary removeObjectForKey:node.displayName];
                }
                [outlineView reloadItem:item reloadChildren:YES];
            } else if ([item class] == [PithosSubdirNode class]) {
                // If new state is
                // mixed/on include directory (if container not included, include and exclude all others)
                // off exclude directory
                PithosSubdirNode *node = (PithosSubdirNode *)item;
                NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
                NSString *directoryName = [[node.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
                if ((newState == NSOnState) || (newState == NSMixedState)) {
                    if (containerExcludedDirectories) {
                        [containerExcludedDirectories removeObject:directoryName];
                    } else {
                        NSMutableArray *newContainerExcludeDirectories = [NSMutableArray arrayWithObject:@""];
                        for (PithosNode *siblingNode in node.parent.children) {
                            if ([siblingNode class] == [PithosSubdirNode class]) {
                                NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
                                if (![siblingDirectoryName isEqualToString:directoryName] && 
                                    ![newContainerExcludeDirectories containsObject:siblingDirectoryName])
                                    [newContainerExcludeDirectories addObject:siblingDirectoryName];
                            }
                        }
                        [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName];
                    }
                } else if (![containerExcludedDirectories containsObject:directoryName]) {
                    [containerExcludedDirectories addObject:directoryName];
                }
                [outlineView reloadItem:[outlineView parentForItem:item]];
            } else if ([item class] == [PithosEmptyNode class]) {
                // If new state is
                // mixed/on include root files (if container not included, include and exclude all others)
                // off exclude root files
                PithosEmptyNode *node = (PithosEmptyNode *)item;
                NSMutableArray *containerExcludedDirectories = [syncContainersDictionary objectForKey:node.parent.displayName];
                if ((newState == NSOnState) || (newState == NSMixedState)) {
                    if (containerExcludedDirectories) {
                        [containerExcludedDirectories removeObject:@""];
                    } else {
                        NSMutableArray *newContainerExcludeDirectories = [NSMutableArray array];
                        for (PithosNode *siblingNode in node.parent.children) {
                            if ([siblingNode class] == [PithosSubdirNode class]) {
                                NSString *siblingDirectoryName = [[siblingNode.displayName lowercaseString] stringByReplacingOccurrencesOfString:@"/" withString:@":"];
                                if (![newContainerExcludeDirectories containsObject:siblingDirectoryName])
                                    [newContainerExcludeDirectories addObject:siblingDirectoryName];
                            }
                        }
                        [syncContainersDictionary setObject:newContainerExcludeDirectories forKey:node.parent.displayName];
                    }
                } else if (![containerExcludedDirectories containsObject:@""]) {
                    [containerExcludedDirectories addObject:@""];
                }
                [outlineView reloadItem:[outlineView parentForItem:item]];
            }
            [self updateSync];
        }
    }
}

@end
