//
//  ServiceFaceBook.m
//  Afficheur
//
//  Created by kichi on 11/01/02.
//  Copyright 2011 Katsuhiko Ichinose. All rights reserved.
//

#import "ServiceFaceBook.h"
#import "ServiceFaceBookKey.h"
#import "AfficheurController.h"
#import "AfficheurPreferences.h"
#import "AfficheurGrowl.h"
#import "NSStringOgreKit.h"
#import <OgreKit/OgreKit.h>
#import <JSON/JSON.h>
#import "i18n.h"


@implementation ServiceFaceBook

static NSString* AUTHORIZE_PATH = @"https://graph.facebook.com/oauth/authorize";
static NSString* REDIRECT_URI = @"http://www.facebook.com/connect/login_success.html";
static NSString* TYPE = @"user_agent";
static NSString* SCOPE = @"publish_stream,read_stream,offline_access,user_about_me,user_events,user_groups,user_status,user_likes,user_notes,user_photos,user_videos,user_photo_video_tags,read_mailbox,friends_about_me,friends_status,friends_likes,friends_notes,friends_photos,friends_videos,friends_photo_video_tags";

- (id)init
{
	self = (ServiceFaceBook*)[super init];
	if (self)
	{
		_title = nil;
		_first = YES;
		_user_id = nil;
		_user_name = nil;
		_access_token = nil;
	}
	return self;
}


- (void)dealloc
{
	[_title release];
	[_userProfile release];
	if (_user_id)
	{
		[_user_id release];
	}
	if (_user_name)
	{
		[_user_name release];
	}
	if (_access_token)
	{
		[_access_token release];
	}
	[super dealloc];
}

- (id)init:(NSString *)service
WithController:(AfficheurController *)controller
preferences:(AfficheurPreferences *)preferences
{
	self = [super init:service
		WithController:controller
		   preferences:preferences];
	if (self)
	{
		_userProfile = [[NSMutableDictionary alloc] init];
		if ([service isEqualToString:FaceBook])
		{
			_notify = GrowlAfficheurFaceBookNotify;
			_notifyComment = GrowlAfficheurFaceBookCommentNotify;
			_notifyGroup = GrowlAfficheurFaceBookGroupNotify;
			_notifyReply = GrowlAfficheurFaceBookReplyNotify;
			_notifyError = GrowlAfficheurFaceBookErrorNotify;
		}
	}
	return self;
}

- (void)setupXMPP:(ServiceXMPP *)xmpp
{
}

- (void)setUserId:(NSString *)userID
{
	if (_user_id)
	{
		[_user_id release];
	}
	_user_id = [userID copy];
}

- (NSString *)userId
{
	LOG2(@"[%@ userId]\n%@", [self className], _user_id);
	return [[_user_id copy] autorelease];
}

- (void)setUserName:(NSString *)userName
{
	if (_user_name)
	{
		[_user_name release];
	}
	_user_name = [userName copy];
}

- (NSString *)userName
{
	LOG2(@"[%@ userName]\n%@", [self className], _user_name);
	return [[_user_name copy] autorelease];
}

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

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

- (NSDictionary *)apiParam
{
	NSString *userId = [_preferences accountFaceBookId];
	NSString *name = [_preferences accountFaceBookUser];
	NSString *token = [_preferences accountFaceBookOAuthToken];
	return [[NSDictionary alloc] initWithObjectsAndKeys:
			token, FaceBookToken,
			userId, FaceBookId,
			name, FaceBookUser,
			nil];
}

- (FaceBookAPI *)createAPI:(NSDictionary *)dic
{
	FaceBookAPI *api = [[[FaceBookAPI alloc] initWithDictionary:dic] autorelease];
	if (api)
	{
	}
	return api;
}

- (NSString *)authorize
{
	LOG(@"[%@ authorize]", [self className]);
	return [NSString stringWithFormat:@"%@?client_id=%@&redirect_uri=%@&type=%@&scope=%@",
			AUTHORIZE_PATH, CLIENT_ID, REDIRECT_URI, TYPE, SCOPE];
}

- (BOOL)access_token:(NSString *)url
{
	LOG2(@"[%@ access_token]\n%@", [self className], url);
	NSArray *arry = [url componentsSeparatedByString:@"#"];
	if ([arry count] <= 1)
	{
		return NO;
	}
	LOG2(@"[%@ access_token]\n%@", [self className], [arry objectAtIndex:1]);
	arry = [[arry objectAtIndex:1] componentsSeparatedByString:@"&"];
	if ([arry count] <= 1)
	{
		return NO;
	}
	LOG2(@"[%@ access_token]\n%@", [self className], [arry objectAtIndex:0]);
	NSArray *token = [[arry objectAtIndex:0] componentsSeparatedByString:@"="];
	if ([token count] <= 1)
	{
		return NO;
	}
	LOG2(@"[%@ access_token]\n%@", [self className], [token objectAtIndex:0]);
	if (![[token objectAtIndex:0] isEqualToString:@"access_token"])
	{
		return NO;
	}
	LOG2(@"[%@ access_token]\n%@", [self className], [token objectAtIndex:1]);
	LOG2(@"[%@ access_token]\n%@", [self className], [HTTP urlDencode:[token objectAtIndex:1]]);
	[self setAccessToken:[HTTP urlDencode:[token objectAtIndex:1]]];
	
	return YES;
}

- (BOOL)me
{
	@try
	{
		FaceBookAPI *api = [self createAPI:[self apiParam]];
		id result = [api me];
		LOG2(@"[%@ me]\n%@", [self className], result);
		if ([result isKindOfClass:[NSDictionary class]])
		{
			NSString *name = [result valueForKey:@"name"];
			id userId = [result valueForKey:@"id"];
			if (name && userId)
			{
				LOG2(@"[%@ me]\n%@\n%@", [self className], userId, name);
				if ([userId isKindOfClass:[NSString class]])
				{
					[self setUserId:userId];
				}
				else
				{
					[self setUserId:[NSString stringWithFormat:@"%ld", [userId longValue]]];
				}
				[self setUserName:name];
				return YES;
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) me] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return NO;
}

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

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

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

- (NSString *)notify:(NSString *)notify
			  withId:(NSString *)item_id
			  fromId:(NSString *)from_id
			  userId:(NSString *)user_id
{
	if ([item_id lengthOfRegularExpression:@"^.*?_.*?_"] > 0)
	{
		notify = _notifyComment;
		if (![from_id isEqualToString:user_id])
		{
			NSString *org = [item_id replaceWithExpression:@"^(.*?_.*?)_.*$" replace:@"\\1"];
			if ([self begins:org withService:_service user:[_preferences accountFaceBookUser]])
			{
				notify = _notifyReply;
			}
		}
	}
	return notify;
}

- (NSString *)string:(NSString *)text
		  withAppend:(NSString *)append
{
	NSString *lf = @"\n";
	if (!text)
	{
		text = @"";
	}
	if ([text isEqualToString:@""])
	{
		lf = @"";
	}
	if (append)
	{
		text = [NSString stringWithFormat:@"%@%@%@", text, lf, append];
	}
	return text;
}

- (NSDictionary *)parseFeed:(NSDictionary *)obj
				   withUser:(NSString *)user_id
					   kind:(int)kind
					  first:(BOOL)first
{
	NSDictionary *item = nil;
	@try
	{
		NSString *item_id = [obj valueForKey:@"id"];
		NSString *text = [obj valueForKey:@"message"];
		NSString *notify = _notify;
		if (!text)
		{
			text = @"";
		}
		if (!text)
		{
			LOG2(@"[%@(%@) parsePost:%d] !text\n%@", [self className], _service, kind, obj);
			text = @"";
		}
		NSString *name = @"";
		NSString *user_profile = @"";
		int hasKeyword = -1;
		NSDictionary *from = [obj valueForKey:@"from"];
		if (from)
		{
			NSString *fromId = [from valueForKey:@"id"];
			if (fromId)
			{
				user_profile = [[self createAPI:[self apiParam]] stringGraphURL:[NSString stringWithFormat:@"%@/picture", fromId]];
				notify = [self notify:notify withId:item_id fromId:fromId userId:user_id];
				if (![fromId isEqualToString:user_id])
				{
					hasKeyword = [self hasKeywords:
								  [NSArray arrayWithObjects:
								   text, name, nil]];
					if (hasKeyword == 0)
					{
						notify = GrowlAfficheurKeywordsNotify;
						first = NO;
					}
				}
			}
			name = [from valueForKey:@"name"];
		}
		NSString *source = nil;
		NSString *sourceId = nil;
		NSDictionary *application = [obj valueForKey:@"application"];
		if (application)
		{
			source = [application valueForKey:@"name"];
			sourceId = [application valueForKey:@"id"];
		}
		if (!source)
		{
			source = @"";
		}
		NSString *kindStr = KindTimeline;
		NSString *category = KindTimeline;
		if ([notify isEqualToString:_notifyReply])
		{
			kindStr = KindReply;
			category = KindReply;
			first = NO;
		}
		NSString *date = [obj valueForKey:@"created_time"];
		date = [date replaceWithExpression:@"T" replace:@" "];
		date = [date replaceWithExpression:@"\\+" replace:@" +"];
		item = [self createItem:item_id
					   withUser:name
						   name:@""
						channel:@""
							rid:@""
						   text:text
						comment:@""
						 source:source
					 created_at:[NSDate dateWithString:date]
				   user_profile:user_profile
						 notify:notify
						   kind:kindStr
					   category:category
					inReplyUser:nil
						keyword:hasKeyword
						  first:first];
		if (item)
		{
			NSMutableDictionary *mutable_item = [NSMutableDictionary dictionaryWithDictionary:item];
			NSString *value = [obj valueForKey:@"picture"];
			if (value)
			{
				[mutable_item setValue:value forKey:KeyPhotoURL];
			}
			value = [obj valueForKey:@"icon"];
			if (value)
			{
				value = [NSString stringWithFormat:@"%@%@", ProfileIcon, value];
				[mutable_item setValue:value
								forKey:KeyIcon];
				NSImage *iconImage = [_controller profileImage:value];
				if (iconImage)
				{
					[mutable_item setValue:iconImage
									forKey:KeyIconImage];
				}
			}
			else if (sourceId)
			{
				NSString *sourceIcon = [[self createAPI:[self apiParam]]
										stringGraphURL:[NSString stringWithFormat:@"%@/picture", sourceId]];
				value = [NSString stringWithFormat:@"%@%@", ProfileSource, sourceIcon];
				[mutable_item setValue:value
								forKey:KeySourceIcon];
				NSImage *iconImage = [_controller profileImage:value];
				if (iconImage)
				{
					[mutable_item setValue:[Service resizeImage:iconImage withSize:NSMakeSize(16, 16)]
									forKey:KeySourceIconImage];
				}
			}
			value = [obj valueForKey:@"name"];
			if (value)
			{
				[mutable_item setValue:value
								forKey:KeyFbName];
			}
			value = [obj valueForKey:@"caption"];
			if (value)
			{
				[mutable_item setValue:value
								forKey:KeyFbCaption];
			}
			value = [obj valueForKey:@"description"];
			if (value)
			{
				[mutable_item setValue:value
								forKey:KeyFbDescription];
			}
			value = [obj valueForKey:@"link"];
			if (value)
			{
				[mutable_item setValue:value
								forKey:KeyFbLink];
			}
			item = [NSDictionary dictionaryWithDictionary:mutable_item];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) parsePost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return item;
}

- (NSDictionary *)parse:(NSDictionary *)obj
			withComment:(NSString *)comment_id
				   user:(NSString *)user_id
				   kind:(int)kind
				  first:(BOOL)first
{
	NSDictionary *item = nil;
	@try
	{
		NSString *notify = _notifyComment;
		NSString *item_id = [obj valueForKey:@"id"];
		LOG(@"[%@(%@) parseWithComment:%d]\n%@", [self className], _service, kind, item_id);
		NSString *text = [obj valueForKey:@"message"];
		if (!text)
		{
			LOG2(@"[%@(%@) parseWithComment:%d] !text\n%@", [self className], _service, kind, obj);
			text = @"";
		}
		NSString *link = [obj valueForKey:@"link"];
		if (link && ![text lengthOfRegularExpression:[link regularizeString] withOptions:OgreMultilineOption])
		{
			text = [self string:text withAppend:link];
		}
		NSString *name = @"";
		NSString *user_profile = @"";
		NSDictionary *from = [obj valueForKey:@"from"];
		if (from)
		{
			NSString *fromId = [from valueForKey:@"id"];
			if (fromId)
			{
				FaceBookAPI *api = [self createAPI:[self apiParam]];
				user_profile = [api stringGraphURL:[NSString stringWithFormat:@"%@/picture", fromId]];
				notify = [self notify:notify withId:item_id fromId:fromId userId:user_id];
			}
			name = [from valueForKey:@"name"];
		}
		NSString *comment_text = @"";
		NSString *comment_user = nil;
		id comment = [self fetch:comment_id withService:_service];
		if (comment)
		{
			comment_user = [comment valueForKey:KeyUser];
			comment_text = [NSString stringWithFormat:@"(on %@: %@)",
							comment_user, [comment valueForKey:KeyText]];
		}
		NSString *source = nil;
		NSDictionary *application = [obj valueForKey:@"application"];
		if (application)
		{
			source = [application valueForKey:@"name"];
		}
		if (!source)
		{
			source = @"";
		}
		NSString *kindStr = KindTimeline;
		NSString *category = KindTimeline;
		if ([notify isEqualToString:_notifyReply])
		{
			kindStr = KindReply;
			category = KindReply;
			first = NO;
		}
		NSString *date = [obj valueForKey:@"created_time"];
		date = [date replaceWithExpression:@"T" replace:@" "];
		date = [date replaceWithExpression:@"\\+" replace:@" +"];
		item = [self createItem:item_id
					   withUser:name
						   name:@""
						channel:@""
							rid:@""
						   text:text
						comment:comment_text
						 source:source
					 created_at:[NSDate dateWithString:date]
				   user_profile:user_profile
						 notify:notify
						   kind:kindStr
					   category:category
					inReplyUser:comment_user
						keyword:NO
						  first:first];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) parseWithComment] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return item;
}

- (NSDictionary *)parse:(NSDictionary *)obj
			   withUser:(NSString *)user_id
				   kind:(int)kind
				  first:(BOOL)first
{
	NSString *item_id = [obj valueForKey:@"id"];
	LOG(@"[%@(%@) parse:%d] id\n%@", [self className], _service, kind, item_id);
	if ([item_id lengthOfRegularExpression:@".*?_.*?_.*"] > 0)
		//[self fetch:[item_id replaceWithExpression:@"^(.*?_.*?)_.*$" replace:@"\\1"] withService:_service])
	{
		LOG2(@"[%@(%@) parse:%d] comment\n%@", [self className], _service, kind, item_id);
		return [self parse:obj
			   withComment:[item_id replaceWithExpression:@"^(.*?_.*?)_.*$" replace:@"\\1"]
					  user:user_id
					  kind:kind first:first];
	}
	NSString *type = [obj valueForKey:@"type"];
	if (!type || [type isEqualToString:@""])
	{
		LOG2(@"[%@(%@) parse:%d] no type\n%@", [self className], _service, kind, obj);
		return nil;
	}
	else if ([type isEqualToString:@"link"])
	{
		return [self parseFeed:obj withUser:user_id kind:kind first:first];
	}
	else if ([type isEqualToString:@"status"])
	{
		return [self parseFeed:obj withUser:user_id kind:kind first:first];
	}
	else if ([type isEqualToString:@"video"])
	{
		return [self parseFeed:obj withUser:user_id kind:kind first:first];
	}
	else
	{
		LOG2(@"[%@(%@) parse:%d]\n%@", [self className], _service, kind, obj);
	}
	return nil;
}

- (NSDictionary *)parsePhotoURL:(NSDictionary *)item
{
	@try
	{
		item = [super parsePhotoURL:item];
		if (item)
		{
			NSString *photo_url = [item valueForKey:KeyPhotoURL];
			if (photo_url && ![photo_url isKindOfClass:[NSNull class]] && ![photo_url isEqualToString:@""])
			{
				NSString *imageURL = [item valueForKey:KeyImageURL];
				if (!imageURL || [imageURL isKindOfClass:[NSNull class]] || [imageURL isEqualToString:@""])
				{
					NSMutableDictionary *mutable_item = [NSMutableDictionary dictionaryWithDictionary:item];
					NSString *photoURL = [NSString stringWithFormat:@"%@%@", ProfilePhoto, photo_url];
					[mutable_item setValue:photoURL
									forKey:KeyImageURL];
					[mutable_item setValue:[NSNumber numberWithInt:-1]
									forKey:KeyWidth];
					item = [NSDictionary dictionaryWithDictionary:mutable_item];
					_changeHeight = YES;
				}
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) parsePhotoURL] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return item;
}

- (BOOL)retrievePost:(NSString *)item_id
			 withAPI:(FaceBookAPI *)api
{
	BOOL retrieve = NO;
	@try
	{
		id result = [api getGraph:item_id];
		if ([result isKindOfClass:[NSDictionary class]])
		{
			NSMutableArray *list = [[NSMutableArray alloc] init];
			NSMutableArray *notify = [[NSMutableArray alloc] init];
			@try
			{
				id item = [self parse:result withUser:[api userId] kind:kindTimeline first:NO];
				if (item)
				{
					[item retain];
					@try
					{
						[list addObject:item];
						if ([self addTimeline:item withFirst:NO])
						{
							retrieve = YES;
							//LOG(@"[%@(%@) performRetrieve]\n%@", [self className], _service, item);
							NSString *channel = [item valueForKey:KeyChannel];
							if (channel && ![channel isEqualToString:@""])
							{
								[self registComplete:channel];
							}
							if ([_preferences generalGrowlOldOrder])
							{
								[notify addObject:item];
							}
							else
							{
								[notify insertObject:item atIndex:0];
							}
							[self finishAddTimeline:list withNotify:notify];
						}
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@(%@) retrievePost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
					}
					@finally
					{
						[item release];
					}
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) retrievePost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return retrieve;
}

- (NSString *)determinePost:(id)object
					withAPI:(FaceBookAPI *)api
				   function:(NSString *)function
{
	NSString *description = [NSString stringWithFormat:@"%@ was failure.", function];
	@try
	{
		NSString *item_id = nil;
		if ([object isKindOfClass:[NSError class]])
		{
			NSString *desc = [api errorLocalizedDescription:object];
			if (desc)
			{
				description = [NSString stringWithFormat:@"%@\n(%@)", description, desc];
			}
		}
		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 if ((item_id = [object valueForKey:@"id"]))
		{
			if ([self retrievePost:item_id withAPI:api])
			{
				description = nil;
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) determinePost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return description;
}

- (void)post:(NSString *)status
   withReply:(NSString *)reply
{
	[self lockService];
	@try
	{
		FaceBookAPI *api = [self createAPI:[self apiParam]];
		id result = nil;
		NSString *function = @"Post";
		if (reply && ![reply isEqualToString:@""])
		{
			result = [api comment:status withId:reply];
			function = @"Comment";
		}
		else
		{
			result = [api postFeed:status];
		}
		LOG2(@"[%@(%@) update]\n%@", [self className], _service, result);
		[self determinePost:[self determinePost:result
										withAPI:api
									   function:function]
				  withTitle:_title
				   function:function];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) post] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[self unlockService];
	}
}

- (void)performRetrieve:(FaceBookAPI *)api
			 withObject:(id)object
				   kind:(int)kind
				  first:(BOOL)first
{
	LOG2(@"[%@(%@) performRetrieve:%d] %@", [self className], _service, kind, [object className]);
	@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:[NSDictionary class]])
		{
			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] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				[item release];
			}
		}
		else
		{
			id stream = [object valueForKey:@"data"];
			if (stream && [stream isKindOfClass:[NSArray class]])
			{
				NSMutableArray *list = [[NSMutableArray alloc] init];
				NSMutableArray *notify = [[NSMutableArray alloc] init];
				@try
				{
					int i = 0;
					while (i < [stream count])
					{
						id obj = [stream objectAtIndex:i++];
						id item = [self parse:obj withUser:[api userId] kind:kind first:first];
						if (item)
						{
							[item retain];
							@try
							{
								[list addObject:item];
								if ([self addTimeline:item withFirst:first])
								{
									//LOG(@"[%@(%@) performRetrieve]\n%@", [self className], _service, item);
									NSString *channel = [item valueForKey:KeyChannel];
									if (channel && ![channel isEqualToString:@""])
									{
										[self registComplete:channel];
									}
									if ([_preferences generalGrowlOldOrder])
									{
										[notify addObject:item];
									}
									else
									{
										[notify insertObject:item atIndex:0];
									}
								}
								NSDictionary *comments = [obj valueForKey:@"comments"];
								if (comments)
								{
									NSArray *comment_data = [comments valueForKey:@"data"];
									if (comment_data)
									{
										NSString *comment_id = [item valueForKey:KeyId];
										NSEnumerator *enumerator = [comment_data objectEnumerator];
										id comment;
										while ((comment = [enumerator nextObject]))
										{
											id comment_item = [self parse:comment withComment:comment_id user:[api userId] kind:kind first:NO];
											if (comment_item)
											{
												[comment_item retain];
												@try
												{
													[list addObject:comment_item];
													if ([self addTimeline:comment_item withFirst:first])
													{
														//LOG(@"[%@(%@) performRetrieve]\n%@", [self className], _service, item);
														NSString *channel = [comment_item valueForKey:KeyChannel];
														if (channel && ![channel isEqualToString:@""])
														{
															[self registComplete:channel];
														}
														if ([_preferences generalGrowlOldOrder])
														{
															[notify addObject:comment_item];
														}
														else
														{
															[notify insertObject:comment_item atIndex:0];
														}
													}
												}
												@catch (NSException *exception)
												{
													EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
												}
												@finally
												{
													[comment_item release];
												}
											}
										}
									}
								}
							}
							@catch (NSException *exception)
							{
								EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
							}
							@finally
							{
								[item release];
							}
						}
					}
					[self finishAddTimeline:list withNotify:notify];
				}
				@catch (NSException *exception)
				{
					EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
				}
				@finally
				{
					[notify release];
					[list release];
				}
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

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

- (void)retrieveHomeFeed
{
	LOG2(@"[%@(%@) retrieveHomeFeed]", [self className], _service);
	BOOL retrieve = NO;
	@synchronized(self)
	{
		if (_dateTimeline)
		{
			retrieve = YES;
			[self setupRetrieveDateTimeline:nil];
		}
	}
	if (retrieve)
	{
		@try
		{
			[self performRetrieveHomeFeed:[self apiParam]];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) retrieveHomeFeed] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (void)doReply:(NSString *)item_id
	   withText:(BOOL)withText
{
	@try
	{
		LOG2(@"[%@(%@) doReply]\n%@", [self className], _service, item_id);
		id obj = [_controller fetchTimelineALL:item_id withService:_service];
		if (obj)
		{
			NSString *replyTo = @"";
			if ([item_id lengthOfRegularExpression:@".*?_.*?_.*"] > 0)
			{
				item_id = [item_id replaceWithExpression:@"^(.*?_.*?)_.*$"
												 replace:@"\\1"];
			}
			replyTo = [NSString stringWithFormat:@"Comment to: %@%@",
					   [obj valueForKey:KeyText], [obj valueForKey:KeyComment]];
			LOG2(@"[%@(%@) doReply]\nreply to: %@\n%@", [self className], _service, [obj valueForKey:KeyInReplyUser], item_id);
			NSString *text = @"";
			if (withText)
			{
				text = @"";//[NSString stringWithFormat:@"@%@ ", [obj valueForKey:KeyUser]];
			}
			[_controller setReply:[self replyAttributedString:replyTo]
					  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
{
	@try
	{
		FaceBookAPI *api = [self createAPI:[self apiParam]];
		/*id result =*/ [api likes:item_id];
		//LOG(@"[%@(%@) favorite]\n%@", [self className], _service, result);
		[self determinePost:nil
				  withTitle:_title
				   function:@"Likes"];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) doReply] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

- (NSString *)permalinkURL:(NSString *)item_id
{
	if ([item_id lengthOfRegularExpression:@".*?_.*?_.*"] > 0)
	{
		item_id = [item_id replaceWithExpression:@"^(.*?_.*?)_.*$"
										 replace:@"\\1"];
	}
	return [item_id replaceWithExpression:@"(.*?)_(.*)"
								  replace:@"http://www.facebook.com/\\1/posts/\\2"];
}

- (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)openURL:(NSString *)item_id
{
	[super openURL:item_id];
	@try
	{
		id item = [self fetchALL:item_id withService:_service];
		if (item)
		{
			NSString *link = [item valueForKey:KeyFbLink];
			[self doOpenURL:link];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) openURL] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

@end
