//
//  PithosContainerNode.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 "PithosContainerNode.h"
#import "PithosObjectNode.h"
#import "PithosSubdirNode.h"
#import "ASIPithos.h"
#import "ASIPithosContainerRequest.h"
#import "ASIPithosContainer.h"
#import "ASIPithosObject.h"
#import "ASIDownloadCache.h"
#import "PithosUtilities.h"
#import "PithosContainerNodeInfoController.h"
#import "PithosActivityFacility.h"

static NSImage *sharedIcon = nil;

@implementation PithosContainerNode
@synthesize pithos, pithosContainer, prefix;
@synthesize policyVersioning, policyQuota;

+ (void)initialize {
	if (self == [PithosContainerNode class])
        sharedIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericHardDiskIcon)] retain];
}

#pragma mark -
#pragma mark Object Lifecycle

- (id)initWithPithos:(ASIPithos *)aPithos pithosContainer:(ASIPithosContainer *)aPithosContainer icon:(NSImage *)anIcon {
    if ((self = [super init])) {
        self.pithos = aPithos;
        self.pithosContainer = aPithosContainer;
        prefix = nil;
        self.icon = anIcon;
        childrenUpdatedNotificationName = @"PithosContainerNodeChildrenUpdated";
    }
    return self;
}

- (id)initWithPithos:(ASIPithos *)aPithos pithosContainer:(ASIPithosContainer *)aPithosContainer {
    return [self initWithPithos:aPithos pithosContainer:aPithosContainer icon:nil];
}

- (id)initWithPithos:(ASIPithos *)aPithos containerName:(NSString *)aContainerName icon:(NSImage *)anIcon {
    ASIPithosContainer *container = [ASIPithosContainer container];
    container.name = aContainerName;
    return [self initWithPithos:aPithos pithosContainer:container icon:anIcon];
}

- (id)initWithPithos:(ASIPithos *)aPithos containerName:(NSString *)aContainerName {
    return [self initWithPithos:aPithos containerName:aContainerName icon:nil];
}

- (void)dealloc {
    [containerRequest clearDelegatesAndCancel];
    [containerRequest release];
    [refreshMetadataContainerRequest clearDelegatesAndCancel];
    [refreshMetadataContainerRequest release];
    [applyMetadataContainerRequest clearDelegatesAndCancel];
    [applyMetadataContainerRequest release];
    [policyQuota release];
    [policyVersioning release];
    [childrenUpdatedNotificationName release];
    [prefix release];
    [objects release];
    [pithosContainer release];
    [pithos release];
    [super dealloc];
}

#pragma mark -
#pragma mark Properties

- (void)setPithos:(ASIPithos *)aPithos {
    if (aPithos && ![aPithos isEqualTo:pithos]) {
        [pithos release];
        pithos = [aPithos retain];
        [url release];
        url = nil;
    }
}

- (NSString *)url {
    if (url == nil)
        url = [[NSString alloc] initWithFormat:@"%@/%@%@", 
               (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL), 
               pithosContainer.name, 
               (shared ? @"?shared" : @"")];
    return url;
}

- (NSArray *)children {
    @synchronized(self) {
        switch (freshness) {
            case PithosNodeStateFresh:
                break;
            case PithosNodeStateRefreshNeeded:
                freshness = PithosNodeStateRefreshing;
                containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
                                                                              containerName:pithosContainer.name 
                                                                                      limit:0 
                                                                                     marker:nil 
                                                                                     prefix:prefix 
                                                                                  delimiter:@"/" 
                                                                                       path:nil 
                                                                                       meta:nil 
                                                                                     shared:shared 
                                                                                      until:nil] retain];
                if (sharingAccount)
                    [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
                containerRequest.delegate = self;
                containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
                containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
                containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                             [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
                                             [NSNumber numberWithUnsignedInteger:10], @"retries", 
                                             NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector", 
                                             NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector", 
                                             nil];
                if (!forcedRefresh)
                    containerRequest.downloadCache = [ASIDownloadCache sharedCache];
                [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
                break;
            case PithosNodeStateRefreshing:
                break;
            case PithosNodeStateRefreshFinished:
                if (newChildren) {
                    [children release];
                    children = newChildren;
                    newChildren = nil;
                }
                freshness = PithosNodeStateFresh;
            default:
                break;
        }
        return children;
    }
}

- (NSString *)displayName {
    return [[pithosContainer.name copy] autorelease];
}

- (void)setDisplayName:(NSString *)aDisplayName {
}

- (NSImage *)icon {
    if (icon == nil) {
        if ([pithosContainer.name isEqualToString:@"pithos"])
            icon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)] retain];
        else if ([pithosContainer.name isEqualToString:@"trash"])
            icon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)] retain];
        else
            icon = [sharedIcon retain];
    }
    return icon;
}

- (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer {
    if (![pithosContainer isEqualTo:aPithosContainer]) {
        [pithosContainer release];
        pithosContainer = [aPithosContainer retain];
    }
    if (pithosContainer.policy) {
        self.policyVersioning = [pithosContainer.policy objectForKey:@"versioning"];
        self.policyQuota = [NSNumber numberWithLongLong:[[pithosContainer.policy objectForKey:@"quota"] longLongValue]];
    } else {
        self.policyVersioning = @"manual";
        self.policyQuota = [NSNumber numberWithLongLong:0];
    }
}

- (void)setLimitedPithosContainer:(ASIPithosContainer *)aPithosContainer {
    if (![pithosContainer isEqualTo:aPithosContainer]) {
        self.pithosContainer.name = aPithosContainer.name;
        self.pithosContainer.count = aPithosContainer.count;
        self.pithosContainer.bytes = aPithosContainer.bytes;
        self.pithosContainer.lastModified = aPithosContainer.lastModified;
        self.pithosContainer.untilTimestamp = aPithosContainer.untilTimestamp;
        if (!pithosNodeInfoController) {
            self.pithosContainer.policy = aPithosContainer.policy;
            self.pithosContainer = pithosContainer;
        }
    }
}

#pragma mark -
#pragma mark ASIHTTPRequestDelegate

- (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
    if (retries > 0) {
        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
        [containerRequest release];
        containerRequest = newContainerRequest;
        [[PithosUtilities prepareRequest:containerRequest priority:[[containerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
    } else {
        NSString *message;
        NSError *error = [containerRequest error];
        if (error)
            message = [NSString stringWithFormat:@"Container listing failed: %@", error];
        else
            message = [NSString stringWithFormat:@"Container listing failed: (%d) %@", 
                       containerRequest.responseStatusCode, containerRequest.responseStatusMessage];
        dispatch_async(dispatch_get_main_queue(), ^{
            [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
        });
        [newChildren release];
        newChildren = nil;
        [containerRequest release];
        containerRequest = nil;
        [objects release];
        objects = nil;
        forcedRefresh = NO;
        @synchronized(self) {
            freshness = PithosNodeStateRefreshNeeded;
        }
    }
    [pool drain];
}

- (void)containerRequestFinished:(ASIPithosContainerRequest *)request {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSLog(@"List container finished: %@", [containerRequest url]);
    NSLog(@"Cached: %d", [containerRequest didUseCachedResponse]);
    if (containerRequest.responseStatusCode == 200) {
        if ((pithosContainer.blockHash == nil) || (pithosContainer.blockSize == 0)) {
            pithosContainer.blockHash = [containerRequest blockHash];
            pithosContainer.blockSize = [containerRequest blockSize];
        }
    
        NSArray *someObjects = [containerRequest objects];
        if (objects == nil) {
            objects = [[NSMutableArray alloc] initWithArray:someObjects];
        } else {
            [objects addObjectsFromArray:someObjects];
        }
        if ([someObjects count] < 10000) {
            if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) {
                // Save new children
                NSLog(@"using newChildren");
                newChildren = [[NSMutableArray alloc] init];
                NSArray *objectNames = [objects valueForKey:@"name"];
                NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
                BOOL isSubdirNode = ([self class] == [PithosSubdirNode class]);
                for (ASIPithosObject *object in objects) {
                    if (!isSubdirNode || 
                        ([object.name hasPrefix:[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"]] &&
                         ([object.name length] > [((PithosSubdirNode *)self).prefix length] + 1))) {
                        // The check above removes false objects due to trailing slash or same prefix
                        if (object.subdir) {
                            NSUInteger sameNameObjectIndex = [objectNames indexOfObject:[object.name substringToIndex:([object.name length] - 1)]];
                            if ((sameNameObjectIndex == NSNotFound) || 
                                ![PithosUtilities isContentTypeDirectory:[[objects objectAtIndex:sameNameObjectIndex] contentType]]) {
                                PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithos:pithos 
                                                                                   pithosContainer:pithosContainer 
                                                                                      pithosObject:object] autorelease];
                                node.parent = self;
                                node.shared = shared;
                                node.sharingAccount = sharingAccount;
                                if (children) {
                                    NSUInteger oldIndex = [children indexOfObject:node];
                                    if (oldIndex != NSNotFound) {
                                        // Use the same pointer value, if possible
                                        node = [children objectAtIndex:oldIndex];
                                        node.pithosContainer = pithosContainer;
//                                        node.pithosObject = object;
                                        [node setLimitedPithosObject:object];
                                        [keptNodes addIndex:oldIndex];
                                    }
                                }
                                if (sharingAccount)
                                    node.pithosObject.allowedTo = [NSString stringWithString:@"read"];
                                [newChildren addObject:node];
                            }
                        } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
                            PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithos:pithos 
                                                                               pithosContainer:pithosContainer 
                                                                                  pithosObject:object] autorelease];
                            node.parent = self;
                            node.shared = shared;
                            node.sharingAccount = sharingAccount;
                            if (children) {
                                NSUInteger oldIndex = [children indexOfObject:node];
                                if (oldIndex != NSNotFound) {
                                    // Use the same pointer value, if possible
                                    node = [children objectAtIndex:oldIndex];
                                    node.pithosContainer = pithosContainer;
//                                    node.pithosObject = object;
                                    [node setLimitedPithosObject:object];
                                    [keptNodes addIndex:oldIndex];
                                }
                            }
                            [newChildren addObject:node];
                        } else {
                            PithosObjectNode *node = [[[PithosObjectNode alloc] initWithPithos:pithos 
                                                                               pithosContainer:pithosContainer 
                                                                                  pithosObject:object] autorelease];
                            node.parent = self;
                            node.shared = shared;
                            node.sharingAccount = sharingAccount;
                            if (children) {
                                NSUInteger oldIndex = [children indexOfObject:node];
                                if (oldIndex != NSNotFound) {
                                    // Use the same pointer value, if possible
                                    node = [children objectAtIndex:oldIndex];
                                    node.pithosContainer = pithosContainer;
//                                    node.pithosObject = object;
                                    [node setLimitedPithosObject:object];
                                    [keptNodes addIndex:oldIndex];
                                }
                            }
                            [newChildren addObject:node];                                
                        }
                    }
                }
                [[children objectsAtIndexes:
                  [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
                    if ([keptNodes containsIndex:idx])
                        return NO;
                    return YES;
                }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
            }
            // Else cache was used and all results were fetched during this request, so existing children can be reused
            [containerRequest release];
            containerRequest = nil;
            [objects release];
            objects = nil;
            forcedRefresh = NO;
            @synchronized(self) {
                freshness = PithosNodeStateRefreshFinished;
            }
            // Notify observers that children are updated
            [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
        } else {
            [containerRequest release];
            // Do an additional request to fetch more objects
            containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
                                                                          containerName:pithosContainer.name 
                                                                                  limit:0 
                                                                                 marker:[[someObjects lastObject] name] 
                                                                                 prefix:prefix 
                                                                              delimiter:@"/" 
                                                                                   path:nil 
                                                                                   meta:nil 
                                                                                 shared:shared 
                                                                                  until:nil] retain];
            if (sharingAccount)
                [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
            containerRequest.delegate = self;
            containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
            containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
            containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                         [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
                                         [NSNumber numberWithUnsignedInteger:10], @"retries", 
                                         NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector", 
                                         NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector", 
                                         nil];
            if (!forcedRefresh)
            containerRequest.downloadCache = [ASIDownloadCache sharedCache];
            [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
        }
    } else if (containerRequest.responseStatusCode == 304) {
        // Container is not modified, so existing children can be reused
        [containerRequest release];
        containerRequest = nil;
        [objects release];
        objects = nil;
        forcedRefresh = NO;
        @synchronized(self) {
            freshness = PithosNodeStateRefreshFinished;
        }
        // Notify observers that children are updated
        [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
    } else {
        [self containerRequestFailed:containerRequest];
    }
    [pool drain];
}

- (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSLog(@"URL: %@", [request url]);
    NSLog(@"cached: %d", [request didUseCachedResponse]);
    
    if ([request isEqualTo:applyMetadataContainerRequest]) {
        @synchronized(self) {
            [applyMetadataContainerRequest release];
            applyMetadataContainerRequest = nil;
        }
        [self refreshInfo];
    } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
        [[pithosNodeInfoController window] makeFirstResponder:nil];
        self.pithosContainer = [refreshMetadataContainerRequest container];
        @synchronized(self) {
            [refreshMetadataContainerRequest release];
            refreshMetadataContainerRequest = nil;
        }
    }
    [pool drain];
}

- (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
    if (retries > 0) {
        ASIPithosContainerRequest *newRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:request];
        [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
        if ([request isEqualTo:applyMetadataContainerRequest]) {
            @synchronized(self) {
                [applyMetadataContainerRequest release];
                applyMetadataContainerRequest = newRequest;
            }
        } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
            @synchronized(self) {
                [refreshMetadataContainerRequest release];
                refreshMetadataContainerRequest = newRequest;
            }
        }
        [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
    } else {
        if ([request isEqualTo:applyMetadataContainerRequest]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
            });
            @synchronized(self) {
                [applyMetadataContainerRequest release];
                applyMetadataContainerRequest = nil;
            }
        } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
            });
            @synchronized(self) {
                [refreshMetadataContainerRequest release];
                refreshMetadataContainerRequest = nil;
            }
        }
    }
    [pool drain];
}

#pragma mark -
#pragma mark Info

- (void)applyInfo {
    @synchronized(self) {
        if (applyMetadataContainerRequest == nil) {
            [[pithosNodeInfoController window] makeFirstResponder:nil];
            applyMetadataContainerRequest = [[ASIPithosContainerRequest updateContainerMetadataRequestWithPithos:pithos 
                                                                                                   containerName:pithosContainer.name 
                                                                                                          policy:[NSDictionary dictionaryWithObjectsAndKeys:
                                                                                                                  policyVersioning, @"versioning", 
                                                                                                                  [policyQuota stringValue], @"quota", 
                                                                                                                  nil] 
                                                                                                        metadata:pithosContainer.metadata 
                                                                                                          update:NO] retain];
            applyMetadataContainerRequest.delegate = self;
            applyMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
            applyMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
            applyMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
                                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
                                                      NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector", 
                                                      NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector", 
                                                      nil];
            [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
        }
    }
}

- (void)refreshInfo {
    @synchronized(self) {
        if (refreshMetadataContainerRequest == nil) {
            refreshMetadataContainerRequest = [[ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos 
                                                                                               containerName:pithosContainer.name] retain];
            refreshMetadataContainerRequest.delegate = self;
            refreshMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
            refreshMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
            refreshMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                                        [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
                                                        [NSNumber numberWithUnsignedInteger:10], @"retries", 
                                                        NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector", 
                                                        NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector", 
                                                        nil];
            refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
            [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
        }
    }
}

#pragma mark -
#pragma mark Actions

- (void)showPithosNodeInfo:(id)sender {
    if (!pithosNodeInfoController) {
        pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self];
        [self refreshInfo];
    }
    [pithosNodeInfoController showWindow:sender];
    [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
    [NSApp activateIgnoringOtherApps:YES];
}

@end
