//
//  PithosAccountNode.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 "PithosAccountNode.h"
#import "PithosContainerNode.h"
#import "ASIPithos.h"
#import "ASIPithosAccountRequest.h"
#import "ASIPithosAccount.h"
#import "ASIPithosContainer.h"
#import "ASIDownloadCache.h"
#import "PithosUtilities.h"
#import "PithosActivityFacility.h"

static NSImage *sharedIcon = nil;

@implementation PithosAccountNode
@synthesize pithos, pithosAccount;

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

#pragma mark -
#pragma mark Object Lifecycle

- (id)initWithPithos:(ASIPithos *)aPithos {
    if ((self = [super init])) {
        self.pithos = aPithos;
    }
    return self;
}

- (void)dealloc {
    [accountRequest clearDelegatesAndCancel];
    [accountRequest release];
    [refreshMetadataAccountRequest clearDelegatesAndCancel];
    [refreshMetadataAccountRequest release];
    [applyMetadataAccountRequest clearDelegatesAndCancel];
    [applyMetadataAccountRequest release];
    [containers release];
    [pithosAccount 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), 
               (shared ? @"?shared" : @"")];
    return url;
}

- (NSArray *)children {
    @synchronized(self) {
        switch (freshness) {
            case PithosNodeStateFresh:
                break;
            case PithosNodeStateRefreshNeeded:
                freshness = PithosNodeStateRefreshing;
                accountRequest = [[ASIPithosAccountRequest listContainersRequestWithPithos:pithos 
                                                                                     limit:0 
                                                                                    marker:nil 
                                                                                    shared:shared 
                                                                                     until:nil] retain];
                if (sharingAccount)
                    [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
                accountRequest.delegate = self;
                accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
                accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
                accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                           [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
                                           [NSNumber numberWithUnsignedInteger:10], @"retries", 
                                           NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector", 
                                           NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector", 
                                           nil];
                if (!forcedRefresh)
                    accountRequest.downloadCache = [ASIDownloadCache sharedCache];
                [[PithosUtilities prepareRequest:accountRequest 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 {
    if (displayName == nil)
        return [NSString stringWithString:(sharingAccount ? sharingAccount : @"account")];
    return [[displayName copy] autorelease];
}

- (NSImage *)icon {
    if (icon == nil)
        icon = [sharedIcon retain];
    return icon;
}

#pragma mark -
#pragma mark ASIHTTPRequestDelegate

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

- (void)accountRequestFinished:(ASIPithosAccountRequest *)request {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSLog(@"List account finished: %@", [accountRequest url]);
    NSLog(@"Cached: %d", [accountRequest didUseCachedResponse]);
    if (accountRequest.responseStatusCode == 200) {
        self.pithosAccount = [accountRequest account];
        
        NSArray *someContainers = [accountRequest containers];
        if (containers == nil) {
            containers = [[NSMutableArray alloc] initWithArray:someContainers];
        } else {
            [containers addObjectsFromArray:someContainers];
        }
        if ([someContainers count] < 10000) {
            if (!accountRequest.didUseCachedResponse || ([containers count] != [someContainers count]) || !children) {
                // Save new children
                NSLog(@"using newChildren");
                newChildren = [[NSMutableArray alloc] init];
                NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
                for (ASIPithosContainer *container in containers) {
                    PithosContainerNode *node = [[[PithosContainerNode alloc] initWithPithos:pithos pithosContainer:container] 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 = container;
                            [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
            [accountRequest release];
            accountRequest = nil;
            [containers release];
            containers = nil;
            forcedRefresh = NO;
            @synchronized(self) {
                freshness = PithosNodeStateRefreshFinished;
            }
            // Notify observers that children are updated
            [[NSNotificationCenter defaultCenter] postNotificationName:@"PithosAccountNodeChildrenUpdated" object:self];
        } else {
            [accountRequest release];
            // Do an additional request to fetch more objects
            accountRequest = [[ASIPithosAccountRequest listContainersRequestWithPithos:pithos 
                                                                                 limit:0 
                                                                                marker:[[someContainers lastObject] name] 
                                                                                shared:shared 
                                                                                 until:nil] retain];
            if (sharingAccount)
                [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
            accountRequest.delegate = self;
            accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
            accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
            accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                       [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
                                       NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector", 
                                       NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector", 
                                       nil];
            if (!forcedRefresh)
                accountRequest.downloadCache = [ASIDownloadCache sharedCache];
            [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
        }
    } else if (accountRequest.responseStatusCode == 304) {
        // Account is not modified, so existing children can be reused
        [accountRequest release];
        accountRequest = nil;
        [containers release];
        containers = nil;
        forcedRefresh = NO;
        @synchronized(self) {
            freshness = PithosNodeStateRefreshFinished;
        }
        // Notify observers that children are updated
        [[NSNotificationCenter defaultCenter] postNotificationName:@"PithosAccountNodeChildrenUpdated" object:self];
    } else {
        [self accountRequestFailed:accountRequest];
    }
    [pool drain];
}

- (void)accountMetadataRequestFinished:(ASIPithosAccountRequest *)request {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSLog(@"URL: %@", [request url]);
    NSLog(@"cached: %d", [request didUseCachedResponse]);
    
    if ([request isEqualTo:applyMetadataAccountRequest]) {
        @synchronized(self) {
            [applyMetadataAccountRequest release];
            applyMetadataAccountRequest = nil;
        }
        [self refreshInfo];
    } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
        self.pithosAccount = [refreshMetadataAccountRequest account];
        @synchronized(self) {
            [refreshMetadataAccountRequest release];
            refreshMetadataAccountRequest = nil;
        }
    }
    [pool drain];
}

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

#pragma mark -
#pragma mark Info

- (void)applyInfo {
    @synchronized(self) {
        if (applyMetadataAccountRequest == nil) {
            NSMutableDictionary *groups = pithosAccount.groups;
            if ([groups count] == 0)
                groups = [NSMutableDictionary dictionaryWithObject:@"" forKey:@"group"];
            applyMetadataAccountRequest = [[ASIPithosAccountRequest updateAccountMetadataRequestWithPithos:pithos 
                                                                                                    groups:groups 
                                                                                                  metadata:pithosAccount.metadata 
                                                                                                    update:NO] retain];
            applyMetadataAccountRequest.delegate = self;
            applyMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
            applyMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
            applyMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                                    [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
                                                    [NSNumber numberWithUnsignedInteger:10], @"retries", 
                                                    NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector", 
                                                    NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector", 
                                                    nil];
            [[PithosUtilities prepareRequest:applyMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
        }
    }
}

- (void)refreshInfo {
    @synchronized(self) {
        if (refreshMetadataAccountRequest == nil) {
            refreshMetadataAccountRequest = [[ASIPithosAccountRequest accountMetadataRequestWithPithos:pithos] retain];
            refreshMetadataAccountRequest.delegate = self;
            refreshMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
            refreshMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
            refreshMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
                                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
                                                      NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector", 
                                                      NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector", 
                                                      nil];
            refreshMetadataAccountRequest.downloadCache = [ASIDownloadCache sharedCache];
            [[PithosUtilities prepareRequest:refreshMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
        }
    }
}

@end
