//  ASIPithosContainerRequest.m
//  Based on ASICloudFilesContainerRequest.m
//
// 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 "ASIPithosContainerRequest.h"
#import "ASIPithos.h"
#import "ASIPithosContainer.h"
#import "ASIPithosObject.h"

@implementation ASIPithosContainerRequest
@synthesize containerName;
@synthesize currentObject, currentKey;

#pragma mark -
#pragma mark Constructors

+ (id)storageRequestWithMethod:(NSString *)method 
                        pithos:(ASIPithos *)pithos 
                 containerName:(NSString *)containerName 
                   queryString:(NSString *)queryString {
    NSString *urlString = [NSString stringWithFormat:@"%@/%@", pithos.storageURL, [self encodeToPercentEscape:containerName]];
    if (queryString)
        urlString = [urlString stringByAppendingString:queryString];

	ASIPithosContainerRequest *request = [[[self alloc] initWithURL:[NSURL URLWithString:urlString]] autorelease];
	[request setRequestMethod:method];
	[request addRequestHeader:@"X-Auth-Token" value:pithos.authToken];
    request.containerName = containerName;
	return request;
}

+ (id)storageRequestWithMethod:(NSString *)method pithos:(ASIPithos *)pithos containerName:(NSString *)containerName {
	return [self storageRequestWithMethod:method pithos:pithos containerName:containerName queryString:nil];
}

#pragma mark -
#pragma mark Memory Management

- (void)dealloc {
    [currentKey release];
	[currentObject release];
    [objects release];
    [metadata release];
    [policy release];
    [containerName release];
	[super dealloc];
}

#pragma mark -
#pragma mark HEAD

// HEAD storageURL/container
+ (id)containerMetadataRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName {
    return [self storageRequestWithMethod:@"HEAD" pithos:pithos containerName:containerName];
}

// HEAD storageURL/container?[until=untilTimestamp]
+ (id)containerMetadataRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName until:(NSDate *)untilTimestamp {
    NSString *queryString = nil;
    if (untilTimestamp)
        queryString = [NSString stringWithFormat:@"?until=%d", (int)[untilTimestamp timeIntervalSince1970]];
    
	return [self storageRequestWithMethod:@"HEAD" pithos:pithos containerName:containerName queryString:queryString];
}

// HEAD storageURL/container?[until=untilTimestamp] [If-Modified-Since]
+ (id)containerMetadataRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName 
                                   until:(NSDate *)untilTimestamp 
                         ifModifiedSince:(NSDate *)sinceTimestamp {
    ASIPithosContainerRequest *request = [self containerMetadataRequestWithPithos:pithos containerName:containerName until:untilTimestamp];
    [request addRequestIfModifiedSinceHeader:sinceTimestamp];
    return request;
}

// HEAD storageURL/container?[until=untilTimestamp] [If-Unmodified-Since]
+ (id)containerMetadataRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName 
                                   until:(NSDate *)untilTimestamp 
                       ifUnmodifiedSince:(NSDate *)sinceTimestamp {
    ASIPithosContainerRequest *request = [self containerMetadataRequestWithPithos:pithos containerName:containerName until:untilTimestamp];
    [request addRequestIfUnmodifiedSinceHeader:sinceTimestamp];
    return request;
}

- (NSUInteger)objectCount {
	return [[[self responseHeaders] objectForKey:@"X-Container-Object-Count"] integerValue];
}

- (NSUInteger)bytesUsed {
	return [[[self responseHeaders] objectForKey:@"X-Container-Bytes-Used"] integerValue];
}

- (NSUInteger)blockSize {
	return [[[self responseHeaders] objectForKey:@"X-Container-Block-Size"] integerValue];
}

- (NSString *)blockHash {
	return [[self responseHeaders] objectForKey:@"X-Container-Block-Hash"];
}

- (NSArray *)objectMeta {
    return [[self decodeFromPercentEscape:[[self responseHeaders] objectForKey:@"X-Container-Object-Meta"]] 
            componentsSeparatedByString:@","];
}

- (NSDate *)untilTimestamp {
    NSString *untilTimestampString = [[self responseHeaders] objectForKey:@"X-Container-Until-Timestamp"];
    if (untilTimestampString)
        return [[self dateFormatterWithFormatId:1] dateFromString:untilTimestampString];
    return nil;
}

- (NSDictionary *)policy {
    if (policy == nil) {
        policy = [self getHeadersDictionaryForPrefix:@"X-Container-Policy-"];
        [policy retain];
    }
    return policy;
}

- (NSDictionary *)metadata {
    if (metadata == nil) {
        metadata = [self getHeadersDictionaryForPrefix:@"X-Container-Meta-"];
        [metadata retain];
    }
    return metadata;
}

- (NSDate *)lastModified {
    NSString *lastModifiedString = [[self responseHeaders] objectForKey:@"Last-Modified"];
    if (lastModifiedString)
        return [[self dateFormatterWithFormatId:1] dateFromString:lastModifiedString];
    return nil;
}

- (ASIPithosContainer *)container {
    ASIPithosContainer *container = [ASIPithosContainer container];
    container.name = containerName;
    container.count = [self objectCount];
    container.bytes = [self bytesUsed];
    container.lastModified = [self lastModified];
    container.untilTimestamp = [self untilTimestamp];
    container.policy = (NSMutableDictionary *)[self policy];
    container.metadata = (NSMutableDictionary *)[self metadata];
    container.blockSize =  [self blockSize];
    container.blockHash =  [self blockHash];
    container.objectMeta = (NSMutableArray *)[self objectMeta];
    return container;
}

#pragma mark -
#pragma mark GET

// GET storageURL/container
+ (id)listObjectsRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName {
    return [self storageRequestWithMethod:@"GET" pithos:pithos containerName:containerName queryString:@"?format=xml"];
}

// GET storageURL?[limit=limit]&[marker=marker]&[prefix=prefix]&[delimiter=delimiter]&[path=path]&[meta=meta]&[shared=]&[until=untilTimestamp]
+ (id)listObjectsRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName
                             limit:(NSUInteger)limit 
                            marker:(NSString *)marker 
                            prefix:(NSString *)prefix 
                         delimiter:(NSString *)delimiter 
                              path:(NSString *)path 
                              meta:(NSArray *)meta 
                            shared:(BOOL)shared
                             until:(NSDate *)untilTimestamp {
    NSString *queryString = @"?format=xml";
	if (limit && (limit > 0))
		queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&limit=%lu", limit]];
	if (marker)
		queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&marker=%@", [self encodeToPercentEscape:marker]]];
	if (prefix)
		queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&prefix=%@", [self encodeToPercentEscape:prefix]]];
	if (delimiter)
		queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&delimiter=%@", [self encodeToPercentEscape:delimiter]]];
	if (path)
		queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&path=%@", [self encodeToPercentEscape:path]]];
    if (meta)
        queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&meta=%@", [self encodeToPercentEscape:[meta componentsJoinedByString:@","]]]];
	if (shared)
		queryString = [queryString stringByAppendingString:@"&shared="];
    if (untilTimestamp)
        queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&until=%d", (int)[untilTimestamp timeIntervalSince1970]]];
    
    return [self storageRequestWithMethod:@"GET" pithos:pithos containerName:containerName queryString:queryString];
}

// GET storageURL?[limit=limit]&[marker=marker]&[prefix=prefix]&[delimiter=delimiter]&[path=path]&[meta=meta]&[shared=]&[until=untilTimestamp] [If-Modified-Since]
+ (id)listObjectsRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName
                             limit:(NSUInteger)limit 
                            marker:(NSString *)marker 
                            prefix:(NSString *)prefix 
                         delimiter:(NSString *)delimiter 
                              path:(NSString *)path 
                              meta:(NSArray *)meta 
                            shared:(BOOL)shared
                             until:(NSDate *)untilTimestamp 
                   ifModifiedSince:(NSDate *)sinceTimestamp {
    ASIPithosContainerRequest *request = [self listObjectsRequestWithPithos:pithos 
                                                              containerName:containerName 
                                                                      limit:limit 
                                                                     marker:marker 
                                                                     prefix:prefix 
                                                                  delimiter:delimiter 
                                                                       path:path 
                                                                       meta:meta 
                                                                     shared:shared 
                                                                      until:untilTimestamp];
    [request addRequestIfModifiedSinceHeader:sinceTimestamp];
    return request;
}

// GET storageURL?[limit=limit]&[marker=marker]&[prefix=prefix]&[delimiter=delimiter]&[path=path]&[meta=meta]&[shared=]&[until=untilTimestamp] [If-Unmodified-Since]
+ (id)listObjectsRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName
                             limit:(NSUInteger)limit 
                            marker:(NSString *)marker 
                            prefix:(NSString *)prefix 
                         delimiter:(NSString *)delimiter 
                              path:(NSString *)path 
                              meta:(NSArray *)meta 
                            shared:(BOOL)shared
                             until:(NSDate *)untilTimestamp 
                 ifUnmodifiedSince:(NSDate *)sinceTimestamp {
    ASIPithosContainerRequest *request = [self listObjectsRequestWithPithos:pithos 
                                                              containerName:containerName 
                                                                      limit:limit 
                                                                     marker:marker 
                                                                     prefix:prefix 
                                                                  delimiter:delimiter 
                                                                       path:path 
                                                                       meta:meta 
                                                                     shared:shared 
                                                                      until:untilTimestamp];
    [request addRequestIfUnmodifiedSinceHeader:sinceTimestamp];
    return request;
}


- (NSArray *)objects {
    if (objects == nil) {
        objects = [[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 objects;
}

#pragma mark -
#pragma mark PUT

// PUT storageURL/container
+ (id)createOrUpdateContainerRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName {
    return [self storageRequestWithMethod:@"PUT" pithos:pithos containerName:containerName];
}

// PUT storageURL/container [X-Container-Policy-*] [X-Container-Meta-*]
+ (id)createOrUpdateContainerRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName 
                                        policy:(NSDictionary *)policy 
                                      metadata:(NSDictionary *)metadata {
    ASIPithosContainerRequest *request = [self storageRequestWithMethod:@"PUT" pithos:pithos containerName:containerName];
	
    if (policy) {
        for (NSString *key in policy) {
            [request addRequestHeader:[NSString stringWithFormat:@"X-Container-Policy-%@", key] value:[policy objectForKey:key]];
        }
    }
    
    if (metadata) {
        for (NSString *key in metadata) {
            [request addRequestHeader:[self encodeToPercentEscape:[NSString stringWithFormat:@"X-Container-Meta-%@", key]] 
                                value:[self encodeToPercentEscape:[metadata objectForKey:key]]];
        }
    }
    
	return request;
}

#pragma mark -
#pragma mark POST

// POST storageURL/container
+ (id)updateContainerMetadataRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName {
    return [self storageRequestWithMethod:@"POST" pithos:pithos containerName:containerName];
}

// POST storageURL/container?[update=] [X-Container-Policy-*] [X-Container-Meta-*]
+ (id)updateContainerMetadataRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName 
                                        policy:(NSDictionary *)policy 
                                      metadata:(NSDictionary *)metadata 
                                        update:(BOOL)update {
    NSString *queryString = nil;
    if (update)
        queryString = @"?update=";
    ASIPithosContainerRequest *request = [self storageRequestWithMethod:@"POST" 
                                                                 pithos:pithos 
                                                          containerName:containerName 
                                                            queryString:queryString];
    if (policy) {
        for (NSString *key in policy) {
            [request addRequestHeader:[NSString stringWithFormat:@"X-Container-Policy-%@", key] value:[policy objectForKey:key]];
        }
    }
    
    if (metadata) {
        for (NSString *key in metadata) {
            [request addRequestHeader:[self encodeToPercentEscape:[NSString stringWithFormat:@"X-Container-Meta-%@", key]] 
                                value:[self encodeToPercentEscape:[metadata objectForKey:key]]];
        }
    }
    
	return request;
}

// POST storageURL/container?[update=] [X-Container-Policy-*] [X-Container-Meta-*] (update data from ASIPithosObject with data)
+ (id)updateContainerDataRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName 
                                    policy:(NSDictionary *)policy 
                                  metadata:(NSDictionary *)metadata 
                                    update:(BOOL)update
                                    object:(ASIPithosObject *)object {
    return [self updateContainerDataRequestWithPithos:pithos 
                                        containerName:containerName 
                                               policy:policy 
                                             metadata:metadata 
                                               update:update 
                                                 data:object.data];
}

// POST storageURL/container?[update=] [X-Container-Policy-*] [X-Container-Meta-*] (update data from NSData)
+ (id)updateContainerDataRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName 
                                    policy:(NSDictionary *)policy 
                                  metadata:(NSDictionary *)metadata 
                                    update:(BOOL)update
                                      data:(NSData *)data {
    ASIPithosContainerRequest *request = [self updateContainerMetadataRequestWithPithos:pithos 
                                                                          containerName:containerName 
                                                                                 policy:policy 
                                                                               metadata:metadata 
                                                                                 update:update];    
    [request addRequestHeader:@"Content-Type" value:@"application/octet-stream"];
	[request appendPostData:data];
	return request;
}

// POST storageURL/container?[update=] [X-Container-Policy-*] [X-Container-Meta-*] (update data from file)
+ (id)updateContainerDataRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName 
                                    policy:(NSDictionary *)policy 
                                  metadata:(NSDictionary *)metadata 
                                    update:(BOOL)update
                                      file:(NSString *)filePath {
    ASIPithosContainerRequest *request = [self updateContainerMetadataRequestWithPithos:pithos 
                                                                          containerName:containerName 
                                                                                 policy:policy 
                                                                               metadata:metadata 
                                                                                 update:update];    
    [request addRequestHeader:@"Content-Type" value:@"application/octet-stream"];
	request.shouldStreamPostDataFromDisk = YES;
    request.postBodyFilePath = filePath;
	return request;
}

#pragma mark -
#pragma mark DELETE

// DELETE storageURL/container
+ (id)deleteContainerRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName {
    return [self storageRequestWithMethod:@"DELETE" pithos:pithos containerName:containerName];
}

// DELETE storageURL/container?[until=untilTimestamp]
+ (id)deleteContainerRequestWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName until:(NSDate *)untilTimestamp {
    NSString *queryString = nil;
    if (untilTimestamp)
        queryString = [NSString stringWithFormat:@"?until=%d", (int)[untilTimestamp timeIntervalSince1970]];
	return [self storageRequestWithMethod:@"DELETE" pithos:pithos containerName:containerName queryString:queryString];    
}

#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:@"object"]) {
		self.currentObject = [ASIPithosObject object];
	} else if ([elementName isEqualToString:@"subdir"]) {
		[objects addObject:[ASIPithosObject subdirWithName:[attributeDict valueForKey:@"name"]]];
	}

	self.currentContent = @"";
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
	if ([elementName isEqualToString:@"name"]) {
        currentObject.name = currentContent;
	} else if ([elementName isEqualToString:@"hash"]) {
        currentObject.hash = currentContent;
	} else if ([elementName isEqualToString:@"x_object_hash"]) {
        currentObject.objectHash = currentContent;
	} else if ([elementName isEqualToString:@"x_object_uuid"]) {
        currentObject.UUID = currentContent;
	} else if ([elementName isEqualToString:@"bytes"]) {
        currentObject.bytes = [currentContent integerValue];
	} else if ([elementName isEqualToString:@"content_type"]) {
        currentObject.contentType = currentContent;
    } else if ([elementName isEqualToString:@"last_modified"]) {
        currentObject.lastModified = [[self dateFormatterWithFormatId:0] dateFromString:
         [currentContent stringByReplacingOccurrencesOfString:@":" 
                                                   withString:@"" 
                                                      options:NSBackwardsSearch 
                                                        range:NSMakeRange(([currentContent length] - 3), 1)]];
	} else if ([elementName isEqualToString:@"x_object_version"]) {
        currentObject.version = currentContent;
    } else if ([elementName isEqualToString:@"x_object_version_timestamp"]) {
        currentObject.versionTimestamp = [NSDate dateWithTimeIntervalSince1970:[currentContent doubleValue]];
	} else if ([elementName isEqualToString:@"x_object_modified_by"]) {
        currentObject.modifiedBy = currentContent;
	} else if ([elementName isEqualToString:@"x_object_sharing"]) {
        currentObject.sharing = currentContent;
	} else if ([elementName isEqualToString:@"x_object_shared_by"]) {
        currentObject.sharedBy = currentContent;
	} else if ([elementName isEqualToString:@"x_object_public"]) {
        currentObject.publicURI = currentContent;
	} else if ([elementName isEqualToString:@"x_object_allowed_to"]) {
        currentObject.allowedTo = currentContent;
	} else if ([elementName isEqualToString:@"object"]) {
		[objects addObject:currentObject];
        self.currentObject = nil;
	}
}

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