#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
//
//  DBRestClient.m
//  DropboxSDK
//
//  Created by Brian Smith on 4/9/10.
//  Copyright 2010 Dropbox, Inc. All rights reserved.
//

#import "DBRestClient.h"
#import "DBAccountInfo.h"
#import "DBError.h"
#import "DBMetadata.h"
#import "DBRequest.h"
#import "MPOAuthURLRequest.h"
#import "MPURLRequestParameter.h"
#import "MPOAuthSignatureParameter.h"
#import "NSString+URLEscapingAdditions.h"


NSString* kDBProtocolHTTP = @"http";
NSString* kDBProtocolHTTPS = @"https";


@interface NSString (Addons)
- (NSString *)replace:(NSString *)targetString with:(NSString *)replaceString;
@end
@implementation NSString (Addons)
- (NSString *)replace:(NSString *)targetString with:(NSString *)replaceString {
	NSAutoreleasePool *pool = [NSAutoreleasePool new];
	NSMutableString *temp = [NSMutableString new];
	NSRange replaceRange = NSMakeRange(0, [self length]);
	NSRange rangeInOriginalString = replaceRange;
	int replaced = 0;
	
	while (1) {
		NSRange rangeToCopy;
		NSRange foundRange = [self rangeOfString:targetString options:0 range:rangeInOriginalString];
		if (foundRange.length == 0) break;
		rangeToCopy = NSMakeRange(rangeInOriginalString.location, foundRange.location - rangeInOriginalString.location);	
		[temp appendString:[self substringWithRange:rangeToCopy]];
		[temp appendString:replaceString];
		rangeInOriginalString.length -= NSMaxRange(foundRange) -
		rangeInOriginalString.location;
		rangeInOriginalString.location = NSMaxRange(foundRange);
		replaced++;
		if (replaced % 100 == 0) {
			[pool drain];
			pool = [NSAutoreleasePool new];
		}
	}
	if (rangeInOriginalString.length > 0) [temp appendString:[self substringWithRange:rangeInOriginalString]];
	[pool drain];
	
	return [temp autorelease];
}
@end

@interface DBRestClient ()

// This method escapes all URI escape characters except /
+ (NSString*)escapePath:(NSString*)path;

- (NSMutableURLRequest*)requestWithProtocol:(NSString*)protocol host:(NSString*)host path:(NSString*)path 
    parameters:(NSDictionary*)params;

- (NSMutableURLRequest*)requestWithProtocol:(NSString*)protocol host:(NSString*)host path:(NSString*)path 
    parameters:(NSDictionary*)params method:(NSString*)method;

- (void)checkForAuthenticationFailure:(DBRequest*)request;

@end


@implementation DBRestClient

- (id)initWithSession:(DBSession*)aSession {
    return [self initWithSession:aSession root:@"dropbox"];
}

- (id)initWithSession:(DBSession*)aSession root:(NSString*)aRoot {
    if ((self = [super init])) {
        session = [aSession retain];
        root = [aRoot retain];
        requests = [[NSMutableSet alloc] init];
        loadRequests = [[NSMutableDictionary alloc] init];
    }
    return self;
}


- (void)dealloc {
	NSArray *requestsArray = [requests allObjects];
    for (int i=0; i<[requestsArray count]; i++) {
        [[requestsArray objectAtIndex:i] cancel];
    }
    [requests release];
    for (int i=0; i<[[loadRequests allValues] count]; i++) {
        [[[loadRequests allValues] objectAtIndex:i] cancel];
    }
    [loadRequests release];
    [session release];
    [root release];
    [super dealloc];
}

- (void)setDelegate:(id)theDelegate {
	delegate = theDelegate;
}
- (id<DBRestClientDelegate>)delegate {
	return delegate;
}

- (void)loginWithEmail:(NSString*)email password:(NSString*)password {
    NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys:
            email, @"email",
            password, @"password", nil];

    NSURLRequest* urlRequest = [self requestWithProtocol:kDBProtocolHTTPS host:kDBDropboxAPIHost 
            path:@"/token" parameters:params];

    DBRequest* request = 
        [[[DBRequest alloc] 
          initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidLogin:)]
         autorelease];

    [requests addObject:request];
}



- (void)requestDidLogin:(DBRequest*)request {
    if (request.error) {
        if ([delegate respondsToSelector:@selector(restClient:loginFailedWithError:)]) {
            [delegate restClient:self loginFailedWithError:request.error];
        }
    } else {
        NSDictionary* result = (NSDictionary*)request.resultJSON;
        NSString* token = [result objectForKey:@"token"];
        NSString* secret = [result objectForKey:@"secret"];
        [session updateAccessToken:token accessTokenSecret:secret];
        if ([delegate respondsToSelector:@selector(restClientDidLogin:)]) {
            [delegate restClientDidLogin:self];
        }
    }

    [requests removeObject:request];
}



- (void)loadMetadata:(NSString*)path withHash:(NSString*)hash
{
    NSDictionary* params = nil;
    if (hash) {
        params = [NSDictionary dictionaryWithObject:hash forKey:@"hash"];
    }
    
    NSString* fullPath = [NSString stringWithFormat:@"/metadata/%@%@", root, path];
    NSURLRequest* urlRequest = 
        [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:fullPath parameters:params];
    
    DBRequest* request = 
        [[[DBRequest alloc] 
          initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidLoadMetadata:)]
         autorelease];
    
    request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:root, @"root", path, @"path", nil];

    [requests addObject:request];
}

- (void)loadMetadata:(NSString*)path
{
    [self loadMetadata:path withHash:nil];
}


- (void)requestDidLoadMetadata:(DBRequest*)request
{
    if (request.statusCode == 304) {
        if ([delegate respondsToSelector:@selector(restClient:metadataUnchangedAtPath:)]) {
            NSString* path = [request.userInfo objectForKey:@"path"];
            [delegate restClient:self metadataUnchangedAtPath:path];
        }
    } else if (request.error) {
        [self checkForAuthenticationFailure:request];
        if ([delegate respondsToSelector:@selector(restClient:loadMetadataFailedWithError:)]) {
            [delegate restClient:self loadMetadataFailedWithError:request.error];
        }
    } else {
		[NSThread detachNewThreadSelector:@selector(parseMetadataWithRequest:) toTarget:self withObject:request];
    }

    [requests removeObject:request];
}


- (void)parseMetadataWithRequest:(DBRequest*)request {
    NSAutoreleasePool* pool = [NSAutoreleasePool new];
    
    NSDictionary* result = (NSDictionary*)[request resultJSON];
    DBMetadata* metadata = [[[DBMetadata alloc] initWithDictionary:result] autorelease];
    [self performSelectorOnMainThread:@selector(didParseMetadata:) withObject:metadata waitUntilDone:NO];
    
    [pool drain];
}


- (void)didParseMetadata:(DBMetadata*)metadata {
    if ([delegate respondsToSelector:@selector(restClient:loadedMetadata:)]) {
        [delegate restClient:self loadedMetadata:metadata];
    }
}


- (void)loadFile:(NSString *)path intoPath:(NSString *)destinationPath
{
    NSString* fullPath = [NSString stringWithFormat:@"/files/%@%@", root, path];
    
    NSURLRequest* urlRequest = 
        [self requestWithProtocol:kDBProtocolHTTPS host:kDBDropboxAPIContentHost path:fullPath parameters:nil];
    DBRequest* request = 
        [[[DBRequest alloc] 
          initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidLoadFile:)]
         autorelease];
    request.resultFilename = destinationPath;
    request.downloadProgressSelector = @selector(requestLoadProgress:);
    request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
            root, @"root", 
            path, @"path", 
            destinationPath, @"destinationPath", nil];
    [loadRequests setObject:request forKey:path];
}


- (void)cancelFileLoad:(NSString*)path {
    DBRequest* outstandingRequest = [loadRequests objectForKey:path];
    if (outstandingRequest) {
        [outstandingRequest cancel];
        [loadRequests removeObjectForKey:path];
    }
}


- (void)requestLoadProgress:(DBRequest*)request {
    if ([delegate respondsToSelector:@selector(restClient:loadProgress:forFile:)]) {
        [delegate restClient:self loadProgress:request.downloadProgress forFile:request.resultFilename];
    }
}


- (void)restClient:(DBRestClient*)restClient loadedFile:(NSString*)destPath
contentType:(NSString*)contentType eTag:(NSString*)eTag {
	// Empty selector to get the signature from
}

- (void)requestDidLoadFile:(DBRequest*)request {
    NSString* path = [request.userInfo objectForKey:@"path"];
    
    if (request.error) {
        [self checkForAuthenticationFailure:request];
        if ([delegate respondsToSelector:@selector(restClient:loadFileFailedWithError:)]) {
            [delegate restClient:self loadFileFailedWithError:request.error];
        }
    } else {
        NSString* filename = request.resultFilename;
        NSDictionary* headers = [request.response allHeaderFields];
        NSString* contentType = [headers objectForKey:@"Content-Type"];
        NSString* eTag = [headers objectForKey:@"Etag"];
        if ([delegate respondsToSelector:@selector(restClient:loadedFile:)]) {
            [delegate restClient:self loadedFile:filename];
        } else if ([delegate respondsToSelector:@selector(restClient:loadedFile:contentType:)]) {
            [delegate restClient:self loadedFile:filename contentType:contentType];
        } else if ([delegate respondsToSelector:@selector(restClient:loadedFile:contentType:eTag:)]) {
			// This code is for the official Dropbox client to get eTag information from the server
			NSMethodSignature* signature = 
				[self methodSignatureForSelector:@selector(restClient:loadedFile:contentType:eTag:)];
			NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];
			[invocation setTarget:delegate];
			[invocation setSelector:@selector(restClient:loadedFile:contentType:eTag:)];
			[invocation setArgument:&self atIndex:2];
			[invocation setArgument:&filename atIndex:3];
			[invocation setArgument:&contentType atIndex:4];
			[invocation setArgument:&eTag atIndex:5];
			[invocation invoke];
        }
    }

    [loadRequests removeObjectForKey:path];
}



- (void)loadThumbnail:(NSString *)path ofSize:(NSString *)size intoPath:(NSString *)destinationPath 
{
    NSString* fullPath = [NSString stringWithFormat:@"/thumbnails/%@%@", root, path];
    NSDictionary *params = nil;

    if(size) {
        params = [NSDictionary dictionaryWithObjectsAndKeys: size, @"size", nil];
    }
    
    NSURLRequest* urlRequest = 
        [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIContentHost path:fullPath parameters:params];

    DBRequest* request = 
        [[[DBRequest alloc] 
          initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidLoadThumbnail:)]
         autorelease];

    request.resultFilename = destinationPath;
    request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
            root, @"root", 
            path, @"path", 
            destinationPath, @"destinationPath", nil];
    [requests addObject:request];
}



- (void)requestDidLoadThumbnail:(DBRequest*)request
{
    if (request.error) {
        [self checkForAuthenticationFailure:request];
        if ([delegate respondsToSelector:@selector(restClient:loadThumbnailFailedWithError:)]) {
            [delegate restClient:self loadThumbnailFailedWithError:request.error];
        }
    } else {
        if ([delegate respondsToSelector:@selector(restClient:loadedThumbnail:)]) {
            [delegate restClient:self loadedThumbnail:request.resultFilename];
        }
    }

    [requests removeObject:request];
}




NSString *createFakeSignature(DBSession *session, NSArray *params, NSString *filename, NSURL *baseUrl)
{
    NSArray* extraParams = [MPURLRequestParameter parametersFromDictionary:
            [NSDictionary dictionaryWithObject:filename forKey:@"file"]];
    
    NSMutableArray* paramList = [NSMutableArray arrayWithArray:params];
    [paramList addObjectsFromArray:extraParams];
    [paramList sortUsingSelector:@selector(compare:)];
    NSString* paramString = [MPURLRequestParameter parameterStringForParameters:paramList];
    
    MPOAuthURLRequest* oauthRequest = 
        [[[MPOAuthURLRequest alloc] initWithURL:baseUrl andParameters:paramList] autorelease];
    oauthRequest.HTTPMethod = @"POST";
    MPOAuthSignatureParameter *signatureParameter = 
        [[[MPOAuthSignatureParameter alloc] 
                initWithText:paramString andSecret:session.credentialStore.signingKey 
                forRequest:oauthRequest usingMethod:session.credentialStore.signatureMethod]
          autorelease];

    return [signatureParameter URLEncodedParameterString];
}

NSMutableURLRequest *createRealRequest(DBSession *session, NSArray *params, NSString *urlString, NSString *signatureText)
{
    NSMutableArray *paramList = [NSMutableArray arrayWithArray:params];
    // Then rebuild request using that signature
    [paramList sortUsingSelector:@selector(compare:)];
    NSMutableString* realParamString = [[[NSMutableString alloc] initWithString:
            [MPURLRequestParameter parameterStringForParameters:paramList]]
            autorelease];
    [realParamString appendFormat:@"&%@", signatureText];
    
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?%@", urlString, realParamString]];
    NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url];
    urlRequest.HTTPMethod = @"POST";

    return urlRequest;
}

// Returns DBErrorNone if no errors were encountered
DBErrorCode addFileUploadToRequest(NSMutableURLRequest *urlRequest, NSString *filename, NSString *sourcePath)
{
    // Create input stream
    CFUUIDRef uuid = CFUUIDCreate(NULL);
    NSString* stringBoundary = [(NSString*)CFUUIDCreateString(NULL, uuid) autorelease];
    CFRelease(uuid);

    NSString* contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",stringBoundary];
    [urlRequest addValue:contentType forHTTPHeaderField: @"Content-Type"];
    
    NSString* tempFilename = 
        [NSString stringWithFormat: @"%.0f.txt", [NSDate timeIntervalSinceReferenceDate] * 1000.0];
    NSString *tempFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:tempFilename];

    //setting up the body
    NSMutableData* bodyData = [NSMutableData data];
    [bodyData appendData:
            [[NSString stringWithFormat:@"--%@\r\n", stringBoundary] 
             dataUsingEncoding:NSUTF8StringEncoding]];

    // Add data to upload
    [bodyData appendData:
            [[NSString stringWithFormat:
                @"Content-Disposition: form-data; name=\"file\"; filename=\"%@\"\r\n", filename] 
             dataUsingEncoding:NSUTF8StringEncoding]];
    [bodyData appendData:
            [[NSString stringWithString:@"Content-Type: application/octet-stream\r\n\r\n"] 
             dataUsingEncoding:NSUTF8StringEncoding]];
             
    if (![[NSFileManager defaultManager] createFileAtPath:tempFilePath contents:bodyData attributes:nil]) {
        NSLog(@"DBRestClient#uploadFileToRoot:path:filename:fromPath: failed to create file");
        return DBErrorGenericError;
    }

    NSFileHandle* bodyFile = [NSFileHandle fileHandleForWritingAtPath:tempFilePath];
    [bodyFile seekToEndOfFile];

    if ([[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) {
        NSFileHandle* readFile = [NSFileHandle fileHandleForReadingAtPath:sourcePath];
        NSData* readData;
        while ((readData = [readFile readDataOfLength:1024 * 512]) != nil && [readData length] > 0) {
            @try {
                [bodyFile writeData:readData];
            } @catch (NSException* e) {
                NSLog(@"DBRestClient#uploadFileToRoot:path:filename:fromPath: failed to write data");
                [readFile closeFile];
                [bodyFile closeFile];
                NSFileManager *manager = [NSFileManager defaultManager];
				if ([manager respondsToSelector:@selector(removeFileAtPath:handler:)])
					[manager removeFileAtPath:tempFilePath handler:nil];
				else
					[manager removeItemAtPath:tempFilePath error:nil];
                return DBErrorInsufficientDiskSpace;
            }
        }
        [readFile closeFile];
    } else {
        NSLog(@"DBRestClient#uploadFileToRoot:path:filename:fromPath: unable to open sourceFile");
    }
    
    @try {
        [bodyFile writeData:
                [[NSString stringWithFormat:@"\r\n--%@--\r\n", stringBoundary] 
                 dataUsingEncoding:NSUTF8StringEncoding]];
    } @catch (NSException* e) {
        NSLog(@"DBRestClient#uploadFileToRoot:path:filename:fromPath: failed to write end of data");
        [bodyFile closeFile];
        NSFileManager *manager = [NSFileManager defaultManager];
		if ([manager respondsToSelector:@selector(removeFileAtPath:handler:)])
			[manager removeFileAtPath:tempFilePath handler:nil];
		else
			[manager removeItemAtPath:tempFilePath error:nil];
        return DBErrorInsufficientDiskSpace;
    }
    
    NSString* contentLength = [NSString stringWithFormat: @"%qu", [bodyFile offsetInFile]];
    [urlRequest addValue:contentLength forHTTPHeaderField: @"Content-Length"];    
    [bodyFile closeFile];
	
    urlRequest.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:tempFilePath];

    return DBErrorNone;
}



- (void)uploadFile:(NSString*)filename toPath:(NSString*)path fromPath:(NSString *)sourcePath
{
    if (![[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) {
        NSDictionary* userInfo = [NSDictionary dictionaryWithObject:sourcePath forKey:@"sourcePath"];
        NSError* error = 
            [NSError errorWithDomain:DBErrorDomain code:DBErrorFileNotFound userInfo:userInfo];
        if ([delegate respondsToSelector:@selector(restClient:uploadFileFailedWithError:)]) {
            [delegate restClient:self uploadFileFailedWithError:error];
        }
        return;
    }

    // path is the directory the file will be uploaded to, make sure it doesn't have a trailing /
    // (unless it's the root dir) and is properly escaped
    NSString* trimmedPath;
    if ([path length] > 1 && [path characterAtIndex:[path length]-1] == '/') {
        trimmedPath = [path substringToIndex:[path length]-1];
    } else {
        trimmedPath = path;
    }
    NSString* escapedPath = [DBRestClient escapePath:trimmedPath];
    
    NSString* urlString = [NSString stringWithFormat:@"%@://%@/%@/files/%@%@", 
            kDBProtocolHTTPS, kDBDropboxAPIContentHost, kDBDropboxAPIVersion, root, escapedPath];
    NSURL* baseUrl = [NSURL URLWithString:urlString];
    NSArray* params = [session.credentialStore oauthParameters];

    NSString *escapedFilename = [filename replace:@";" with:@"-"];

    NSString *signatureText = createFakeSignature(session, params, escapedFilename, baseUrl);

    NSMutableURLRequest *urlRequest = createRealRequest(session, params, urlString, signatureText);
   
    DBErrorCode errorCode = addFileUploadToRequest(urlRequest, escapedFilename, sourcePath);
    if(errorCode == DBErrorNone) {
        DBRequest* request = 
            [[[DBRequest alloc] 
              initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidUploadFile:)]
             autorelease];
        request.uploadProgressSelector = @selector(requestUploadProgress:);
        NSString* dropboxPath = [path stringByAppendingPathComponent:filename];
        request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                root, @"root", 
                path, @"path",
                dropboxPath, @"destinationPath",
                sourcePath, @"sourcePath", nil];
        [requests addObject:request];
    } else {
        NSDictionary* userInfo = [NSDictionary dictionaryWithObject:sourcePath forKey:@"sourcePath"];
        NSError* error = 
            [NSError errorWithDomain:DBErrorDomain code:errorCode userInfo:userInfo];
        if ([delegate respondsToSelector:@selector(restClient:uploadFileFailedWithError:)]) {
            [delegate restClient:self uploadFileFailedWithError:error];
        }
    }
}


- (void)requestUploadProgress:(DBRequest*)request {
    NSString* sourcePath = [(NSDictionary*)request.userInfo objectForKey:@"sourcePath"];
    NSString* destPath = [request.userInfo objectForKey:@"destinationPath"];

    if ([delegate respondsToSelector:@selector(restClient:uploadProgress:forFile:from:)]) {
        [delegate restClient:self uploadProgress:request.uploadProgress
                    forFile:destPath from:sourcePath];
    } else if ([delegate respondsToSelector:@selector(restClient:uploadProgress:forFile:)]) {
        [delegate restClient:self uploadProgress:request.uploadProgress forFile:sourcePath];
    }
}


- (void)requestDidUploadFile:(DBRequest*)request {
    if (request.error) {
        [self checkForAuthenticationFailure:request];
        if ([delegate respondsToSelector:@selector(restClient:uploadFileFailedWithError:)]) {
            [delegate restClient:self uploadFileFailedWithError:request.error];
        }
    } else {
        NSString* sourcePath = [(NSDictionary*)request.userInfo objectForKey:@"sourcePath"];
        NSString* destPath = [request.userInfo objectForKey:@"destinationPath"];
        if ([delegate respondsToSelector:@selector(restClient:uploadedFile:from:)]) {
            [delegate restClient:self uploadedFile:destPath from:sourcePath];
        } else if ([delegate respondsToSelector:@selector(restClient:uploadedFile:)]) {
            [delegate restClient:self uploadedFile:sourcePath];
        }
    }

    [requests removeObject:request];
}



- (void)moveFrom:(NSString*)from_path toPath:(NSString *)to_path
{
    NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys:
            root, @"root",
            from_path, @"from_path",
            to_path, @"to_path", nil];
            
    NSMutableURLRequest* urlRequest = 
        [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:@"/fileops/move"
                parameters:params method:@"POST"];

    DBRequest* request = 
        [[[DBRequest alloc] 
          initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidMovePath:)]
         autorelease];

    request.userInfo = params;
    [requests addObject:request];
}



- (void)requestDidMovePath:(DBRequest*)request {
    if (request.error) {
        [self checkForAuthenticationFailure:request];
        if ([delegate respondsToSelector:@selector(restClient:movePathFailedWithError:)]) {
            [delegate restClient:self movePathFailedWithError:request.error];
        }
    } else {
        NSDictionary *params = (NSDictionary *)request.userInfo;

        if ([delegate respondsToSelector:@selector(restClient:movedPath:toPath:)]) {
            [delegate restClient:self movedPath:[params valueForKey:@"from_path"] 
                        toPath:[params valueForKey:@"to_path"]];
        }
    }

    [requests removeObject:request];
}


- (void)copyFrom:(NSString*)from_path toPath:(NSString *)to_path
{
    NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys:
            root, @"root",
            from_path, @"from_path",
            to_path, @"to_path", nil];
            
    NSMutableURLRequest* urlRequest = 
        [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:@"/fileops/copy"
                parameters:params method:@"POST"];

    DBRequest* request = 
        [[[DBRequest alloc] 
          initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidCopyPath:)]
         autorelease];

    request.userInfo = params;
    [requests addObject:request];
}



- (void)requestDidCopyPath:(DBRequest*)request {
    if (request.error) {
        [self checkForAuthenticationFailure:request];
        if ([delegate respondsToSelector:@selector(restClient:copyPathFailedWithError:)]) {
            [delegate restClient:self copyPathFailedWithError:request.error];
        }
    } else {
        NSDictionary *params = (NSDictionary *)request.userInfo;

        if ([delegate respondsToSelector:@selector(restClient:copiedPath:toPath:)]) {
            [delegate restClient:self copiedPath:[params valueForKey:@"from_path"] 
                        toPath:[params valueForKey:@"to_path"]];
        }
    }

    [requests removeObject:request];
}


- (void)deletePath:(NSString*)path
{
    NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys:
            root, @"root",
            path, @"path", nil];
            
    NSMutableURLRequest* urlRequest = 
        [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:@"/fileops/delete" 
                parameters:params method:@"POST"];

    DBRequest* request = 
        [[[DBRequest alloc] 
          initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidDeletePath:)]
         autorelease];

    request.userInfo = params;
    [requests addObject:request];
}



- (void)requestDidDeletePath:(DBRequest*)request {
    if (request.error) {
        [self checkForAuthenticationFailure:request];
        if ([delegate respondsToSelector:@selector(restClient:deletePathFailedWithError:)]) {
            [delegate restClient:self deletePathFailedWithError:request.error];
        }
    } else {
        if ([delegate respondsToSelector:@selector(restClient:deletedPath:)]) {
            NSString* path = [request.userInfo objectForKey:@"path"];
            [delegate restClient:self deletedPath:path];
        }
    }

    [requests removeObject:request];
}




- (void)createFolder:(NSString*)path
{
    NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys:
            root, @"root",
            path, @"path", nil];
            
    NSString* fullPath = @"/fileops/create_folder";
    NSMutableURLRequest* urlRequest = 
        [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:fullPath 
                parameters:params method:@"POST"];
    DBRequest* request = 
        [[[DBRequest alloc] 
          initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidCreateDirectory:)]
         autorelease];
    request.userInfo = params;
    [requests addObject:request];
}



- (void)requestDidCreateDirectory:(DBRequest*)request {
    if (request.error) {
        [self checkForAuthenticationFailure:request];
        if ([delegate respondsToSelector:@selector(restClient:createFolderFailedWithError:)]) {
            [delegate restClient:self createFolderFailedWithError:request.error];
        }
    } else {
        NSDictionary* result = (NSDictionary*)[request resultJSON];
        DBMetadata* metadata = [[[DBMetadata alloc] initWithDictionary:result] autorelease];
        if ([delegate respondsToSelector:@selector(restClient:createdFolder:)]) {
            [delegate restClient:self createdFolder:metadata];
        }
    }

    [requests removeObject:request];
}



- (void)loadAccountInfo
{
    NSURLRequest* urlRequest = 
        [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:@"/account/info" parameters:nil];
    
    DBRequest* request = 
        [[[DBRequest alloc] 
          initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidLoadAccountInfo:)]
         autorelease];
    
    request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:root, @"root", nil];

    [requests addObject:request];
}


- (void)requestDidLoadAccountInfo:(DBRequest*)request
{
    if (request.error) {
        [self checkForAuthenticationFailure:request];
        if ([delegate respondsToSelector:@selector(restClient:loadAccountInfoFailedWithError:)]) {
            [delegate restClient:self loadAccountInfoFailedWithError:request.error];
        }
    } else {
        NSDictionary* result = (NSDictionary*)[request resultJSON];
        DBAccountInfo* accountInfo = [[[DBAccountInfo alloc] initWithDictionary:result] autorelease];
        if ([delegate respondsToSelector:@selector(restClient:loadedAccountInfo:)]) {
            [delegate restClient:self loadedAccountInfo:accountInfo];
        }
    }

    [requests removeObject:request];
}

- (void)createAccount:(NSString *)email password:(NSString *)password firstName:(NSString *)firstName lastName:(NSString *)lastName
{
    NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys:
                            email, @"email",
                            password, @"password",
                            firstName, @"first_name",
                            lastName, @"last_name", nil];
    
    NSString* fullPath = @"/account";
    NSMutableURLRequest* urlRequest = 
    [self requestWithProtocol:kDBProtocolHTTPS host:kDBDropboxAPIHost path:fullPath 
                   parameters:params method:@"POST"];
    
    DBRequest* request = 
        [[[DBRequest alloc] 
          initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidCreateAccount:)]
            autorelease];

    request.userInfo = params;
    
    [requests addObject:request];
}

- (void)requestDidCreateAccount:(DBRequest *)request
{
    if(request.error) {
        if([delegate respondsToSelector:@selector(restClient:createAccountFailedWithError:)]) {
            [delegate restClient:self createAccountFailedWithError:request.error];
        }
    } else {
        if ([delegate respondsToSelector:@selector(restClientCreatedAccount:)]) {
            [delegate restClientCreatedAccount:self];
        }
    }
    
    [requests removeObject:request];
}


#pragma mark private methods

+ (NSString*)escapePath:(NSString*)path {
    CFStringEncoding encoding = CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding);
    NSString *escapedPath = 
        (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                            (CFStringRef)path,
                                                            NULL,
                                                            (CFStringRef)@":?=,!$&'()*+;[]@#~",
                                                            encoding);
    
    return [escapedPath autorelease];
}


- (NSMutableURLRequest*)requestWithProtocol:(NSString*)protocol host:(NSString*)host path:(NSString*)path 
    parameters:(NSDictionary*)params {
    
    return [self requestWithProtocol:protocol host:host path:path parameters:params method:nil];
}


- (NSMutableURLRequest*)requestWithProtocol:(NSString*)protocol host:(NSString*)host path:(NSString*)path 
    parameters:(NSDictionary*)params method:(NSString*)method {
    
    NSString* escapedPath = [DBRestClient escapePath:path];
    NSString* urlString = [NSString stringWithFormat:@"%@://%@/%@%@", 
                                        protocol, host, kDBDropboxAPIVersion, escapedPath];
    NSURL* url = [NSURL URLWithString:urlString];
    
    NSArray* paramList = [session.credentialStore oauthParameters];
    if ([params count] > 0) {
        NSArray* extraParams = [MPURLRequestParameter parametersFromDictionary:params];
        paramList = [paramList arrayByAddingObjectsFromArray:extraParams];
    }
    MPOAuthURLRequest* oauthRequest = 
        [[[MPOAuthURLRequest alloc] initWithURL:url andParameters:paramList] autorelease];
    if (method) {
        oauthRequest.HTTPMethod = method;
    }
    NSMutableURLRequest* urlRequest = [oauthRequest 
            urlRequestSignedWithSecret:session.credentialStore.signingKey 
            usingMethod:session.credentialStore.signatureMethod];
    return urlRequest;
}


- (void)checkForAuthenticationFailure:(DBRequest*)request {
    if (request.error && request.error.code == 401 && [request.error.domain isEqual:@"dropbox.com"]) {
        [session.delegate sessionDidReceiveAuthorizationFailure:session];
    }
}

@end
