//
//  PithosAccount.m
//  pithos-macos
//
// Copyright 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 "PithosAccount.h"
#import "PithosSyncDaemon.h"
#import "ASIPithos.h"
#import "PithosAccountNode.h"
#import "pithos_macosAppDelegate.h"

@interface PithosAccount (Internal)
- (BOOL)urlIsValid:(NSString *)urlString;
@end

@implementation PithosAccount
@synthesize uniqueName, active, name;
@synthesize syncActive, syncDirectoryPath, syncContainersDictionary, syncLastCompleted, syncDaemon;
@synthesize serverURL, versionResource, loginResource, publicResource;
@synthesize authUser, authToken, storageURLPrefix, authURL, loginURLPrefix, publicURLPrefix;
@synthesize pithos, accountNode;

#pragma mark -
#pragma mark Object Lifecycle

+ (id)pithosAccount {
    PithosAccount *pithosAccount = [[[self alloc] init] autorelease];
    pithosAccount.uniqueName = [NSString stringWithFormat:@"pithosAccount-%f", [NSDate timeIntervalSinceReferenceDate]];
    pithosAccount.versionResource = [NSString stringWithString:@"v1"];
    pithosAccount.loginResource = [NSString stringWithString:@"login"];
    return pithosAccount;
}

- (void)dealloc {
    [accountNode release];
    [pithos release];
    [publicURLPrefix release];
    [loginURLPrefix release];
    [authURL release];
    [storageURLPrefix release];
    [authToken release];
    [authUser release];
    [publicResource release];
    [loginResource release];
    [versionResource release];
    [serverURL release];
    [syncDaemon release];
    [syncLastCompleted release];
    [syncContainersDictionary release];
    [syncDirectoryPath release];
    [name release];
    [uniqueName release];
    [super dealloc];
}

- (NSString *)description {
    return [NSString stringWithFormat:@"uniqueName: %@, active: %d, name: %@, syncActive: %d, syncDirectoryPath: %@, syncContainersDictionary: %@, syncLastCompleted: %@, serverURL: %@, versionResource: %@, loginResource: %@, publicResource: %@, authUser: %@, authToken: %@, storageURLPrefix: %@, authURL: %@, loginURLPrefix: %@, publicURLPrefix: %@", 
            uniqueName, active, name, syncActive, syncDirectoryPath, syncContainersDictionary, syncLastCompleted, serverURL, versionResource, loginResource, publicResource, authUser, authToken, storageURLPrefix, authURL, loginURLPrefix, publicURLPrefix];
}

#pragma mark -
#pragma mark Internal

- (BOOL)urlIsValid:(NSString *)urlString {
    if (urlString) {
        NSURL *url = [NSURL URLWithString:urlString];
        if (url && url.scheme && url.host)
            return YES;
    }
    return NO;
}

#pragma mark -
#pragma mark Properties

- (NSString *)name {
    if (![name length]) {
        [name release];
        NSDictionary *pithosAccountsDictionary = [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] pithosAccountsDictionary];
        NSString *namePrefix = [NSString stringWithString:@"okeanos"];
        NSUInteger nameSuffix = 1;
        name = [NSString stringWithString:@"okeanos"];
        NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        NSFileManager *fileManager = [NSFileManager defaultManager];
        while ([pithosAccountsDictionary objectForKey:name] || 
               [fileManager fileExistsAtPath:[documentsDirectoryPath stringByAppendingPathComponent:name]]) {
            name = [NSString stringWithFormat:@"%@%d", namePrefix, ++nameSuffix];
        }
        [name retain];
    }
    return name;
}

- (void)setName:(NSString *)aName {
    NSMutableDictionary *pithosAccountsDictionary = [(pithos_macosAppDelegate *)[[NSApplication sharedApplication] delegate] pithosAccountsDictionary];
    if (![self.name isEqualToString:aName] && [aName length] && ![pithosAccountsDictionary objectForKey:aName]) {
        [pithosAccountsDictionary setObject:self forKey:aName];
        [pithosAccountsDictionary removeObjectForKey:name];
        [name release];
        name = [aName retain];
    }
}

- (BOOL)syncActive {
    if (active)
        return syncActive;
    else
        return NO;
}

- (void)setSyncActive:(BOOL)aSyncActive {
    syncActive = aSyncActive;
    if (syncDaemon && !self.syncActive)
        [syncDaemon resetDaemon];
}

- (NSString *)syncDirectoryPath {
    if (![syncDirectoryPath length]) {
        [syncDirectoryPath release];
        syncDirectoryPath = [[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
                              stringByAppendingPathComponent:self.name] retain];
    }
    return syncDirectoryPath;
}

- (void)setSyncDirectoryPath:(NSString *)aSyncDirectoryPath {
    if (![self.syncDirectoryPath isEqualToString:aSyncDirectoryPath] && [aSyncDirectoryPath length]) {
        BOOL isDirectory;
        if (![[NSFileManager defaultManager] fileExistsAtPath:aSyncDirectoryPath isDirectory:&isDirectory] || isDirectory) {
            [syncDirectoryPath release];
            syncDirectoryPath = [aSyncDirectoryPath retain];
        } else {
            return;
        }

        @synchronized(self) {
            resetSyncDaemonLocalState = YES;
            [syncLastCompleted release];
            syncLastCompleted = nil;
        }
    }
}

- (NSMutableDictionary *)syncContainersDictionary {
    if (!syncContainersDictionary) {
        syncContainersDictionary = [[NSMutableDictionary dictionaryWithObject:[NSMutableArray array] 
                                                                       forKey:@"pithos"] retain];
    }        
    return syncContainersDictionary;
}

- (void)setSyncContainersDictionary:(NSMutableDictionary *)aSyncContainersDictionary {
    if (aSyncContainersDictionary && ![self.syncContainersDictionary isEqualToDictionary:aSyncContainersDictionary]) {
        [syncContainersDictionary release];
        syncContainersDictionary = [aSyncContainersDictionary retain];
        // XXX maybe check also here the validity of the dictionary? 
        
        @synchronized(self) {
            resetSyncDaemonLocalState = YES;
            [syncLastCompleted release];
            syncLastCompleted = nil;
        }
    }
}

- (NSDate *)syncLastCompleted {
    if (self.syncDaemon.lastCompletedSync && ![self.syncDaemon.lastCompletedSync isEqualToDate:syncLastCompleted]) {
        [syncLastCompleted release];
        syncLastCompleted = [self.syncDaemon.lastCompletedSync copy];
    }
    return syncLastCompleted;
}

- (PithosSyncDaemon *)syncDaemon {
    @synchronized(self) {
        if (self.syncActive && !syncDaemon)
            syncDaemon = [[PithosSyncDaemon alloc] initWithDirectoryPath:self.syncDirectoryPath 
                                                           pithosAccount:self
                                                    containersDictionary:self.syncContainersDictionary
                                                         resetLocalState:resetSyncDaemonLocalState];
        resetSyncDaemonLocalState = NO;
    }
    return syncDaemon;
}

- (NSString *)serverURL {
    if (![self urlIsValid:serverURL]) {
        [serverURL release];
        serverURL = [[NSString stringWithString:@"https://pithos.okeanos.grnet.gr"] retain];
    }
    return serverURL;
}

- (void)setServerURL:(NSString *)aServerURL {
    if (![self.serverURL isEqualToString:aServerURL] && [self urlIsValid:aServerURL]) {
        [serverURL release];
        serverURL = [aServerURL retain];
        [storageURLPrefix release];
        storageURLPrefix = nil;
        [authURL release];
        authURL = nil;
        [publicURLPrefix release];
        publicURLPrefix = nil;
        [loginURLPrefix release];
        loginURLPrefix = nil;

        @synchronized(self) {
            updatePithos = YES;
            resetSyncDaemonLocalState = YES;
            [syncLastCompleted release];
            syncLastCompleted = nil;
        }
    }
}

- (void)setAuthUser:(NSString *)anAuthUser {
    if ([anAuthUser length] && ![anAuthUser isEqualToString:authUser]) {
        [authUser release];
        authUser = [anAuthUser retain];
        
        @synchronized(self) {
            updatePithos = YES;
            resetSyncDaemonLocalState = YES;
            [syncLastCompleted release];
            syncLastCompleted = nil;

        }
    }
}

- (void)setAuthToken:(NSString *)anAuthToken {
    if ([anAuthToken length] && ![anAuthToken isEqualToString:authToken]) {
        [authToken release];
        authToken = [anAuthToken retain];
        
        @synchronized(self) {
            updatePithos = YES;
        }
    }
}

- (NSString *)storageURLPrefix {
    if (![self urlIsValid:storageURLPrefix]) {
        [storageURLPrefix release];
        if (versionResource)
            storageURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", versionResource] retain];
        else
            storageURLPrefix = [self.serverURL copy];
    }
    return storageURLPrefix;
}

- (void)setStorageURLPrefix:(NSString *)aStorageURLPrefix {
    if (![self.storageURLPrefix isEqualToString:aStorageURLPrefix] && [self urlIsValid:aStorageURLPrefix]) {
        [storageURLPrefix release];
        storageURLPrefix = [aStorageURLPrefix retain];
    }
}

- (NSString *)authURL {
    if (![self urlIsValid:authURL]) {
        [authURL release];
        if (versionResource)
            authURL = [[self.serverURL stringByAppendingFormat:@"/%@", versionResource] retain];
        else
            authURL = [self.serverURL copy];
    }
    return authURL;
}

- (void)setAuthURL:(NSString *)anAuthURL {
    if (![self.authURL isEqualToString:anAuthURL] && [self urlIsValid:anAuthURL]) {
        [authURL release];
        authURL = [anAuthURL retain];
    }
}

- (NSString *)publicURLPrefix {
    if (![self urlIsValid:publicURLPrefix]) {
        [publicURLPrefix release];
        if (publicResource)
            publicURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", publicResource] retain];
        else
            publicURLPrefix = [self.serverURL copy];
    }
    return publicURLPrefix;
}

- (void)setPublicURLPrefix:(NSString *)aPublicURLPrefix {
    if (![self.publicURLPrefix isEqualToString:aPublicURLPrefix] && [self urlIsValid:aPublicURLPrefix]) {
        [publicURLPrefix release];
        publicURLPrefix = [aPublicURLPrefix retain];
    }
}

- (NSString *)loginURLPrefix {
    if (![self urlIsValid:loginURLPrefix]) {
        [loginURLPrefix release];
        if (loginResource)
            loginURLPrefix = [[self.serverURL stringByAppendingFormat:@"/%@", loginResource] retain];
        else
            loginURLPrefix = [self.serverURL copy];
    }
    return loginURLPrefix;
}

- (void)setLoginURLPrefix:(NSString *)aLoginURLPrefix {
    if (![self.loginURLPrefix isEqualToString:aLoginURLPrefix] && [self urlIsValid:aLoginURLPrefix]) {
        [loginURLPrefix release];
        loginURLPrefix = [aLoginURLPrefix retain];
    }
}

- (ASIPithos *)pithos {
    @synchronized(self) {
        if (!pithos || updatePithos) {
            [pithos release];
            pithos = [[ASIPithos pithos] retain];
            pithos.authUser = authUser;
            pithos.authToken = authToken;
            pithos.storageURLPrefix = self.storageURLPrefix;
            pithos.authURL = self.authURL;
            pithos.publicURLPrefix = self.publicURLPrefix;

            if (accountNode && ![accountNode.pithos isEqualTo:pithos]) {
                accountNode.pithos = pithos;
                if (active)
                    [accountNode refreshInfo];
            }
            
            updatePithos = NO;
        }
    }
    return pithos;
}

- (PithosAccountNode *)accountNode {
    if (!accountNode) {
        accountNode = [[PithosAccountNode alloc] initWithPithos:self.pithos];
        accountNode.childrenUpdatedNotificationName = nil;
        accountNode.inheritChildrenUpdatedNotificationName = YES;
    }
    return accountNode;
}

#pragma mark -
#pragma mark Actions

- (void)authenticateWithServerURL:(NSString *)aServerURL authUser:(NSString *)anAuthUser authToken:(NSString *)anAuthToken {
    self.serverURL = aServerURL;
    self.authUser = anAuthUser;
    self.authToken = anAuthToken;
    NSLog(@"Account: %@\nauthentication", self);
    if (![authUser length] || ![authToken length]) {
        self.active = NO;
        self.syncActive = NO;
        // XXX Show preferences with self as the selected account?
    } else  {
        self.active = YES;
        if (syncDaemon) {
            self.syncDaemon.pithos = self.pithos;
            if (self.syncActive)
                [self.syncDaemon startDaemon];
        }
    }
}

- (void)loginWithServerURL:(NSString *)aServerURL {
    self.serverURL = aServerURL;
    NSProcessInfo *processInfo = [NSProcessInfo processInfo];
    NSString *loginURL = [NSString stringWithFormat:@"%@?next=pithos://%@_%d/%@", 
                          self.loginURLPrefix, [processInfo processName], [processInfo processIdentifier], self.name];
    NSLog(@"Account: %@\nloginURL: %@", self, loginURL);
    [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:loginURL]];
}

- (void)updateSyncWithSyncActive:(BOOL)aSyncActive 
               syncDirectoryPath:(NSString *)aSyncDirectoryPath 
        syncContainersDictionary:(NSMutableDictionary *)aSyncContainersDictionary {
    self.syncContainersDictionary = aSyncContainersDictionary;
    self.syncDirectoryPath = aSyncDirectoryPath;
    self.syncActive = aSyncActive;
    if (syncDaemon) {
        self.syncDaemon.containersDictionary = self.syncContainersDictionary;
        self.syncDaemon.directoryPath = self.syncDirectoryPath;
        if (self.syncActive)
            [self.syncDaemon startDaemon];
    }    
}

#pragma mark -
#pragma mark NSCoding

- (id)initWithCoder:(NSCoder *)decoder {
    if ((self = [super init])) {
        self.uniqueName = [decoder decodeObjectForKey:@"uniqueName"];
        self.active = [decoder decodeBoolForKey:@"active"];
        name = [[decoder decodeObjectForKey:@"name"] retain];

        self.syncActive = [decoder decodeBoolForKey:@"syncActive"];
        self.syncDirectoryPath = [decoder decodeObjectForKey:@"syncDirectoryPath"];
        NSDictionary *immutableContainersDictionary = [decoder decodeObjectForKey:@"syncContainersDictionary"];
        syncContainersDictionary = [[NSMutableDictionary alloc] initWithCapacity:[immutableContainersDictionary count]];
        for (NSString *containerName in immutableContainersDictionary) {
            [syncContainersDictionary setObject:[NSMutableArray arrayWithArray:[immutableContainersDictionary objectForKey:containerName]] 
                                         forKey:containerName];
        }
        self.syncLastCompleted = [decoder decodeObjectForKey:@"syncLastCompleted"];
        
        self.serverURL = [decoder decodeObjectForKey:@"serverURL"];
        self.versionResource = [decoder decodeObjectForKey:@"versionResource"];
        self.loginResource = [decoder decodeObjectForKey:@"loginResource"];
        self.publicResource = [decoder decodeObjectForKey:@"publicResource"];
        
        self.authUser = [decoder decodeObjectForKey:@"authUser"];
        self.authToken = [decoder decodeObjectForKey:@"authToken"];
        self.storageURLPrefix = [decoder decodeObjectForKey:@"storageURLPrefix"];
        self.authURL = [decoder decodeObjectForKey:@"authURL"];
        self.publicURLPrefix = [decoder decodeObjectForKey:@"publicURLPrefix"];
        self.loginURLPrefix = [decoder decodeObjectForKey:@"loginURLPrefix"];
        
        if (![authUser length] || ![authToken length] || ![self.storageURLPrefix length])
            self.active = NO;
        
        resetSyncDaemonLocalState = NO;
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:uniqueName forKey:@"uniqueName"];
    [encoder encodeBool:active forKey:@"active"];
    [encoder encodeObject:name forKey:@"name"];
    
    [encoder encodeBool:syncActive forKey:@"syncActive"];
    [encoder encodeObject:syncDirectoryPath forKey:@"syncDirectoryPath"];
    [encoder encodeObject:syncContainersDictionary forKey:@"syncContainersDictionary"];
    [encoder encodeObject:self.syncLastCompleted forKey:@"syncLastCompleted"];

    [encoder encodeObject:serverURL forKey:@"serverURL"];
    [encoder encodeObject:versionResource forKey:@"versionResource"];
    [encoder encodeObject:publicResource forKey:@"publicResource"];
    [encoder encodeObject:loginResource forKey:@"loginResource"];
    
    [encoder encodeObject:authUser forKey:@"authUser"];
    [encoder encodeObject:authToken forKey:@"authToken"];
    [encoder encodeObject:storageURLPrefix forKey:@"storageURLPrefix"];
    [encoder encodeObject:authURL forKey:@"authURL"];
    [encoder encodeObject:publicURLPrefix forKey:@"publicURLPrefix"];
    [encoder encodeObject:loginURLPrefix forKey:@"loginURLPrefix"];
}

@end
