//
//  OAuth.m
//  Afficheur
//
//  Created by kichi on 09/03/29.
//  Copyright 2009 Katsuhiko Ichinose. All rights reserved.
//

#import "OAuth.h"
#import "NSStringOgreKit.h"
#import <OgreKit/OgreKit.h>
#import <openssl/hmac.h>


@implementation OAuth

- (id)init
{
	self = [super init];
	if (self)
	{
		_comsumer_key = nil;
		_comsumer_secret = nil;
		_request_token = nil;
		_request_token_secret = nil;
		_access_token = nil;
		_access_token_secret = nil;
		
		_request_token_path = nil;
		_access_token_path = nil;
		_authorize_path = nil;
	}
	return self;
}

- (void)dealloc
{
	if (_comsumer_key)
	{
		[_comsumer_key release];
	}
	if (_comsumer_secret)
	{
		[_comsumer_secret release];
	}
	if (_request_token)
	{
		[_request_token release];
	}
	if (_request_token_secret)
	{
		[_request_token_secret release];
	}
	if (_access_token)
	{
		[_access_token release];
	}
	if (_access_token_secret)
	{
		[_access_token_secret release];
	}
	if (_request_token_path)
	{
		[_request_token_path release];
	}
	if (_access_token_path)
	{
		[_access_token_path release];
	}
	if (_authorize_path)
	{
		[_authorize_path release];
	}
	[super dealloc];
}

- (void)setConsumerKey:(NSString *)consumer_key
{
	if (_comsumer_key)
	{
		[_comsumer_key release];
	}
	if (consumer_key)
	{
		_comsumer_key = [consumer_key copy];
	}
	else
	{
		_comsumer_key = nil;
	}
}

- (void)setConsumerSecret:(NSString *)consumer_secret
{
	if (_comsumer_secret)
	{
		[_comsumer_secret release];
	}
	if (consumer_secret)
	{
		_comsumer_secret = [consumer_secret copy];
	}
	else
	{
		_comsumer_secret = nil;
	}
}

- (NSString *)requestToken
{
	return [[_request_token copy] autorelease];
}

- (NSString *)requestTokenSecret
{
	return [[_request_token_secret copy] autorelease];
}

- (void)setRequestToken:(NSString *)request_token
{
	if (_request_token)
	{
		[_request_token release];
	}
	_request_token = [request_token copy];
}

- (void)setRequestTokenSecret:(NSString *)request_token_secret
{
	if (_request_token_secret)
	{
		[_request_token_secret release];
	}
	if (request_token_secret)
	{
		_request_token_secret = [request_token_secret copy];
	}
	else
	{
		_request_token_secret = nil;
	}
}

- (NSString *)accessToken
{
	return [[_access_token copy] autorelease];
}

- (NSString *)accessTokenSecret
{
	return [[_access_token_secret copy] autorelease];
}

- (void)setAccessToken:(NSString *)access_token
{
	if (_access_token)
	{
		[_access_token release];
	}
	if (access_token)
	{
		_access_token = [access_token copy];
	}
	else
	{
		_access_token = nil;
	}
}

- (void)setAccessTokenSecret:(NSString *)access_token_secret
{
	if (_access_token_secret)
	{
		[_access_token_secret release];
	}
	_access_token_secret = [access_token_secret copy];
}

- (void)setRequestTokenPath:(NSString *)request_token_path
{
	if (_request_token_path)
	{
		[_request_token_path release];
	}
	if (request_token_path)
	{
		_request_token_path = [request_token_path copy];
	}
	else
	{
		_request_token_path = nil;
	}
}

- (void)setAccessTokenPath:(NSString *)access_token_path
{
	if (_access_token_path)
	{
		[_access_token_path release];
	}
	if (access_token_path)
	{
		_access_token_path = [access_token_path copy];
	}
	else
	{
		_access_token_path = nil;
	}
}

- (void)setAuthorizePath:(NSString *)authorize_path
{
	if (_authorize_path)
	{
		[_authorize_path release];
	}
	if (authorize_path)
	{
		_authorize_path = [authorize_path copy];
	}
	else
	{
		_authorize_path = nil;
	}
}

- (long)nonce:(NSDate *)date
{
	return (((unsigned long)[date timeIntervalSince1970]) * 1000) +
	(unsigned long)[[date descriptionWithCalendarFormat:@"%F"
											   timeZone:nil
												 locale:nil] intValue];
}

- (unsigned long)timestamp:(NSDate *)date
{
	return (unsigned long)[date timeIntervalSince1970];
}

- (NSString*)stringLower:(OGRegularExpressionMatch *)aMatch
			 contextInfo:(id)contextInfo
{
	NSString *match = [aMatch matchedString];
	//LOG(@"[%@ stringLower] match: %@", [self className], match);
	@try
	{
		match = [match lowercaseString];
		//LOG(@"[%@ stringLower] lower: %@", [self className], match);
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ stringLower] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return match;
}

- (NSString *)stringUrlLower:(NSString *)string
{
	LOG(@"[%@ stringUrlLower] %@", [self className], string);
	@synchronized([NSApplication sharedApplication])
	{
		NSString *exp = @"\%[0-9A-Z][0-9A-Z]";
		OGRegularExpression *regex = [OGRegularExpression
									  regularExpressionWithString:exp
									  options:OgreMultilineOption];
		string = [regex replaceAllMatchesInString:string
										 delegate:self
								  replaceSelector:@selector(stringLower:contextInfo:)
									  contextInfo:nil];
	}
	return string;
}

- (NSString*)stringUrlUpper:(OGRegularExpressionMatch *)aMatch
				contextInfo:(id)contextInfo
{
	NSString *match = [aMatch matchedString];
	//LOG(@"[%@ stringLower] match: %@", [self className], match);
	@try
	{
		match = [match uppercaseString];
		//LOG(@"[%@ stringLower] lower: %@", [self className], match);
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ stringUrlUpper] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return match;
}

- (NSString *)stringUrlUpper:(NSString *)string
{
	LOG(@"[%@ stringUrlUpper] %@", [self className], string);
	@synchronized([NSApplication sharedApplication])
	{
		NSString *exp = @"\%[0-9a-z][0-9a-z]";
		OGRegularExpression *regex = [OGRegularExpression
									  regularExpressionWithString:exp
									  options:OgreMultilineOption];
		string = [regex replaceAllMatchesInString:string
										 delegate:self
								  replaceSelector:@selector(stringUrlUpper:contextInfo:)
									  contextInfo:nil];
	}
	return string;
}

- (NSDictionary *)makeHeaderWithMethod:(NSString *)method
								   url:(NSString *)url
								header:(NSDictionary *)header
								  body:(NSString *)body
						   tokenSecret:(NSString *)tokenSecret
{
	//LOG(@"[%@ makeHeader]", [self className]);
	NSArray *urls = [url componentsSeparatedByString:@"?"];
	NSString *auth = [header valueForKey:@"Authorization"];
	NSArray *params = [auth componentsSeparatedByString:@","];
	NSMutableDictionary *headers = [NSMutableDictionary dictionary];
	NSEnumerator *enumerator = [params objectEnumerator];
	id obj;
	while ((obj = [enumerator nextObject]))
	{
		NSString *name = [obj replaceWithExpression:@"^.*?(\\S+)=\"(.*)\".*$"
											replace:@"\\1"];
		NSString *value = [obj replaceWithExpression:@"^.*?(\\S+)=\"(.*)\".*$"
											 replace:@"\\2"];
		//LOG(@"[%@ makeHeader] header\n'%@'\n%@=%@", [self className], obj, name, value);
		[headers setValue:value forKey:name];
	}
	NSMutableDictionary *bodys = [NSMutableDictionary dictionary];
	params = [body componentsSeparatedByString:@"&"];
	enumerator = [params objectEnumerator];
	while ((obj = [enumerator nextObject]))
	{
		if ([obj length] >= 3)
		{
			NSString *name = [obj replaceWithExpression:@"^.*?(\\S+)=(.*)$"
												replace:@"\\1"];
			NSString *value = [obj replaceWithExpression:@"^.*?(\\S+)=(.*)$"
												 replace:@"\\2"];
			value = [value replaceWithExpression:@"\\+"
										 replace:@" "];
			value = [HTTP urlEncode:value unescaped:@"%-._~" escaped:@" !@#$^&*()`+=[]{}\\|;:'\"<>?,/"];
			//LOG(@"[%@ makeHeader] body\n'%@'\n%@=%@", [self className], obj, name, value);
			[bodys setValue:value forKey:name];
		}
	}
	if ([urls count] > 1)
	{
		params = [[urls objectAtIndex:1] componentsSeparatedByString:@"&"];
		enumerator = [params objectEnumerator];
		while ((obj = [enumerator nextObject]))
		{
			if ([obj length] >= 3)
			{
				NSString *name = [obj replaceWithExpression:@"^.*?(\\S+)=(.*)$"
													replace:@"\\1"];
				NSString *value = [obj replaceWithExpression:@"^.*?(\\S+)=(.*)$"
													 replace:@"\\2"];
				value = [value replaceWithExpression:@"\\+"
											 replace:@" "];
				value = [HTTP urlEncode:value unescaped:@"%-._~" escaped:@" !@#$^&*()`+=[]{}\\|;:'\"<>?,/"];
				//LOG(@"[%@ makeHeader] url\n'%@'\n%@=%@", [self className], obj, name, value);
				[bodys setValue:value forKey:name];
			}
		}
	}
	NSString *oauth = @"";
	NSString *amp = @"";
	NSMutableArray *keys = [NSMutableArray arrayWithArray:[headers allKeys]];
	[keys addObjectsFromArray:[bodys allKeys]];
	params = [keys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
	enumerator = [params objectEnumerator];
	while ((obj = [enumerator nextObject]))
	{
		if (![obj isEqualToString:@"realm"] && ![obj isEqualToString:@"oauth_signature"])
		{
			id value = [headers valueForKey:obj];
			if (!value)
			{
				value = [bodys valueForKey:obj];
			}
			//LOG(@"[%@ makeHeader]\n%@: %@", [self className], obj, value);
			oauth = [oauth stringByAppendingFormat:@"%@%@=%@", amp, obj, value];
			amp = @"&";
		}
	}
	//LOG(@"[%@ makeHeader]\n%@", [self className], oauth);
	NSString *param = [NSString stringWithFormat:
					   @"%@&%@&%@",
					   method,
					   [HTTP urlEncode:[urls objectAtIndex:0]
							 unescaped:@"-._~"
							   escaped:@" !@#$^&*()`+=[]{}\\|;:'\"<>?,/"],
					   [HTTP urlEncode:oauth
							 unescaped:@"-._~"
							   escaped:@" !@#$^&*()`+=[]{}\\|;:'\"<>?,/"]];
	NSString *sha1_key;
	if (tokenSecret)
	{
		sha1_key = [NSString stringWithFormat:@"%@&%@", _comsumer_secret, tokenSecret];
	}
	else
	{
		sha1_key = [NSString stringWithFormat:@"%@&", _comsumer_secret];
	}
	//LOG(@"[%@ makeHeader]\n%@", [self className], param);
	NSData *data = [param dataUsingEncoding:NSASCIIStringEncoding];
	NSData *data_key = [sha1_key dataUsingEncoding:NSASCIIStringEncoding];
	unsigned char sha1[SHA_DIGEST_LENGTH + 1];
	unsigned int sha1_len = 0;
	HMAC(EVP_sha1(), [data_key bytes], [data_key length], [data bytes], [data length], sha1, &sha1_len);
	NSString *sha1_base64 = [HTTP base64StringWithBytes:(char*)sha1 length:sha1_len];
	oauth = [NSString stringWithFormat:@"OAuth %@=\"%@\"",
			 @"realm", [headers valueForKey:@"realm"]];
	enumerator = [[headers allKeys] objectEnumerator];
	BOOL signature = NO;
	while ((obj = [enumerator nextObject]))
	{
		if (![obj isEqualToString:@"oauth_signature"] && ![obj isEqualToString:@"realm"])
		{
			oauth = [oauth stringByAppendingFormat:@", %@=\"%@\"",
					 obj,
					 [headers valueForKey:obj]];
		}
		else if (![obj isEqualToString:@"realm"])
		{
			oauth = [oauth stringByAppendingFormat:@", %@=\"%@\"",
					 @"oauth_signature",
					 [HTTP urlEncode:sha1_base64
						   unescaped:nil
							 escaped:@"+/="]];
			signature = YES;
		}
	}
	if (!signature)
	{
		oauth = [oauth stringByAppendingFormat:@", %@=\"%@\"",
				 @"oauth_signature",
				 [HTTP urlEncode:sha1_base64
					   unescaped:nil
						 escaped:@"+/="]];
	}
	//LOG(@"[%@ makeHeader]\n%@", [self className], oauth);
	headers = [NSMutableDictionary dictionaryWithDictionary:header];
	[headers setValue:oauth forKey:@"Authorization"];
	NSString *host = [url replaceWithExpression:@"^.*(:?http|https)://(.*?)/.*$"
										replace:@"\\2"];
	[headers setValue:host forKey:@"Host"];
	//LOG(@"[%@ makeHeader]\n%@", [self className], headers);
	return [NSDictionary dictionaryWithDictionary:headers];
}

- (id)request_token
{
	if (!_comsumer_key || !_comsumer_secret || !_request_token_path)
	{
		return nil;
	}
	NSDate *date = [NSDate date];
	NSString *host = [_request_token_path replaceWithExpression:@"^.*(:?http|https)://(.*?)/.*$"
														replace:@"\\2"];
	NSString *realm = [_request_token_path replaceWithExpression:@"^.*((:?http|https)://.*?/).*$"
														 replace:@"\\1"];
	NSMutableDictionary *header = [NSMutableDictionary dictionaryWithObjectsAndKeys:
								   host, @"Host",
								   @"application/x-www-form-urlencoded", @"Content-Type",
								   [NSString stringWithFormat:
									@"OAuth realm=\"%@\", "\
									@"oauth_consumer_key=\"%@\", "\
									@"oauth_signature_method=\"HMAC-SHA1\", "\
									@"oauth_signature=\"@\", "\
									@"oauth_timestamp=\"%lu\", "\
									@"oauth_nonce=\"%lx\", "\
									@"oauth_version=\"1.0\"",
									realm,
									_comsumer_key,
									[self timestamp:date],
									[self nonce:date]], @"Authorization",
								   nil];
	header = [NSMutableDictionary dictionaryWithDictionary:
			  [self makeHeaderWithMethod:@"POST"
									 url:_request_token_path
								  header:header
									body:@""
							 tokenSecret:nil]];
	id auth = [self post:_request_token_path
				  header:header
					body:@""];
	LOG2(@"[%@ request_token]\n%@\n%@\n%@", [self className], _request_token_path, host, realm);
	if ([auth isKindOfClass:[NSData class]])
	{
		auth = [[[NSString alloc] initWithData:auth encoding:NSUTF8StringEncoding] autorelease];
		LOG2(@"[%@ request_token]\n%@", [self className], auth);
		if ([HTTP isHTML:auth] ||
			![auth lengthOfRegularExpression:@"oauth_token_secret"] ||
			![auth lengthOfRegularExpression:@"oauth_token"])
		{
			auth = nil;
		}
		else
		{
			NSArray *array = [auth componentsSeparatedByString:@"&"];
			if ([array count] < 2)
			{
				auth = nil;
			}
			else
			{
				NSEnumerator *enumerator = [array objectEnumerator];
				id object;
				while ((object = [enumerator nextObject]))
				{
					if ([object lengthOfRegularExpression:@"^oauth_token="])
					{
						[self setRequestToken:[object replaceWithExpression:@"^oauth_token=(.*)$"
																	replace:@"\\1"]];
					}
					else if ([object lengthOfRegularExpression:@"^oauth_token_secret="])
					{
						[self setRequestTokenSecret:[object replaceWithExpression:@"^oauth_token_secret=(.*)$"
																		  replace:@"\\1"]];
					}
				}
				LOG(@"[%@ request_token]\nrequest_token=%@\nrequest_token_secret=%@", [self className], _request_token, _request_token_secret);
			}
		}
	}
	return auth;
}

- (NSString *)authorizePath
{
	if (!_comsumer_key || !_comsumer_secret ||
		!_request_token ||
		!_authorize_path)
	{
		return nil;
	}
	return [NSString stringWithFormat:@"%@", _authorize_path];
}

- (id)access_tokenWithPIN:(NSString *)pin
{
	LOG(@"[%@ access_token]", [self className]);
	if (!_comsumer_key || !_comsumer_secret ||
		!_request_token || !_request_token_secret ||
		!_access_token_path)
	{
		return nil;
	}
	NSDate *date = [NSDate date];
	NSString *host = [_access_token_path replaceWithExpression:@"^.*http://(.*?)/.*$"
													   replace:@"\\1"];
	NSString *realm = [_access_token_path replaceWithExpression:@"^.*(http://.*?/).*$"
														replace:@"\\1"];
	if (pin)
	{
		pin = [NSString stringWithFormat:@"oauth_verifier=\"%@\", ", pin];
	}
	else
	{
		pin = @"";
	}
	NSMutableDictionary *header = [NSMutableDictionary dictionaryWithObjectsAndKeys:
								   host, @"Host",
								   @"application/x-www-form-urlencoded", @"Content-Type",
								   [NSString stringWithFormat:
									@"OAuth realm=\"%@\", "\
									@"oauth_consumer_key=\"%@\", "\
									@"oauth_token=\"%@\", "\
									@"oauth_signature_method=\"HMAC-SHA1\", "\
									@"oauth_signature=\"@\", "\
									@"oauth_timestamp=\"%lu\", "\
									@"oauth_nonce=\"%lx\", "\
									@"%@"\
									@"oauth_version=\"1.0\"",
									realm,
									_comsumer_key,
									_request_token,
									[self timestamp:date],
									[self nonce:date],
									pin], @"Authorization",
								   nil];
	header = [NSMutableDictionary dictionaryWithDictionary:
			  [self makeHeaderWithMethod:@"POST"
									 url:_access_token_path
								  header:header
									body:@""
							 tokenSecret:_request_token_secret]];
	//LOG(@"[%@ access_token]\n%@", [self className], header);
	id auth = [self post:_access_token_path
				  header:header
					body:@""];
	if ([auth isKindOfClass:[NSData class]])
	{
		auth = [[[NSString alloc] initWithData:auth encoding:NSUTF8StringEncoding] autorelease];
		if ([HTTP isHTML:auth] ||
			![auth lengthOfRegularExpression:@"oauth_token_secret"] ||
			![auth lengthOfRegularExpression:@"oauth_token"])
		{
			LOG(@"[%@ access_token]\n%@", [self className], auth);
			auth = nil;
		}
		else
		{
			NSArray *array = [auth componentsSeparatedByString:@"&"];
			if ([array count] < 2)
			{
				auth = nil;
			}
			else
			{
				NSEnumerator *enumerator = [array objectEnumerator];
				id object;
				while ((object = [enumerator nextObject]))
				{
					if ([object lengthOfRegularExpression:@"^oauth_token="])
					{
						[self setAccessToken:[object replaceWithExpression:@"^oauth_token=(.*)$"
																   replace:@"\\1"]];
					}
					else if ([object lengthOfRegularExpression:@"^oauth_token_secret="])
					{
						[self setAccessTokenSecret:[object replaceWithExpression:@"^oauth_token_secret=(.*)$"
																		 replace:@"\\1"]];
					}
					else
					{
						LOG(@"[%@ access_token]\n%@", [self className], object);
					}
				}
				LOG(@"[%@ access_token]\naccess_token=%@\naccess_token_secret=%@", [self className], _access_token, _access_token_secret);
			}
		}
	}
	return auth;
}

- (id)access_token
{
	return [self access_tokenWithPIN:nil];
}

- (id)postWithOAuth:(NSString *)path
			 header:(NSMutableDictionary *)header
			   body:(NSString *)body
{
	LOG(@"[%@ postWithOAuth] body\n%@", [self className], body);
	NSDate *date = [NSDate date];
	NSString *realm = [path replaceWithExpression:@"^.*((:?http|https)://.*?/).*$"
														replace:@"\\1"];
	[header setValue:[NSString stringWithFormat:
					  @"OAuth realm=\"%@/\", "\
					  @"oauth_consumer_key=\"%@\", "\
					  @"oauth_token=\"%@\", "\
					  @"oauth_signature_method=\"HMAC-SHA1\", "\
					  @"oauth_signature=\"@\", "\
					  @"oauth_timestamp=\"%lu\", "\
					  @"oauth_nonce=\"%lx\", "\
					  @"oauth_version=\"1.0\"",
					  realm,
					  _comsumer_key,
					  _access_token,
					  [self timestamp:date],
					  [self nonce:date]]
			  forKey:@"Authorization"];
	header = [NSMutableDictionary dictionaryWithDictionary:
			  [self makeHeaderWithMethod:@"POST"
									 url:path
								  header:header
									body:body
							 tokenSecret:_access_token_secret]];
	return [self post:path header:header body:body];
}

- (id)getWithOAuth:(NSString *)path
			header:(NSMutableDictionary *)header
			  body:(NSString *)body
		  delegate:(id)delegate
{
	LOG(@"[%@ getWithOAuth]\n%@", [self className], path);
	NSDate *date = [NSDate date];
	NSString *realm = [path replaceWithExpression:@"^.*((:?http|https)://.*?/).*$"
										  replace:@"\\1"];
	[header setValue:[NSString stringWithFormat:
					  @"OAuth realm=\"%@/\", "\
					  @"oauth_consumer_key=\"%@\", "\
					  @"oauth_token=\"%@\", "\
					  @"oauth_signature_method=\"HMAC-SHA1\", "\
					  @"oauth_signature=\"@\", "\
					  @"oauth_timestamp=\"%lu\", "\
					  @"oauth_nonce=\"%lx\", "\
					  @"oauth_version=\"1.0\"",
					  realm,
					  _comsumer_key,
					  _access_token,
					  [self timestamp:date],
					  [self nonce:date]]
			  forKey:@"Authorization"];
	header = [NSMutableDictionary dictionaryWithDictionary:
			  [self makeHeaderWithMethod:@"GET"
									 url:path
								  header:header
									body:body
							 tokenSecret:_access_token_secret]];
	//LOG(@"[%@ getWithOAuth]\npath: %@\nheader: %@\nbody: %@", [self className], path, header, body);
	return [self get:path header:header body:body delegate:delegate];
}

- (id)getWithOAuth:(NSString *)path
			header:(NSMutableDictionary *)header
			  body:(NSString *)body
{
	return [self getWithOAuth:path
					   header:header
						 body:body
					 delegate:nil];
}

- (id)headWithOAuth:(NSString *)path
			 header:(NSMutableDictionary *)header
			   body:(NSString *)body
{
	NSDate *date = [NSDate date];
	NSString *realm = [path replaceWithExpression:@"^.*((:?http|https)://.*?/).*$"
										  replace:@"\\1"];
	[header setValue:[NSString stringWithFormat:
					  @"OAuth realm=\"%@/\", "\
					  @"oauth_consumer_key=\"%@\", "\
					  @"oauth_token=\"%@\", "\
					  @"oauth_signature_method=\"HMAC-SHA1\", "\
					  @"oauth_signature=\"@\", "\
					  @"oauth_timestamp=\"%lu\", "\
					  @"oauth_nonce=\"%lx\", "\
					  @"oauth_version=\"1.0\"",
					  realm,
					  _comsumer_key,
					  _access_token,
					  [self timestamp:date],
					  [self nonce:date]]
			  forKey:@"Authorization"];
	header = [NSMutableDictionary dictionaryWithDictionary:
			  [self makeHeaderWithMethod:@"HEAD"
									 url:path
								  header:header
									body:body
							 tokenSecret:_access_token_secret]];
	return [self head:path header:header body:body];
}

@end
