//  ASIPithosRequest.m
//  Based on ASICloudFilesRequest.m
//  Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Copyright 2011 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 "ASIPithosRequest.h"
#import "ASIPithosAccount.h"

static NSString *authURL = nil;
static NSString *authUser = nil;
static NSString *authKey = nil;

static NSString *storageURLPrefix = nil;
static NSString *storageURL = nil;
static NSString *authToken = nil;

static NSString *publicURLPrefix = nil;

static NSRecursiveLock *accessDetailsLock = nil;

@implementation ASIPithosRequest
@synthesize currentElement, currentContent, currentAccount;

+ (void)initialize {
	if (self == [ASIPithosRequest class]) {
		accessDetailsLock = [[NSRecursiveLock alloc] init];
	}
}

#pragma mark -
#pragma mark Constructors

+ (id)authRequestWithMethod:(NSString *)method queryString:(NSString *)queryString useAuthToken:(BOOL)useAuthToken {
    NSString *urlString = [NSString stringWithString:authURL];
    if (queryString)
        urlString = [urlString stringByAppendingString:queryString];
    
	ASIPithosRequest *request = [[[self alloc] initWithURL:[NSURL URLWithString:urlString]] autorelease];
	[request setRequestMethod:method];
    if (useAuthToken)
        [request addRequestHeader:@"X-Auth-Token" value:authToken];
	return request;
}

+ (id)authRequestWithMethod:(NSString *)method useAuthToken:(BOOL)useAuthToken {
	return [self authRequestWithMethod:method queryString:nil useAuthToken:useAuthToken];
}

#pragma mark -
#pragma mark Memory Management

- (void)dealloc {
	[currentAccount release];
    [currentContent release];
    [currentElement release];
    [sharingAccounts release];
	[super dealloc];
}

#pragma mark -
#pragma mark Properties

+ (NSString *)authURL {
    return authURL;
}

+ (void)setAuthURL:(NSString *)newAuthURL {
	[accessDetailsLock lock];
	[authURL release];
	authURL = [newAuthURL retain];
	[accessDetailsLock unlock];
}

+ (NSString *)authUser {
	return authUser;
}

+ (void)setAuthUser:(NSString *)newAuthUser {
	[accessDetailsLock lock];
	[authUser release];
	authUser = [newAuthUser retain];
	[accessDetailsLock unlock];
}

+ (NSString *)authKey {
	return authKey;
}

+ (void)setAuthKey:(NSString *)newAuthKey {
	[accessDetailsLock lock];
	[authKey release];
	authKey = [newAuthKey retain];
	[accessDetailsLock unlock];
}

+ (NSString *)storageURLPrefix {
    return storageURLPrefix;
}

+ (void)setStorageURLPrefix:(NSString *)newStorageURLPrefix {
	[accessDetailsLock lock];
	[storageURLPrefix release];
	storageURLPrefix = [newStorageURLPrefix retain];
	[accessDetailsLock unlock];
}

+ (NSString *)storageURL {
    if (storageURL)
        return storageURL;
    else if (storageURLPrefix && authUser)
        return [NSString stringWithFormat:@"%@/%@", storageURLPrefix, [authUser stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    return nil;
}

+ (void)setStorageURL:(NSString *)newStorageURL {
	[accessDetailsLock lock];
	[storageURL release];
	storageURL = [newStorageURL retain];
	[accessDetailsLock unlock];
}

+ (NSString *)authToken {
	return authToken;
}

+ (void)setAuthToken:(NSString *)newAuthToken {
	[accessDetailsLock lock];
	[authToken release];
	authToken = [newAuthToken retain];
	[accessDetailsLock unlock];
}

+ (NSString *)storageURLWithAuthUser:(NSString *)anAuthUser {
    if (storageURLPrefix)
        return [NSString stringWithFormat:@"%@/%@", storageURLPrefix, [anAuthUser stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    return nil;
}

+ (NSString *)publicURLPrefix {
    return publicURLPrefix;
}

+ (void)setPublicURLPrefix:(NSString *)newPublicURLPrefix {
    [accessDetailsLock lock];
	[publicURLPrefix release];
	publicURLPrefix = [newPublicURLPrefix retain];
	[accessDetailsLock unlock];
}

#pragma mark -
#pragma mark Authentication

+ (id)authenticationRequest {
	[accessDetailsLock lock];
    ASIPithosRequest *request = [self authRequestWithMethod:@"GET" useAuthToken:NO];
	[request addRequestHeader:@"X-Auth-User" value:authUser];
	[request addRequestHeader:@"X-Auth-Key" value:authKey];
	[accessDetailsLock unlock];
	return request;
}

+ (NSError *)authenticate {
	[accessDetailsLock lock];
	ASIPithosRequest *request = [self authenticationRequest];
	[request startSynchronous];
	
	if (![request error]) {
		NSDictionary *responseHeaders = [request responseHeaders];
        [ASIPithosRequest setAuthToken:[responseHeaders objectForKey:@"X-Auth-Token"]];
        [ASIPithosRequest setStorageURL:[responseHeaders objectForKey:@"X-Storage-Url"]];
	}
	[accessDetailsLock unlock];
	return [request error];
}

#pragma mark -
#pragma mark GET

// GET authURL
+ (id)listSharingAccountsRequest {
    return [self authRequestWithMethod:@"GET" queryString:@"?format=xml" useAuthToken:YES];
}

// GET authURL?[limit=limit]&[marker=marker]
+ (id)listSharingAccountsRequestWith:(NSUInteger)limit marker:(NSString *)marker {
    NSString *queryString = @"?format=xml";
	if (limit && (limit > 0))
		queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&limit=%lu", limit]];
	if (marker)
		queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&marker=%@", [marker stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
    
    return [self authRequestWithMethod:@"GET" queryString:queryString useAuthToken:YES];
}

- (NSArray *)sharingAccounts {
    if (sharingAccounts == nil) {
        sharingAccounts = [[NSMutableArray alloc] init];
        
        NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
        [parser setDelegate:self];
        [parser setShouldProcessNamespaces:NO];
        [parser setShouldReportNamespacePrefixes:NO];
        [parser setShouldResolveExternalEntities:NO];
        [parser parse];
    }    
	return sharingAccounts;
}

#pragma mark -
#pragma mark NSXMLParserDelegate

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
	self.currentElement = elementName;
	if ([elementName isEqualToString:@"account"]) {
		self.currentAccount = [ASIPithosAccount account];
	}
    
	self.currentContent = @"";
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
	if ([elementName isEqualToString:@"name"]) {
        currentAccount.name = currentContent;
    } else if ([elementName isEqualToString:@"last_modified"]) {
        currentAccount.lastModified = [[self dateFormatterWithFormatId:0] dateFromString:currentContent];
	} else if ([elementName isEqualToString:@"account"]) {
		[sharingAccounts addObject:currentAccount];
        self.currentAccount = nil;
	}
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    self.currentContent = [currentContent stringByAppendingString:string];
}

#pragma mark -
#pragma mark Date Formatters

// We store our date formatters in the calling thread's dictionary
// NSDateFormatter is not thread-safe, this approach ensures each formatter is only used on a single thread
// Each formatter can be reused many times in parsing a single response, so it would be expensive to keep creating new date formatters

-(NSDateFormatter *)dateFormatterWithFormatId:(NSUInteger)formatId {
    NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
    NSString *dateFormatterKey = [NSString stringWithFormat:@"ASIPithosDateFormatter%lu", formatId];
    NSDateFormatter *dateFormatter = [threadDict objectForKey:dateFormatterKey];
	if (dateFormatter == nil) {
        switch (formatId) {
            case 0:
                // date format: 2009-11-04T19:46:20.192723
                // Needed for last_modified
                dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
                [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
                [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss"];
                [threadDict setObject:dateFormatter forKey:dateFormatterKey];
                break;
            case 1:
                // date format: Wed, 20 Jul 2011 13:39:19 GMT
                // Needed for reading X-Account-Until-Timestamp, Last-Modified
                dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
                [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
                [dateFormatter setDateFormat:@"eee, dd MMM yyyy HH:mm:ss zzz"];
                [threadDict setObject:dateFormatter forKey:dateFormatterKey];
                break;
            case 2:
                // date to format: Thu Jul 21 10:54:45 2010
                // Can be used to write If-Modified-Since, If-Unmodified-Since in the request headers
                dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
                [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
                [dateFormatter setDateFormat:@"eee MMM dd HH:mm:ss yyyy"];
                [threadDict setObject:dateFormatter forKey:dateFormatterKey];
                break;
            default:
                break;
        }
    }
    return dateFormatter;
}

#pragma mark -
#pragma mark Extra Headers

- (void)addRequestIfModifiedSinceHeader:(NSDate *)sinceTimestamp {
    if (sinceTimestamp)
        [self addRequestHeader:@"If-Modified-Since" value:[[self dateFormatterWithFormatId:2] stringFromDate:sinceTimestamp]];        
}

- (void)addRequestIfUnmodifiedSinceHeader:(NSDate *)sinceTimestamp {
    if (sinceTimestamp)
        [self addRequestHeader:@"If-Unmodified-Since" value:[[self dateFormatterWithFormatId:2] stringFromDate:sinceTimestamp]];
}

- (void)addRequestIfMatchHeader:(NSString *)matchETag {
    if (matchETag)
        [self addRequestHeader:@"If-Match" value:matchETag];
}

- (void)addRequestIfNoneMatchHeader:(NSString *)matchETag {
    if (matchETag)
        [self addRequestHeader:@"If-None-Match" value:matchETag];
}

- (void)addRequestRangeHeader:(NSString *)rangeString {
    if (rangeString)
        [self addRequestHeader:@"Range" value:rangeString];    
}

- (void)addRequestRangeHeader:(NSString *)rangeString ifRangeETag:(NSString *)rangeETag {
    if (rangeString) {
        [self addRequestHeader:@"Range" value:rangeString];
        if (rangeETag)
            [self addRequestHeader:@"If-Range" value:rangeETag];
    }
}

- (void)addRequestRangeHeader:(NSString *)rangeString ifRangeTimestamp:(NSDate *)rangeTimestamp {
    if (rangeString) {
        [self addRequestHeader:@"Range" value:rangeString];
        if (rangeTimestamp)
            [self addRequestHeader:@"If-Range" value:[[self dateFormatterWithFormatId:2] stringFromDate:rangeTimestamp]];
    }
}

#pragma mark -
#pragma mark Internal

- (NSMutableDictionary *)getHeadersDictionaryForPrefix:(NSString *)prefix {
    NSMutableDictionary *headersDictionary = [NSMutableDictionary dictionary];
    for (NSString *key in [[self responseHeaders] keyEnumerator]) {
        NSRange prefixRange = [key rangeOfString:prefix];
        if (prefixRange.location == 0) {
            [headersDictionary setObject:[[self responseHeaders] objectForKey:key] forKey:[key substringFromIndex:prefix.length]];
        }
    }
    return headersDictionary;
}

@end
