//
//  ServiceTwitter.m
//  Afficheur
//
//  Created by kichi on 08/09/02.
//  Copyright 2008 Katsuhiko Ichinose. All rights reserved.
//

#import "ServiceTwitter.h"
#import "ServiceTwitterKey.h"
#import "AfficheurController.h"
#import "AfficheurPreferences.h"
#import "AfficheurGrowl.h"
#import "AfficheurTimeline.h"
#import "NSStringOgreKit.h"
#import <OgreKit/OgreKit.h>
#import <JSON/JSON.h>


@implementation ServiceTwitter

static NSString* REQUEST_TOKEN_PATH = @"http://twitter.com/oauth/request_token";
static NSString* AUTHORIZE_PATH = @"http://twitter.com/oauth/authorize";
static NSString* ACCESS_TOKEN_PATH = @"http://twitter.com/oauth/access_token";

- (id)init
{
	//LOG(@"[%@ init]", [self className]);
	self = [super init];
	if (self)
	{
		_title = nil;
//		_since = [[NSMutableArray alloc] init];
		_dateTimeline = nil;
		_dateReplies = nil;
		_dateDM = nil;
		_first = YES;
		_firstReplies = YES;
		_firstDM = YES;
		_tweets = 20;
		_keepTweets = 0;
		_request_token = nil;
		_request_token_secret = nil;
		_access_token = nil;
		_access_token_secret = nil;
		_stream = nil;
		_lock = [[NSLock alloc] init];
	}
	return self;
}

- (void)dealloc
{
	[_title release];
//	if (_since)
//	{
//		[_since 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 (_stream)
	{
		[_stream release];
	}
	if (_lock)
	{
		[_lock release];
	}
	[super dealloc];
}

- (id)init:(NSString *)service
WithController:(AfficheurController *)controller
preferences:(AfficheurPreferences *)preferences
{
	self = [super init:service
		WithController:controller
		   preferences:preferences];
	if (self)
	{
		if ([service isEqualToString:Twitter])
		{
			_notify = GrowlAfficheurTwitterNotify;
			_notifyReply = GrowlAfficheurTwitterReplyNotify;
			_notifyDM = GrowlAfficheurTwitterDirectNotify;
			_notifyError = GrowlAfficheurTwitterErrorNotify;
			_tweets = [preferences tweetsMinAccountTwitterRetrieveTweets];
			LOG(@"[%@(%@) init] _tweets: %d", [self className], _service, _tweets);
		}
		else if ([service isEqualToString:Wassr])
		{
			_notify = GrowlAfficheurWassrNotify;
			_notifyReply = GrowlAfficheurWassrReplyNotify;
			_notifyDM = GrowlAfficheurWassrReplyNotify;
			_notifyError = GrowlAfficheurWassrErrorNotify;
		}
//		else if ([service isEqualToString:Nowa])
//		{
//			_notify = GrowlAfficheurNowaNotify;
//			_notifyReply = GrowlAfficheurNowaReplyNotify;
//			_notifyDM = GrowlAfficheurNowaReplyNotify;
//			_notifyError = GrowlAfficheurNowaErrorNotify;
//		}
		else if ([service isEqualToString:Identica])
		{
			_notify = GrowlAfficheurIdenticaNotify;
			_notifyReply = GrowlAfficheurIdenticaReplyNotify;
			_notifyDM = GrowlAfficheurIdenticaDirectNotify;
			_notifyError = GrowlAfficheurIdenticaErrorNotify;
		}
//		else if ([service isEqualToString:Jisko])
//		{
//			_notify = GrowlAfficheurJiskoNotify;
//			_notifyReply = GrowlAfficheurJiskoReplyNotify;
//			_notifyDM = GrowlAfficheurJiskoPrivateNotify;
//			_notifyError = GrowlAfficheurJiskoErrorNotify;
//		}
//		else if ([service isEqualToString:Chuitter])
//		{
//			_notify = GrowlAfficheurChuitterNotify;
//			_notifyReply = GrowlAfficheurChuitterReplyNotify;
//			_notifyDM = GrowlAfficheurChuitterDirectNotify;
//			_notifyError = GrowlAfficheurChuitterErrorNotify;
//		}
		else if ([service isEqualToString:Tumblr])
		{
			_notify = GrowlAfficheurTumblrNotify;
			_notifyReply = GrowlAfficheurTumblrReplyNotify;
			_notifyDM = GrowlAfficheurTumblrDirectNotify;
			_notifyError = GrowlAfficheurTumblrErrorNotify;
		}
	}
	return self;
}

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

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

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

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

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

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

- (NSString *)authorize
{
	LOG(@"[%@ authorize]", [self className]);
	NSString *authorize = nil;
	@try
	{
		OAuth *oAuth = [[OAuth alloc] init];
		[oAuth setConsumerKey:CONSUMER_KEY];
		[oAuth setConsumerSecret:CONSUMER_SECRET];
		[oAuth setRequestTokenPath:REQUEST_TOKEN_PATH];
		[oAuth setAuthorizePath:AUTHORIZE_PATH];
		[oAuth setAccessTokenPath:ACCESS_TOKEN_PATH];
		if ([oAuth request_token])
		{
			[self setRequestToken:[oAuth requestToken]];
			[self setRequestTokenSecret:[oAuth requestTokenSecret]];
			authorize = [NSString stringWithFormat:
						 @"%@?oauth_token=%@&perms=delete",
						 [oAuth authorizePath], [oAuth requestToken]];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ authorize] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return authorize;
}

- (BOOL)access_token:(NSString *)pin
{
	LOG(@"[%@ access_token]", [self className]);
	BOOL access_token = NO;
	@try
	{
		OAuth *oAuth = [[OAuth alloc] init];
		[oAuth setConsumerKey:CONSUMER_KEY];
		[oAuth setConsumerSecret:CONSUMER_SECRET];
		[oAuth setRequestToken:_request_token];
		[oAuth setRequestTokenSecret:_request_token_secret];
		[oAuth setRequestTokenPath:REQUEST_TOKEN_PATH];
		[oAuth setAuthorizePath:AUTHORIZE_PATH];
		[oAuth setAccessTokenPath:ACCESS_TOKEN_PATH];
		if ([oAuth access_tokenWithPIN:pin])
		{
			[self setAccessToken:[oAuth accessToken]];
			[self setAccessTokenSecret:[oAuth accessTokenSecret]];
			access_token = YES;
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ access_token] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return access_token;
}

- (void)setupRetrieveDate:(NSDate *)date
{
	[self setupRetrieveDateTimeline:date];
	[self setupRetrieveDateReplies:date];
	[self setupRetrieveDateDM:date];
}

- (void)setupRetrieveDateTimeline:(NSDate *)date
{
	@synchronized(self)
	{
		if (_dateTimeline)
		{
			[_dateTimeline release];
		}
		if (date)
		{
			_dateTimeline = [[NSDate alloc] initWithTimeInterval:0 sinceDate:date];
		}
		else
		{
			_dateTimeline = nil;
		}
	}
}

- (void)setupRetrieveDateReplies:(NSDate *)date
{
	@synchronized(self)
	{
		if (_dateReplies)
		{
			[_dateReplies release];
		}
		if (date)
		{
			_dateReplies = [[NSDate alloc] initWithTimeInterval:0 sinceDate:date];
		}
		else
		{
			_dateReplies = nil;
		}
	}
}

- (void)setupRetrieveDateDM:(NSDate *)date
{
	@synchronized(self)
	{
		if (_dateDM)
		{
			[_dateDM release];
		}
		if (date)
		{
			_dateDM = [[NSDate alloc] initWithTimeInterval:0 sinceDate:date];
		}
		else
		{
			_dateDM = nil;
		}
	}
}

- (BOOL)checkRetrieve:(NSDate *)now
			 withDate:(NSDate *)date
			 interval:(NSTimeInterval)interval
{
	BOOL check = NO;
	@synchronized(self)
	{
		if (now && date && (interval > 0))
		{
			if (![date isKindOfClass:[NSDate class]])
			{
				LOG(@"[%@(%@) checkRetrieve] %@", [self className], _service, [date className]);
			}
			else
			{
				NSDate *aDate = [date addTimeInterval:interval];
				if ([now compare:aDate] != NSOrderedAscending)
				{
					check = YES;
				}
			}
		}
	}
	return check;
}

- (BOOL)checkRetrieveTimeline:(NSDate *)now
{
	BOOL check = NO;
	@synchronized(self)
	{
		check = [self checkRetrieve:now
						   withDate:_dateTimeline
						   interval:[_preferences accountTwitterIntervalTimeline]];
	}
	return check;
}

- (BOOL)checkRetrieveReplies:(NSDate *)now
{
	BOOL check = NO;
	@synchronized(self)
	{
		check = [self checkRetrieve:now
						   withDate:_dateReplies
						   interval:[_preferences accountTwitterIntervalReplies]];
	}
	return check;
}

- (BOOL)checkRetrieveDM:(NSDate *)now
{
	BOOL check = NO;
	@synchronized(self)
	{
		check = [self checkRetrieve:now
						   withDate:_dateDM
						   interval:[_preferences accountTwitterIntervalDM]];
	}
	return check;
}

- (TwitterAPI *)createAPI:(NSDictionary *)dic
{
	TwitterAPI *api = [[[TwitterAPI alloc] initWithDictionary:dic] autorelease];
	if (api)
	{
		[api setConsumerKey:CONSUMER_KEY];
		[api setConsumerSecret:CONSUMER_SECRET];
		[api setRequestTokenPath:REQUEST_TOKEN_PATH];
		[api setAuthorizePath:AUTHORIZE_PATH];
		[api setAccessTokenPath:ACCESS_TOKEN_PATH];
	}
	return api;
}

- (int)rateLimit
{
	int rateLimit = -1;
	@try
	{
		NSString *user = [_preferences accountTwitterUser];
		NSString *pass = [_preferences accountTwitterPass];
		NSString *key = [_preferences accountTwitterOAuthKey];
		NSString *secret = [_preferences accountTwitterOAuthSecret];
		if (!pass)
		{
			pass = (NSString *)[NSNull null];
		}
		if (!key)
		{
			key = (NSString *)[NSNull null];
		}
		if (!secret)
		{
			secret = (NSString *)[NSNull null];
		}
		NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
							 user, TwitterUser,
							 pass, TwitterPass,
							 key, TwitterKey,
							 secret, TwitterSecret,
							 nil];
		TwitterAPI *api = [self createAPI:dic];
		id status = [api rateLimit];
		LOG2(@"[%@(%@) rateLimit]\n%@", [self className], _service, status);
		if (status && [status isKindOfClass:[NSDictionary class]])
		{
			id hourly_limit = [status valueForKey:@"hourly_limit"];
			if (hourly_limit && [hourly_limit isKindOfClass:[NSNumber class]])
			{
				rateLimit = [hourly_limit intValue];
				//LOG(@"[%@(%@) rateLimit] %d", [self className], _service, rateLimit);
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) rateLimit] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return rateLimit;
}

- (NSDictionary *)parse:(NSDictionary *)obj
			   withUser:(NSString *)user_id
				   kind:(int)kind
				  first:(BOOL)first
{
	if (![obj isKindOfClass:[NSDictionary class]])
	{
		return nil;
	}
	LOG(@"[%@(%@) parse:%d]\n%@", [self className], _service, kind, obj);
	NSDictionary *item = nil;
	@try
	{
		NSDictionary *org = obj;
		NSDictionary *retweet = [obj valueForKey:@"retweeted_status"];
		NSString *retweetedBy = nil;
		NSString *retweetedId = nil;
		if (retweet)
		{
			NSDictionary *byUser = [obj valueForKey:@"user"];
			retweetedBy = [byUser valueForKey:@"screen_name"];
			obj = retweet;
			//LOG2(@"[%@(%@) parse:%d]\n%@", [self className], _service, kind, retweet);
		}
		NSString *item_id = [NSString stringWithFormat:@"%@", [obj valueForKey:@"id"]];
		//LOG(@"[%@(%@) parse:%d] %@", [self className], _service, kind, item_id);
		NSDictionary *user = [obj valueForKey:@"user"];
		if (!user)
		{
			user = [obj valueForKey:@"sender"];
		}
		id protected = [user valueForKey:@"protected"];
		id verified = [user valueForKey:@"verified"];
		NSString *screen_name = [user valueForKey:@"screen_name"];
		NSString *user_name = [user valueForKey:@"name"];
		if (!user_name)
		{
			user_name = @"";
		}
		else if ([user_name isKindOfClass:[NSNull class]])
		{
			user_name = @"";
		}
		user_name = [self convertText:user_name];
		NSString *name = @"";
		if ((![user_name isEqualToString:@""]) &&(![user_name isEqualToString:screen_name]))
		{
			name = [NSString stringWithFormat:@" / %@", user_name];
		}
		NSString *user_profile = [user valueForKey:@"profile_image_url"];
		NSString *date = [obj valueForKey:@"created_at"];
		NSString *channel = @"";
		NSString *text = [obj valueForKey:@"text"];
		NSString *comment = @"";
		NSString *notify = _notify;
		NSString *kindStr = KindTimeline;
		NSString *category = KindTimeline;
		NSString *inReplyUser = nil;
		int hasKeyword = -1;
		if (retweet)
		{
			retweetedId = item_id;
			item_id = [NSString stringWithFormat:@"%@", [org valueForKey:@"id"]];
			date = [org valueForKey:@"created_at"];
		}
		if (!text || [text isKindOfClass:[NSNull class]])
		{
			text = @"";
		}
		if (![screen_name isEqualToString:user_id])
		{
			hasKeyword = [self hasKeywords:
						  [NSArray arrayWithObjects:
						   text, screen_name, user_name, channel, nil]];
			if (hasKeyword == 0)
			{
				notify = GrowlAfficheurKeywordsNotify;
				first = NO;
			}
		}
		if (kind == kindReply)
		{
			notify = _notifyReply;
			kindStr = KindReply;
			category = KindReply;
		}
		else if (kind == kindDM)
		{
			notify = _notifyDM;
			kindStr = KindDM;
			category = KindDM;
		}
		id in_reply = [obj valueForKey:@"in_reply_to_status_id"];
		if (!in_reply || [in_reply isKindOfClass:[NSNull class]])
		{
			if ((kind == kindTimeline) || (kind == kindChannel))
			{
				if (![screen_name isEqualToString:user_id] &&
					([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@$", user_id]] > 0) ||
					([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@[^!-~]", user_id]] > 0))
				{
					//LOG(@"[%@(%@) parse:%d] Reply\n%@:%@", [self className], _service, kind, screen_name, text);
					notify = _notifyReply;
					category = KindReply;
					first = NO;
				}
			}
		}
		else
		{
			NSDictionary *reply = [self fetch:[NSString stringWithFormat:@"%@", in_reply]
								  withService:_service];
			if (reply)
			{
				inReplyUser = [reply valueForKey:KeyUser];
				comment = [NSString stringWithFormat:@"(on %@: %@)",
						   [reply valueForKey:KeyUser], [reply valueForKey:KeyText]];
				if ((kind == kindTimeline) || (kind == kindChannel))
				{
					if ([[reply valueForKey:KeyUser] isEqualToString:user_id])
					{
						//LOG(@"[%@(%@) parse:%d] Reply\n%@:%@", [self className], _service, kind, screen_name, text);
						notify = _notifyReply;
						category = KindReply;
						first = NO;
					}
				}
			}
			else if ((kind == kindTimeline) || (kind == kindChannel))
			{
				if (![screen_name isEqualToString:user_id] &&
					([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@$", user_id]] > 0) ||
					([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@[^!-~]", user_id]] > 0))
				{
					//LOG(@"[%@(%@) parse:%d] Reply\n%@:%@", [self className], _service, kind, screen_name, text);
					notify = _notifyReply;
					category = KindReply;
					first = NO;
				}
			}
		}
		//	if (kind == kindTimeline)
		//	{
		//		int i = 0;
		//		while (i < [_since count])
		//		{
		//			NSString *since = [_since objectAtIndex:i];
		//			if ([since compare:item_id] != NSOrderedDescending)
		//			{
		//				break;
		//			}
		//			i++;
		//		}
		//		[_since insertObject:item_id atIndex:i];
		//	}
		if (kind == kindDM)
		{
			item_id = [NSString stringWithFormat:@"DM%@", item_id];
			user_profile = [NSString stringWithFormat:@"%@ %@", user_profile, Mail];
		}
		NSString *rid = [obj valueForKey:@"rid"];
		item = [self createItem:item_id
					   withUser:screen_name
						   name:name
						channel:channel
							rid:rid
						   text:text
						comment:comment
						 source:[obj valueForKey:@"source"]
					 created_at:[NSDate dateWithNaturalLanguageString:date]
				   user_profile:user_profile
						 notify:notify
						   kind:kindStr
					   category:category
					inReplyUser:inReplyUser
					retweetedBy:retweetedBy
					retweetedId:retweetedId
						keyword:hasKeyword
						protect:[protected intValue]
					   verified:[verified intValue]
						  first:first];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) parse:%d] EXCEPTION\n%@: %@", [self className], _service, kind, [exception name], [exception reason]);
	}
	return item;
}

- (BOOL)addPostResult:(NSDictionary *)object
			 withUser:(NSString *)user
				 kind:(int)kind
{
	//LOG2(@"[%@(%@) addPostResult:%d]\n%@", [self className], _service, kind, object);
	BOOL result = NO;
	NSMutableArray *list = [[NSMutableArray alloc] init];
	NSMutableArray *notify = [[NSMutableArray alloc] init];
	[self lockService];
	@try
	{
		id item = [self parse:object
					 withUser:user
						 kind:kind
						first:NO];
		if (item)
		{
			LOG(@"[%@(%@) addPostResult:%d]\n%@", [self className], _service, kind, item);
			[item retain];
			@try
			{
				if ([self addTimeline:item withFirst:NO])
				{
					NSString *channel = [item valueForKey:KeyChannel];
					if (channel && ![channel isEqualToString:@""])
					{
						[self registComplete:channel
								flagIdentica:[_service isEqualToString:Identica]];
					}
					[self registComplete:[item valueForKey:KeyText]
							flagIdentica:[_service isEqualToString:Identica]];
					if ([_preferences generalGrowlOldOrder])
					{
						[notify addObject:item];
					}
					else
					{
						[notify insertObject:item atIndex:0];
					}
					result = YES;
				}
				[list addObject:item];
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) addPostResult:%d] EXCEPTION\n%@: %@", [self className], _service, kind, [exception name], [exception reason]);
			}
			@finally
			{
				[item release];
			}
		}
		[self finishAddTimeline:list withNotify:notify];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) addPostResult:%d] EXCEPTION\n%@: %@", [self className], _service, kind, [exception name], [exception reason]);
	}
	@finally
	{
		[self unlockService];
		[notify release];
		[list release];
	}
	return result;
}

- (NSString *)determinePost:(id)object
					withAPI:(TwitterAPI *)api
				   function:(NSString *)function
					   post:(NSString *)post
{
	id description = [NSString stringWithFormat:@"%@ was failure.", function];
	@try
	{
		//LOG(@"[%@(%@) determinPost] %@", [self className], _service, [api className]);
		if ([object isKindOfClass:[NSError class]])
		{
			NSString *desc = [api errorLocalizedDescription:object];
			if (desc)
			{
				description = [NSString stringWithFormat:@"%@\n(%@)", description, desc];
			}
		}
//		else if((![_service isEqualToString:Jisko] && ![object isKindOfClass:[NSDictionary class]]) &&
//				([_service isEqualToString:Jisko] && ![object isKindOfClass:[NSArray class]]))
		else if(![object isKindOfClass:[NSDictionary class]])
		{
			NSString *strring = [NSString stringWithFormat:@"%@", object];
			NSString *title = [api titleOfHTML:strring];
			if (title)
			{
				strring = title;
			}
			description = [NSString stringWithFormat:@"%@\n(%@)", description, strring];
		}
		else
		{
			NSString *text = nil;
			if (post)
			{
				LOG(@"[%@(%@) determinPost]\n%@", [self className], _service, object);
			}
			if ([object isKindOfClass:[NSDictionary class]])
			{
				text = [object valueForKey:@"text"];
			}
			if (text && post)
			{
				if ([self addPostResult:object withUser:[api user] kind:kindTimeline])
				{
					description = [NSNull null];
				}
				else
				{
					NSString *post2 = [self convertText:post];
					NSString *text2 = [self convertText:text];
					LOG2(@"[%@(%@) determinPost]\n\'%@\'\n\'%@\'", [self className], _service, post2, text2);
					if ([post2 lengthOfRegularExpression:@"^[dD] \\S+ "
											 withOptions:OgreMultilineOption])
					{
						description = nil;
					}
					else if ([post2 isEqualToString:text2])
					{
						description = nil;
					}
					else if ([post2 lengthOfRegularExpression:[text2 regularizeString]])
					{
						description = nil;
					}
				}
			}
			else
			{
				description = nil;
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) determinPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return description;
}

- (NSString *)determinePost:(id)object
					withAPI:(TwitterAPI *)api
				   function:(NSString *)function
					   user:(NSString *)user
					   post:(NSString *)post
{
	id description = [NSString stringWithFormat:@"%@ was failure.", function];
	@try
	{
		//LOG(@"[%@(%@) determinPost] %@", [self className], _service, [api className]);
		if ([object isKindOfClass:[NSError class]])
		{
			NSString *desc = [api errorLocalizedDescription:object];
			if (desc)
			{
				description = [NSString stringWithFormat:@"%@\n(%@)", description, desc];
			}
		}
//		else if((![_service isEqualToString:Jisko] && ![object isKindOfClass:[NSDictionary class]]) &&
//				([_service isEqualToString:Jisko] && ![object isKindOfClass:[NSArray class]]))
		else if(![object isKindOfClass:[NSDictionary class]])
		{
			NSString *strring = [NSString stringWithFormat:@"%@", object];
			NSString *title = [api titleOfHTML:strring];
			if (title)
			{
				strring = title;
			}
			description = [NSString stringWithFormat:@"%@\n(%@)", description, strring];
		}
		else
		{
			NSString *error = nil;
			NSString *text = nil;
			NSString *recipient = nil;
			if (post)
			{
				LOG2(@"[%@(%@) determinPost]\n%@", [self className], _service, object);
			}
			if ([object isKindOfClass:[NSDictionary class]])
			{
				error = [object valueForKey:@"error"];
				text = [object valueForKey:@"text"];
				recipient = [object valueForKey:@"recipient_screen_name"];
			}
			if (error)
			{
				description = [NSString stringWithFormat:@"%@\n\n%@", description, error];
			}
			else if (text && post && recipient && user)
			{
				if ([self addPostResult:object withUser:[api user] kind:kindDM]) {
					description = [NSNull null];
				}
#if 0
				LOG2(@"[%@(%@) determinPost]\n\'%@\'\n\'%@\'", [self className], _service, user, recipient);
				LOG2(@"[%@(%@) determinPost]\n\'%@\'\n\'%@\'", [self className], _service, post, text);
				if ([recipient isEqualToString:user] && [text isEqualToString:post])
				{
					description = nil;
				}
				else if ([recipient isEqualToString:user] && [post lengthOfRegularExpression:[text regularizeString]])
				{
					description = nil;
				}
#endif
			}
			else
			{
				description = nil;
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) determinPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return description;
}

- (NSString *)determinePost:(id)object
					withAPI:(TwitterAPI *)api
				   function:(NSString *)function
{
	return [self determinePost:object withAPI:api function:function post:nil];
}

- (void)performPost:(NSArray *)array
{
	@try
	{
		[_controller syncPost];
		TwitterAPI *api = [self createAPI:[array objectAtIndex:1]];
		if (api)
		{
			[self registComplete:[array objectAtIndex:0]];
			id result = nil;
			[_lock lock];
			@try
			{
//				if ([_service isEqualToString:Nowa])
//				{
//					result = [api updateNowa:[array objectAtIndex:0]
//								   withReply:[array objectAtIndex:2]];
//				}
//				else
				{
					result = [api update:[array objectAtIndex:0]
							   withReply:[array objectAtIndex:2]];
				}
				[self determinePost:[self determinePost:result
												withAPI:api
											   function:@"Post"
												   post:[array objectAtIndex:0]]
						  withTitle:_title
						   function:@"Post"];
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				[_lock unlock];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[array release];
	}
}

- (void)performDirectMessage:(NSArray *)array
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		[_controller syncPost];
		TwitterAPI *api = [self createAPI:[array objectAtIndex:1]];
		if (api)
		{
			NSString *status = [array objectAtIndex:0];
			[self registComplete:status];
			id result = nil;
			NSString *user = [status replaceWithExpression:@"^[dD] (\\S+) (.*)$"
												   replace:@"\\1"
												   options:OgreMultilineOption];
			NSString *text = [status replaceWithExpression:@"^[dD] (\\S+) (.*)$"
												   replace:@"\\2"
												   options:OgreMultilineOption];
			[_lock lock];
			@try
			{
				result = [api directMessageNew:user text:text];
				[self determinePost:[self determinePost:result
												withAPI:api
											   function:@"DM"
												   user:user
												   post:text]
						  withTitle:_title
						   function:@"DM"];
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				[_lock unlock];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[array release];
	}
	[pool release];
	[NSThread exit];
}

- (void)directMessage:(NSString *)message
{
	//@synchronized(self)
	{
		@try
		{
			NSString *user = [_preferences accountTwitterUser];
			NSString *pass = [_preferences accountTwitterPass];
			NSString *key = [_preferences accountTwitterOAuthKey];
			NSString *secret = [_preferences accountTwitterOAuthSecret];
			if (!pass)
			{
				pass = (NSString *)[NSNull null];
			}
			if (!key)
			{
				key = (NSString *)[NSNull null];
			}
			if (!secret)
			{
				secret = (NSString *)[NSNull null];
			}
			NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
								 user, TwitterUser,
								 pass, TwitterPass,
								 key, TwitterKey,
								 secret, TwitterSecret,
								 Afficheur, TwitterSource,
								 nil];
			if ([_preferences generalConvertEmoji])
			{
				message = [self stringFromEmoji:message];
			}
			[self performDirectMessage:[[NSArray alloc] initWithObjects:message, dic, nil]];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) directMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (void)post:(NSString *)status
   withReply:(NSString *)reply
{
	//@synchronized(self)
	{
		@try
		{
			if ([status lengthOfRegularExpression:@"^[dD] \\S+ "
									  withOptions:OgreMultilineOption])
			{
				[self directMessage:status];
			}
			else
			{
				if (![reply isEqualToString:@""])
				{
					reply = [NSString stringWithFormat:@"&in_reply_to_status_id=%@", reply];
				}
				NSString *user = [_preferences accountTwitterUser];
				NSString *pass = [_preferences accountTwitterPass];
				NSString *key = [_preferences accountTwitterOAuthKey];
				NSString *secret = [_preferences accountTwitterOAuthSecret];
				if (!pass)
				{
					pass = (NSString *)[NSNull null];
				}
				if (!key)
				{
					key = (NSString *)[NSNull null];
				}
				if (!secret)
				{
					secret = (NSString *)[NSNull null];
				}
				NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
									 user, TwitterUser,
									 pass, TwitterPass,
									 key, TwitterKey,
									 secret, TwitterSecret,
									 Afficheur, TwitterSource,
									 nil];
				if ([_preferences generalConvertEmoji])
				{
					status = [self stringFromEmoji:status];
				}
				[self performPost:[[NSArray alloc] initWithObjects:status, dic, reply, nil]];
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) post] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (BOOL)isReply:(NSDictionary *)object
{
	return [[object valueForKey:KeyNotify] isEqualToString:_notifyReply];
}

//- (NSString *)since
//{
//	NSString * since = nil;
//	int i = 20;
//	int count = [_since count];
//	if (i >= count)
//	{
//		i = count - 1;
//	}
//	if (i >= 0)
//	{
//		since = [_since objectAtIndex:i];
//	}
//	return since;
//}

- (BOOL)performRetrieve:(TwitterAPI *)api
			 withObject:(id)object
				   kind:(int)kind
				  first:(BOOL)first
			   useTweet:(BOOL)useTweet
{
	//LOG(@"[%@(%@) performRetrieve:%d] %@", [self className], _service, kind, [object className]);
	BOOL retry = NO;
	@try
	{
		id description = nil;
		switch (kind)
		{
			default:
			case kindTimeline:
				description = @"Retrieve timeline was failure.";
				break;
			case kindReply:
				description = @"Retrieve replies was failure.";
				break;
			case kindDM:
				description = @"Retrieve direct message was failure.";
				break;
			case kindChannel:
				description = @"Retrieve channel was failure.";
				break;
		}
		if ([object isKindOfClass:[NSError class]])
		{
			NSString *desc = [api errorLocalizedDescription:object];
			if (desc)
			{
				description = [NSString stringWithFormat:@"%@\n(%@)", description, desc];
			}
		}
		if (![object isKindOfClass:[NSArray class]])
		{
			if ([object isKindOfClass:[NSDictionary class]])
			{
				NSString *error = [object valueForKey:@"error"];
				if (error &&
					![error isKindOfClass:[NSNull class]] &&
					![error isEqualToString:@""])
				{
					description = [NSString stringWithFormat:@"%@\n(%@)", description, error];
				}
			}
			else if ([object isKindOfClass:[NSString class]])
			{
				if ([HTTP isHTML:object])
				{
					NSString *title = [api titleOfHTML:object];
					if (title)
					{
						description = [NSString stringWithFormat:@"%@\n(%@)", description, title];
					}
				}
				else
				{
#if defined(_D_E_B_U_G_)
					description = [NSString stringWithFormat:@"%@\n(%@)", description, object];
#else //defined(_D_E_B_U_G_)
					description = [NSString stringWithFormat:@"%@", description];
#endif //defined(_D_E_B_U_G_)
				}
			}
			NSDictionary *item = [[self createItem:IdZero
										  withUser:@""
											  name:@""
										   channel:@""
											   rid:@""
											  text:[NSString stringWithFormat:@"%@", description]
										   comment:@""
											source:nil
										created_at:[NSDate dateWithTimeIntervalSinceNow:0.0]
									  user_profile:_errorIcon
											notify:_notifyError
											  kind:KindError
										  category:KindError
									   inReplyUser:nil
											 first:NO] retain];
			@try
			{
				[self addTimeline:item withFirst:NO];
				NSArray *notify = [NSArray arrayWithObjects:item, nil];
				[self finishAddTimeline:notify withNotify:notify];
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performRetrieve:%d] EXCEPTION\n%@: %@", [self className], _service, kind, [exception name], [exception reason]);
			}
			@finally
			{
				[item release];
			}
		}
		else
		{
#if 0 //def _D_E_B_U_G_
			if ([_service isEqualToString:Wassr] && (kind == kindTimeline))
			{
				LOG(@"[%@(%@) performRetrieve]\n%@", [self className], _service, [api headers]);
			}
#endif
			NSMutableArray *list = [[NSMutableArray alloc] init];
			NSMutableArray *notify = [[NSMutableArray alloc] init];
			@try
			{
				int i = [object count];
				while (i-- > 0)
				{
					id obj = [object objectAtIndex:i];
					id item = [self parse:obj
								 withUser:[api user]
									 kind:kind
									first:first];
					if (item)
					{
						[item retain];
						@try
						{
							if ([self addTimeline:item withFirst:first])
							{
								NSString *channel = [item valueForKey:KeyChannel];
								if (channel && ![channel isEqualToString:@""])
								{
									[self registComplete:channel
											flagIdentica:[_service isEqualToString:Identica]];
								}
								[self registComplete:[item valueForKey:KeyText]
										flagIdentica:[_service isEqualToString:Identica]];
								if ([_preferences generalGrowlOldOrder])
								{
									[notify addObject:item];
								}
								else
								{
									[notify insertObject:item atIndex:0];
								}
							}
							[list addObject:item];
						}
						@catch (NSException *exception)
						{
							EXPLOG(@"[%@(%@) performRetrieve:%d] EXCEPTION\n%@: %@", [self className], _service, kind, [exception name], [exception reason]);
						}
						@finally
						{
							[item release];
						}
					}
				}
				if (kind == kindTimeline)
				{
				//	while ([_since count] > 30)
				//	{
				//		//[[_since objectAtIndex:30] release];
				//		[_since removeObjectAtIndex:30];
				//	}
					if (useTweet && !first)
					{
#if 0 //defined(_D_E_B_U_G_)
						int old = _tweets;
#endif //defined(_D_E_B_U_G_)
						int tweets = _tweets;
						int max = [_preferences tweetsMaxAccountTwitterRetrieveTweets];
						int min = [_preferences tweetsMinAccountTwitterRetrieveTweets];
						//int inc = [_preferences tweetsIncAccountTwitterRetrieveTweets];
						int dec = [_preferences tweetsDecAccountTwitterRetrieveTweets];
						int up = [_preferences tweetsUpAccountTwitterRetrieveTweets];
						int down = [_preferences tweetsDownAccountTwitterRetrieveTweets];
						int keep = [_preferences tweetsKeepAccountTwitterRetrieveTweets];
						int diff = tweets - [notify count];
						if (diff <= up)
						{
							_keepTweets = 0;
							tweets *= 2;
							if (tweets > max)
							{
								tweets = max;
							}
							if (diff <= 0)
							{
								LOG(@"[%@(%@) performRetrieve:%d] min: %d", [self className], _service, kind, tweets);
								if (tweets <= 50)
								{
									retry = YES;
									//[_preferences setTweetsMinAccountTwitterRetrieveTweets:tweets];
								}
							}
						}
						else if (diff >= down)
						{
							_keepTweets++;
							if (_keepTweets >= keep)
							{
								_keepTweets = 0;
								tweets -= dec;
								if (tweets < min)
								{
									tweets = min;
								}
							}
						}
						else
						{
							_keepTweets = 0;
						}
						_tweets = tweets;
						LOG(@"[%@(%@) performRetrieve:%d] %d -> %d / %d", [self className], _service, kind, old, tweets, _keepTweets);
					}
				}
				[self finishAddTimeline:list withNotify:notify];
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performRetrieve:%d] EXCEPTION\n%@: %@", [self className], _service, kind, [exception name], [exception reason]);
			}
			@finally
			{
				[notify release];
				[list release];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performRetrieve:%d] EXCEPTION\n%@: %@", [self className], _service, kind, [exception name], [exception reason]);
	}
	return retry;
}

- (void)performRetrieve:(TwitterAPI *)api
			 withObject:(id)object
				   kind:(int)kind
				  first:(BOOL)first
{
	[self performRetrieve:api
			   withObject:object
					 kind:kind
					first:first
				 useTweet:NO];
}

- (void)performRetrieveTimeline:(NSDictionary *)dic
{
	//LOG(@"[%@(%@) performRetrieveTimeline]", [self className], _service);
	[self setupRetrieveDateTimeline:nil];
	@try
	{
		TwitterAPI *api = [self createAPI:dic];		
		if (api)
		{
			[api setTimeoutInterval:30];
			BOOL success = NO;
			int count = 3;
			while (count--)
			{
				id result = nil;
				if ([_service isEqualToString:Twitter])
				{
					int retry = 3;
					while (retry--)
					{
						LOG(@"[%@(%@) performRetrieveTimeline] %d", [self className], _service, _tweets);
						result = [api friendsTimelineWithSince:nil tweets:_tweets];
						if (result && ![result isKindOfClass:[NSError class]])
						{
							success = YES;
							break;
						}
					}
					LOG(@"[%@(%@) performRetrieveTimeline] lock", [self className], _service);
					[self lockService];
					LOG(@"[%@(%@) performRetrieveTimeline] locked", [self className], _service);
					@try
					{
						if (![self performRetrieve:api withObject:result kind:kindTimeline first:_first useTweet:YES])
						{
							count = 0;
						}
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@(%@) performRetrieveTimeline] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
					}
					@finally
					{
						LOG(@"[%@(%@) performRetrieveTimeline] unlock", [self className], _service);
						[self unlockService];
						LOG(@"[%@(%@) performRetrieveTimeline] unlocked", [self className], _service);
					}
				}
				else
				{
					int retry = 3;
					while (retry--)
					{
						result = [api friendsTimelineWithSince:nil];
						if (result && ![result isKindOfClass:[NSError class]])
						{
							success = YES;
							break;
						}
					}
					LOG(@"[%@(%@) performRetrieveTimeline] lock", [self className], _service);
					[self lockService];
					LOG(@"[%@(%@) performRetrieveTimeline] locked", [self className], _service);
					@try
					{
						[self performRetrieve:api withObject:result kind:kindTimeline first:_first];
						count = 0;
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@(%@) performRetrieveTimeline] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
					}
					@finally
					{
						LOG(@"[%@(%@) performRetrieveTimeline] unlock", [self className], _service);
						[self unlockService];
						LOG(@"[%@(%@) performRetrieveTimeline] unlocked", [self className], _service);
					}
				}
			}
			if (success && _first)
			{
				_first = NO;
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performRetrieveTimeline] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[dic release];
		[self setupRetrieveDateTimeline:[NSDate dateWithTimeIntervalSinceNow:0]];
	}
}

- (void)performRetrieveHomeTimeline:(NSDictionary *)dic
{
	//LOG(@"[%@(%@) performRetrieveHomeTimeline]", [self className], _service);
	[self setupRetrieveDateTimeline:nil];
	@try
	{
		TwitterAPI *api = [self createAPI:dic];		
		if (api)
		{
			[api setTimeoutInterval:30];
			BOOL success = NO;
			int count = 3;
			while (count--)
			{
				id result = nil;
				if ([_service isEqualToString:Twitter])
				{
					int retry = 3;
					while (retry--)
					{
						LOG(@"[%@(%@) performRetrieveHomeTimeline] %d", [self className], _service, _tweets);
						result = [api homeTimelineWithSince:nil tweets:_tweets];
						if (result && ![result isKindOfClass:[NSError class]])
						{
							success = YES;
							break;
						}
					}
					LOG(@"[%@(%@) performRetrieveHomeTimeline] lock", [self className], _service);
					[self lockService];
					LOG(@"[%@(%@) performRetrieveHomeTimeline] locked", [self className], _service);
					@try
					{
						if (![self performRetrieve:api withObject:result kind:kindTimeline first:_first useTweet:YES])
						{
							count = 0;
						}
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@(%@) performRetrieveHomeTimeline] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
					}
					@finally
					{
						LOG(@"[%@(%@) performRetrieveHomeTimeline] unlock", [self className], _service);
						[self unlockService];
						LOG(@"[%@(%@) performRetrieveHomeTimeline] unlocked", [self className], _service);
					}
				}
				else
				{
					int retry = 3;
					while (retry--)
					{
						result = [api homeTimelineWithSince:nil];
						if (result && ![result isKindOfClass:[NSError class]])
						{
							success = YES;
							break;
						}
					}
					LOG(@"[%@(%@) performRetrieveHomeTimeline] lock", [self className], _service);
					[self lockService];
					LOG(@"[%@(%@) performRetrieveHomeTimeline] locked", [self className], _service);
					@try
					{
						[self performRetrieve:api withObject:result kind:kindTimeline first:_first];
						count = 0;
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@(%@) performRetrieveHomeTimeline] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
					}
					@finally
					{
						LOG(@"[%@(%@) performRetrieveHomeTimeline] unlock", [self className], _service);
						[self unlockService];
						LOG(@"[%@(%@) performRetrieveHomeTimeline] unlocked", [self className], _service);
					}
				}
			}
			if (success && _first)
			{
				_first = NO;
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performRetrieveHomeTimeline] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[dic release];
		[self setupRetrieveDateTimeline:[NSDate dateWithTimeIntervalSinceNow:0]];
	}
}

- (void)performRetrieveReplies:(NSDictionary *)dic
{
	//LOG(@"[%@(%@) performRetrieveReplies]", [self className], _service);
	[self setupRetrieveDateReplies:nil];
	@try
	{
		TwitterAPI *api = [self createAPI:dic];		
		if (api)
		{
			[api setTimeoutInterval:30];
			id result;
			int retry = 3;
			while (retry--)
			{
				result = [api replies];
				if (result && ![result isKindOfClass:[NSError class]])
				{
					break;
				}
			}
			LOG(@"[%@(%@) performRetrieveReplies] lock", [self className], _service);
			[self lockService];
			LOG(@"[%@(%@) performRetrieveReplies] locked", [self className], _service);
			@try
			{
				[self performRetrieve:api withObject:result kind:kindReply first:_firstReplies];
				if (_firstReplies)
				{
					_firstReplies = NO;
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performRetrieveReplies] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				LOG(@"[%@(%@) performRetrieveReplies] unlock", [self className], _service);
				[self unlockService];
				LOG(@"[%@(%@) performRetrieveReplies] unlocked", [self className], _service);
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performRetrieveReplies] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[dic release];
		[self setupRetrieveDateReplies:[NSDate dateWithTimeIntervalSinceNow:0]];
	}
}

- (void)performRetrieveDirectMessage:(NSDictionary *)dic
{
	//LOG(@"[%@(%@) performRetrieveDirectMessage]", [self className], _service);
	[self setupRetrieveDateDM:nil];
	@try
	{
		TwitterAPI *api = [self createAPI:dic];		
		if (api)
		{
			[api setTimeoutInterval:30];
			BOOL success = NO;
			id result;
			int retry = 3;
			while (retry--)
			{
				result = [api directMessage];
				if (result && ![result isKindOfClass:[NSError class]])
				{
					success = YES;
					break;
				}
			}
			LOG(@"[%@(%@) performRetrieveDirectMessage] lock", [self className], _service);
			[self lockService];
			LOG(@"[%@(%@) performRetrieveDirectMessage] locked", [self className], _service);
			@try
			{
				[self performRetrieve:api withObject:result kind:kindDM first:_firstDM];
				if (success && _firstDM)
				{
					_firstDM = NO;
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performRetrieveDirectMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				LOG(@"[%@(%@) performRetrieveDirectMessage] unlock", [self className], _service);
				[self unlockService];
				LOG(@"[%@(%@) performRetrieveDirectMessage] unlocked", [self className], _service);
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performRetrieveDirectMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[dic release];
		[self setupRetrieveDateDM:[NSDate dateWithTimeIntervalSinceNow:0]];
	}
}

- (void)performFavorite:(NSArray *)object
{
	//LOG(@"[%@(%@) performFavorite]", [self className], _service);
	@try
	{
		NSString *item_id = [object objectAtIndex:0];
		NSDictionary *dic = [object objectAtIndex:1];
		TwitterAPI *api = [self createAPI:dic];		
		if (api)
		{
			[api setTimeoutInterval:30];
			id result = [api favorite:item_id];
			[self determinePost:[self determinePost:result withAPI:api function:@"Favorite"]
					  withTitle:_title
					   function:@"Favorite"];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performFavorite] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[object release];
	}
}

- (void)performRetweetViaAPI:(NSArray *)object
{
	//LOG(@"[%@(%@) performRetweetViaAPI]", [self className], _service);
	@try
	{
		NSString *item_id = [object objectAtIndex:0];
		NSDictionary *dic = [object objectAtIndex:1];
		TwitterAPI *api = [self createAPI:dic];		
		if (api)
		{
			[api setTimeoutInterval:30];
			id result = [api retweet:item_id];
			//LOG2(@"[%@(%@) performRetweetViaAPI]\n%@", [self className], _service, result);
			[self determinePost:[self determinePost:result withAPI:api function:@"Retweet"]
					  withTitle:_title
					   function:@"Retweet"];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performRetweetViaAPI] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[object release];
	}
}

- (void)retrieveTimeline
{
	//LOG(@"[%@(%@) retrieveTimeline]", [self className], _service);
	BOOL retrieve = NO;
	@synchronized(self)
	{
		if (_dateTimeline)
		{
			retrieve = YES;
			[self setupRetrieveDateTimeline:nil];
		}
	}
	if (retrieve)
	{
		@try
		{
			NSString *user = [_preferences accountTwitterUser];
			NSString *pass = [_preferences accountTwitterPass];
			NSString *key = [_preferences accountTwitterOAuthKey];
			NSString *secret = [_preferences accountTwitterOAuthSecret];
			if (!pass)
			{
				pass = (NSString *)[NSNull null];
			}
			if (!key)
			{
				key = (NSString *)[NSNull null];
			}
			if (!secret)
			{
				secret = (NSString *)[NSNull null];
			}
			NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
								 user, TwitterUser,
								 pass, TwitterPass,
								 key, TwitterKey,
								 secret, TwitterSecret,
								 nil];
			[self performRetrieveHomeTimeline:dic];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) retrieveTimeline] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (void)retrieveReplies
{
	//LOG(@"[%@(%@) retrieveTimeline]", [self className], _service);
	BOOL retrieve = NO;
	@synchronized(self)
	{
		if (_dateReplies)
		{
			retrieve = YES;
			[self setupRetrieveDateReplies:nil];
		}
	}
	if (retrieve)
	{
		@try
		{
			NSString *user = [_preferences accountTwitterUser];
			NSString *pass = [_preferences accountTwitterPass];
			NSString *key = [_preferences accountTwitterOAuthKey];
			NSString *secret = [_preferences accountTwitterOAuthSecret];
			if (!pass)
			{
				pass = (NSString *)[NSNull null];
			}
			if (!key)
			{
				key = (NSString *)[NSNull null];
			}
			if (!secret)
			{
				secret = (NSString *)[NSNull null];
			}
			NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
								 user, TwitterUser,
								 pass, TwitterPass,
								 key, TwitterKey,
								 secret, TwitterSecret,
								 nil];
			[self performRetrieveReplies:dic];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) retrieveReplies] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (void)retrieveDirectMessage
{
	//LOG(@"[%@(%@) retrieveDirectMessage]", [self className], _service);
	BOOL retrieve = NO;
	@synchronized(self)
	{
		if (_dateDM)
		{
			retrieve = YES;
			[self setupRetrieveDateDM:nil];
		}
	}
	if (retrieve)
	{
		@try
		{
			NSString *user = [_preferences accountTwitterUser];
			NSString *pass = [_preferences accountTwitterPass];
			NSString *key = [_preferences accountTwitterOAuthKey];
			NSString *secret = [_preferences accountTwitterOAuthSecret];
			if (!pass)
			{
				pass = (NSString *)[NSNull null];
			}
			if (!key)
			{
				key = (NSString *)[NSNull null];
			}
			if (!secret)
			{
				secret = (NSString *)[NSNull null];
			}
			NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
								 user, TwitterUser,
								 pass, TwitterPass,
								 key, TwitterKey,
								 secret, TwitterSecret,
								 nil];
			[self performRetrieveDirectMessage:dic];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) retrieveDirectMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (void)doReply:(NSString *)item_id
	   withText:(BOOL)withText
{
	@try
	{
		//LOG(@"[%@(%@) doReply]\n%@", [self className], _service, item_id);
		id obj = [_controller fetchTimeline:item_id withService:_service];
		if (obj)
		{
			if (![[obj valueForKey:KeyKind] isEqualToString:KindDM])
			{
				NSString *text = @"";
				if (withText)
				{
					text = [NSString stringWithFormat:@"@%@ ",
							[obj valueForKey:KeyUser]];
				}
				[_controller setReply:[self replyAttributedString:[NSString stringWithFormat:@"Reply to: %@%@",
															  [obj valueForKey:KeyText], [obj valueForKey:KeyComment]]]
						  withService:_service
							inReplyTo:item_id
								 text:text];
			}
			else
			{
				NSString *text = @"";
				if (withText)
				{
					text = [NSString stringWithFormat:@"d %@ ",
							[obj valueForKey:KeyUser]];
				}
				[_controller setReply:[self replyAttributedString:[NSString stringWithFormat:@"Reply DM: %@%@",
															  [obj valueForKey:KeyText], [obj valueForKey:KeyComment]]]
						  withService:_service
							inReplyTo:item_id
								 text:text];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) doReply] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

- (void)favorite:(NSString *)item_id
{
	//LOG(@"[%@(%@) favorite]", [self className], _service);
	@synchronized(self)
	{
		@try
		{
			id item = [self fetch:item_id withService:_service];
			if (item && ![[item valueForKey:KeyKind] isEqualToString:KindDM])
			{
				NSString *user = [_preferences accountTwitterUser];
				NSString *pass = [_preferences accountTwitterPass];
				NSString *key = [_preferences accountTwitterOAuthKey];
				NSString *secret = [_preferences accountTwitterOAuthSecret];
				if (!pass)
				{
					pass = (NSString *)[NSNull null];
				}
				if (!key)
				{
					key = (NSString *)[NSNull null];
				}
				if (!secret)
				{
					secret = (NSString *)[NSNull null];
				}
				NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
									 user, TwitterUser,
									 pass, TwitterPass,
									 key, TwitterKey,
									 secret, TwitterSecret,
									 nil];
				[self performFavorite:[[NSArray alloc] initWithObjects:item_id, dic, nil]];
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) favorite] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (NSString *)permalinkURL:(NSString *)item_id
{
	NSString *url = nil;
	@try
	{
		id item = [self fetch:item_id withService:_service];
		if (item)
		{
			if ([[item valueForKey:KeyKind] isEqualToString:KindDM])
			{
				url = @"http://twitter.com/inbox";
			}
			else
			{
				NSString *user = [item valueForKey:KeyUser];
				NSString *retweetedId = [item valueForKey:KeyRetweetedId];
				if (retweetedId && ![retweetedId isEqualToString:@""])
				{
					item_id = retweetedId;
				}
				url = [NSString stringWithFormat:@"http://twitter.com/%@/statuses/%@", user, item_id];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) permalinkURL] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return url;
}

- (void)permalink:(NSString *)item_id
{
	@try
	{
		NSString *url = [self permalinkURL:item_id];
		if (url)
		{
			[self doOpenURL:url];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) permalink] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

- (void)doRetweetViaAPI:(NSString *)item_id
{
	LOG2(@"[%@(%@) doRetweetViaAPI]\n%@", [self className], _service, item_id);
	@synchronized(self)
	{
		@try
		{
			id item = [self fetch:item_id withService:_service];
			if (item && ![[item valueForKey:KeyKind] isEqualToString:KindDM])
			{
				NSString *user = [_preferences accountTwitterUser];
				NSString *pass = [_preferences accountTwitterPass];
				NSString *key = [_preferences accountTwitterOAuthKey];
				NSString *secret = [_preferences accountTwitterOAuthSecret];
				if (!pass)
				{
					pass = (NSString *)[NSNull null];
				}
				if (!key)
				{
					key = (NSString *)[NSNull null];
				}
				if (!secret)
				{
					secret = (NSString *)[NSNull null];
				}
				NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
									 user, TwitterUser,
									 pass, TwitterPass,
									 key, TwitterKey,
									 secret, TwitterSecret,
									 nil];
				[self performRetweetViaAPI:[[NSArray alloc] initWithObjects:item_id, dic, nil]];
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) favorite] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (void)doDirectMessage:(NSString *)item_id
{
	@try
	{
		//LOG(@"[%@(%@) doDirectMessage]\n%@", [self className], _service, item_id);
		id obj = [_controller fetchTimeline:item_id withService:_service];
		if (obj)
		{
			NSString *text = @"";
			text = [NSString stringWithFormat:@"d %@ ",
					[obj valueForKey:KeyUser]];
			[_controller setReply:[self replyAttributedString:[NSString stringWithFormat:@"DM: %@%@",
															   [obj valueForKey:KeyText], [obj valueForKey:KeyComment]]]
					  withService:_service
						inReplyTo:item_id
							 text:text];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) doDirectMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

- (BOOL)userStream
{
	if (!_stream)
	{
		LOG2(@"[%@(%@) userStream]", [self className], _service);
		@try
		{
			NSString *user = [_preferences accountTwitterUser];
			NSString *pass = [_preferences accountTwitterPass];
			NSString *key = [_preferences accountTwitterOAuthKey];
			NSString *secret = [_preferences accountTwitterOAuthSecret];
			if (!pass)
			{
				pass = (NSString *)[NSNull null];
			}
			if (!key)
			{
				key = (NSString *)[NSNull null];
			}
			if (!secret)
			{
				secret = (NSString *)[NSNull null];
			}
			NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
								 user, TwitterUser,
								 pass, TwitterPass,
								 key, TwitterKey,
								 secret, TwitterSecret,
								 nil];
			_stream = [self createAPI:dic];		
			if (_stream)
			{
				[_stream retain];
				[_stream setTimeoutInterval:120];
				[_stream userStream:self];
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) userStream] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
		return YES;
	}
	return NO;
}

- (void)closeStream
{
	if (_stream)
	{
		LOG2(@"[%@(%@) closeStream]", [self className], _service);
		[_stream release];
		_stream = nil;
	}
}

- (BOOL)isStream
{
	if (_stream)
	{
		return YES;
	}
	return NO;
}

- (void)receiveStream:(id)userStream
{
	[_lock lock];
	@try
	{
		LOG(@"[%@ receiveStream] %@", [self className], [userStream className]);
		NSMutableArray *list = [[NSMutableArray alloc] init];
		NSMutableArray *notify = [[NSMutableArray alloc] init];
		[self lockService];
		@try
		{
			int n = 0;
			NSEnumerator *enumStream = [userStream objectEnumerator];
			id stream;
			while ((stream = [enumStream nextObject]))
			{
				NSArray *array = [stream componentsSeparatedByString:@"\n"];
				NSEnumerator *enumerator = [array objectEnumerator];
				id str = nil;
				while ((str = [enumerator nextObject]))
				{
					str = [[str componentsSeparatedByString:@"\r"] objectAtIndex:0];
					int kind = -1;
					if ([str isEqualToString:@""])
					{
					}
					else if ([str lengthOfRegularExpression:@"\"friends\"\\:\\["] > 0)
					{
					}
					else if ([str lengthOfRegularExpression:@"\"delete\"\\:\\{"] > 0)
					{
					}
					else if ([str lengthOfRegularExpression:@"\"event\"\\:"] > 0)
					{
					}
					else if ([str lengthOfRegularExpression:@"\"direct_message\"\\:"] > 0)
					{
						kind = kindDM;
					}
					else if ([str lengthOfRegularExpression:@"\"text\"\\:"] > 0)
					{
						kind = kindTimeline;
					}
					if (kind < 0)
					{
						if (([str length] > 0) && ![str isEqualToString:@"\n"])
						{
							LOG2(@"[%@ receiveStream] %@", [self className], str);
							@try
							{
								id json = [str JSONValue];
								id obj = [json valueForKey:@"delete"];
								if (obj)
								{
									id delete = [obj valueForKey:@"status"];
									if (delete)
									{
										if ([self fetchALL:[delete valueForKey:@"id_str"] withService:_service])
										{
											LOG2(@"[%@ receiveStream] delete(status) %@", [self className], [delete valueForKey:@"id_str"]);
											[self deleteTimeline:[delete valueForKey:@"id_str"]
													 withService:_service];
										}
									}
									delete = [obj valueForKey:@"direct_message"];
									if (delete)
									{
										NSString* item_id = [NSString stringWithFormat:
															 @"DM%@", [delete valueForKey:@"id"]];
										if ([self fetchALL:item_id
											   withService:_service])
										{
											LOG2(@"[%@ receiveStream] delete(direct_message) %@", [self className], item_id);
											[self deleteTimeline:item_id
													 withService:_service];
										}
									}
								}
							}
							@catch (NSException *exception)
							{
								EXPLOG(@"[%@ receiveStream] EXCEPTION\n%@: %@\n%@", [self className], [exception name], [exception reason], userStream);
							}
						}
					}
					else
					{
						LOG(@"[%@ receiveStream] %@", [self className], [str replaceWithExpression:@"^(.*?\\:).*$" replace:@"\\1" options:OgreMultilineOption]);
						@try
						{
							id json = [str JSONValue];
							if (kind == kindDM)
							{
								json = [json valueForKey:@"direct_message"];
							}
							id item = [self parse:json
										 withUser:[_preferences accountTwitterUser]
											 kind:kind
											first:NO];
							if (item)
							{
								[item retain];
								@try
								{
									LOG(@"[%@ receiveStream] item %@", [self className], [item valueForKey:KeyId]);
									if ([self addTimeline:item withFirst:NO])
									{
										NSString *channel = [item valueForKey:KeyChannel];
										if (channel && ![channel isEqualToString:@""])
										{
											[self registComplete:channel
													flagIdentica:[_service isEqualToString:Identica]];
										}
										[self registComplete:[item valueForKey:KeyText]
												flagIdentica:[_service isEqualToString:Identica]];
										if ([_preferences generalGrowlOldOrder])
										{
											[notify addObject:item];
										}
										else
										{
											[notify insertObject:item atIndex:0];
										}
									}
									[list addObject:item];
									n++;
								}
								@catch (NSException *exception)
								{
									EXPLOG(@"[%@(%@) receiveStream] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
								}
								@finally
								{
									[item release];
								}
							}
						}
						@catch (NSException *exception)
						{
							EXPLOG(@"[%@ receiveStream] EXCEPTION\n%@: %@\n%@", [self className], [exception name], [exception reason], userStream);
						}
					}
				}
			}
			if (n > 0)
			{
				[self finishAddTimeline:list withNotify:notify];
				LOG(@"[%@ receiveStream] notify %d", [self className], [notify count]);
			}
		}
		@catch (NSException * exception)
		{
			EXPLOG(@"[%@(%@) receiveStream] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
		@finally
		{
			[self unlockService];
			[notify release];
			[list release];
		}
	}
	@catch (NSException * exception)
	{
		EXPLOG(@"[%@(%@) receiveStream] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[_lock unlock];
	}
}

- (void)finishStream
{
	[self closeStream];
}

- (void)errorStream:(NSError *)error
{
	[_controller notifyWithTitle:[NSString stringWithFormat:@"%@ Streaming API", _service]
					 description:[error localizedDescription]
				notificationName:GrowlAfficheurFailureNotify
						iconData:[NSImage imageNamed:_errorIcon]
					clickContext:nil];
	[self closeStream];
}

- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
	LOG(@"[%@ didReceiveResponse] %@", [self className], [response className]);
	if (![response isKindOfClass:[NSHTTPURLResponse class]])
	{
		EXPLOG(@"[%@ didReceiveResponse] Unknown response type:%@", [self className], response);
	}
	else
	{
		LOG(@"[%@ didReceiveResponse] %d:%@\n%@", [self className], [(NSHTTPURLResponse *)response statusCode], [NSHTTPURLResponse localizedStringForStatusCode:[(NSHTTPURLResponse *)response statusCode]], [[NSDictionary alloc] initWithDictionary:[(NSHTTPURLResponse *)response allHeaderFields]]);
	}
}

- (void)connection:(NSURLConnection *)connection
	didReceiveData:(NSData *)data
{
	LOG(@"[%@ didReceiveData] %@", [self className], [data className]);
	[_controller userStream:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
				withService:_service];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
	LOG2(@"[%@ connectionDidFinishLoading] %@", [self className], [connection className]);
	[_controller finishUserStreamWithService:_service];
}

- (void)connection:(NSURLConnection *)connection
  didFailWithError:(NSError *)error
{
	LOG2(@"[%@ didFailWithError] %@", [self className], [[NSError alloc] initWithDomain:[error domain] code:[error code] userInfo:[error userInfo]]);
	[_controller errorUserStream:error
					 withService:_service];
}

@end
