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

#import "ServiceJaiku.h"
#import "ServiceJaikuKey.h"
#import "AfficheurController.h"
#import "AfficheurPreferences.h"
#import "AfficheurGrowl.h"
#import "NSStringOgreKit.h"
#import <OgreKit/OgreKit.h>
#import <JSON/JSON.h>
#import "ServiceXMPP.h"
#import "ServiceXMPPClient.h"
#import "XMPPStream.h"
#import "NSXMLElementAdditions.h"
#import "i18n.h"


@implementation ServiceJaiku

static NSString* REQUEST_TOKEN_PATH = @"http://www.jaiku.com/api/request_token";
static NSString* AUTHORIZE_PATH = @"http://www.jaiku.com/api/authorize";
static NSString* ACCESS_TOKEN_PATH = @"http://www.jaiku.com/api/access_token";

- (id)init
{
	self = (ServiceJaiku*)[super init];
	if (self)
	{
		_title = nil;
		_first = YES;
		_request_token = nil;
		_request_token_secret = nil;
		_access_token = nil;
		_access_token_secret = nil;
	}
	return self;
}


- (void)dealloc
{
	[_title release];
	[_userProfile 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];
	}
	[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:Jaiku])
		{
			_notify = GrowlAfficheurJaikuNotify;
			_notifyComment = GrowlAfficheurJaikuCommentNotify;
			_notifyReply = GrowlAfficheurJaikuReplyNotify;
			_notifyError = GrowlAfficheurJaikuErrorNotify;
		}
	}
	return self;
}

- (void)setupXMPP:(ServiceXMPP *)xmpp
{
	[xmpp addService:Jaiku withObject:self];
	if ([_preferences accountJaikuUseXMPP])
	{
		[xmpp addXMPP:Jaiku
			  withJid:[_preferences accountJaikuJidXMPP]
				 pass:[_preferences accountJaikuPassXMPP]
			   server:[_preferences accountJaikuServerXMPP]
				 port:[_preferences accountJaikuPortXMPP]
			  offline:[_preferences accountJaikuOfflineXMPP]];
	}
	else
	{
		[xmpp removeXMPP:Jaiku];
	}
}

- (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
{
	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_token])
		{
			[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];
}

- (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 *)now
{
	BOOL check = NO;
	@synchronized(self)
	{
		NSTimeInterval interval = [_preferences accountJaikuIntervalTimeline];
		if (_dateTimeline && (interval > 0))
		{
			if (![_dateTimeline isKindOfClass:[NSDate class]])
			{
				LOG(@"[%@(%@) checkRetrieve] %@", [self className], _service, [_dateTimeline className]);
			}
			else
			{
				NSDate *aDate = [_dateTimeline addTimeInterval:interval];
				if ([now compare:aDate] != NSOrderedAscending)
				{
					check = YES;
				}
			}
		}
	}
	return check;
}

- (NSDictionary *)parse:(NSDictionary *)obj
			   withUser:(NSString *)user_id
				  first:(BOOL)first
{
	//LOG(@"[%@ parse]", [self className]);
	if (![obj isKindOfClass:[NSDictionary class]])
	{
		return nil;
	}
	//LOG(@"[%@ parse]\n%@", [self className], obj);
	NSDictionary *item = nil;
	@try
	{
		id item_id = [obj valueForKey:@"id"];
		id comment_id = [obj valueForKey:@"comment_id"];
		NSString *notify = _notify;
		NSDictionary *user = [obj valueForKey:@"user"];
		NSString *nick = [user valueForKey:@"nick"];
		NSString *first_name = [user valueForKey:@"first_name"];
		if ([first_name lengthOfRegularExpression:@"\\s+"] >= [first_name length])
		{
			first_name = @"";
		}
		first_name = [self convertText:first_name];
		NSString *last_name = [user valueForKey:@"last_name"];
		if ([last_name lengthOfRegularExpression:@"\\s+"] >= [last_name length])
		{
			last_name = @"";
		}
		last_name = [self convertText:last_name];
		NSString *name = @"";
		if ((![first_name isEqualToString:@""]) && (![last_name isEqualToString:@""]))
		{
			if (![[NSString stringWithFormat:@"%@%@", first_name, last_name] isEqualToString:nick])
			{
				name = [NSString stringWithFormat:@" / %@ %@", first_name, last_name];
			}
		}
		else if ((![first_name isEqualToString:@""]) && (![first_name isEqualToString:nick]))
		{
			name = [NSString stringWithFormat:@" / %@", first_name];
		}
		else if ((![last_name isEqualToString:@""]) && (![last_name isEqualToString:nick]))
		{
			name = [NSString stringWithFormat:@" / %@", last_name];
		}
		NSString *user_profile = [user valueForKey:@"avatar"];
		user_profile = [user_profile replaceWithExpression:@"%40" replace:@"@"];
		NSString *date = [obj valueForKey:@"created_at"];
		//LOG(@"[%@ parse] date:%@", [self className], date);
		date = [date replaceWithExpression:@"GMT" replace:@"+0000"];
		date = [date replaceWithExpression:@"T" replace:@" "];
		date = [date replaceWithExpression:@"Z$" replace:@" +0000"];
		date = [date replaceWithExpression:@"^(....)-(..)-(..) (..)-(..)-(..)"
								   replace:@"\\1-\\2-\\3 \\4:\\5:\\6"];
		//LOG(@"[%@ parse] date:%@", [self className], date);
		int hasKeyword = -1;
		NSString *category = KindTimeline;
		NSString *channel = @"";
		NSString *url = [obj valueForKey:@"url"];
		[_userProfile setObject:user_profile forKey:nick];
		if ([url lengthOfRegularExpression:@"jaiku\\.com"] > 0)
		{
			if ([url lengthOfRegularExpression:@"jaiku\\.com/channel/"] > 0)
			{
				channel = [url replaceWithExpression:@"^.*jaiku\\.com/channel/(\\S.*?)/.*$" replace:@" #\\1"];
			}
		}
		if (![channel isEqualToString:@""])
		{
			notify = GrowlAfficheurJaikuChannelNotify;
		}
		if (item_id)
		{
			NSString *icon = [obj valueForKey:@"icon"];
			//LOG(@"[%@ parse] icon:%@ %@", [self className], icon, [icon className]);
			if ([icon isKindOfClass:[NSString class]])
			{
				if ([icon lengthOfRegularExpression:@"^http"] <= 0)
				{
					if (![NSImage imageNamed:icon])
					{
						icon = [NSString stringWithFormat:
								@"http://%@.jaiku.com/themes/classic/icons/%@.gif",
								user_id, icon];
					}
				}
				LOG(@"[%@ parse] icon: %@", [self className], icon);
			//	user_profile = [NSString stringWithFormat:@"%@ %@",
			//					user_profile,
			//					icon];
			}
			NSString *text = [obj valueForKey:@"title"];
			NSString *location = [obj valueForKey:@"location"];
			NSString *content = [obj valueForKey:@"content"];
			if ([url lengthOfRegularExpression:@"jaiku\\.com"] <= 0)
			{
				if (![content isEqualToString:@""])
				{
					text = [NSString stringWithFormat:@"%@ %@", text, content];
				}
				text = [NSString stringWithFormat:@"%@ %@", text, url];
			}
			if ([url lengthOfRegularExpression:@"^.*jaiku.com/channel/.*?/presence/"] > 0)
			{
				item_id = [NSString stringWithFormat:@"%@", url];
			}
			else
			{
				item_id = [NSString stringWithFormat:
						   @"http://%@.jaiku.com/presence/%@", nick, item_id];
			}
			text = [text replaceWithExpression:@"\\r\\n" replace:@"\n"];
			text = [text replaceWithExpression:@"\\r" replace:@"\n"];
			if (![nick isEqualToString:user_id])
			{
				hasKeyword = [self hasKeywords:
							  [NSArray arrayWithObjects:
							   text, nick, name, channel, nil]];
				if (hasKeyword == 0)
				{
					notify = GrowlAfficheurKeywordsNotify;
					first = NO;
				}
			}
			if (![nick isEqualToString:user_id] &&
				([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@$", user_id]] > 0) ||
				([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@[^!-~]", user_id]] > 0))
			{
				notify = _notifyReply;
				category = KindReply;
				first = NO;
			}
			if (location && ![location isEqualToString:@""])
			{
				text = [NSString stringWithFormat:@"%@\nin %@", text, location];
			}
			item = [self createItem:item_id
						   withUser:nick
							   name:name
							channel:channel
								rid:nil
							   text:text
							comment:nil
							 source:nil
						 created_at:[NSDate dateWithString:date]
					   user_profile:user_profile
							 notify:notify
							   kind:KindTimeline
						   category:category
						inReplyUser:nil
							keyword:hasKeyword
							  first:first];
			if (item)
			{
				NSMutableDictionary *mutable_item = [NSMutableDictionary dictionaryWithDictionary:item];
				if ([icon isKindOfClass:[NSString class]])
				{
					[mutable_item setValue:[NSString stringWithFormat:@"%@%@", ProfileIcon, icon]
									forKey:KeyIcon];
				}
				item = [NSDictionary dictionaryWithDictionary:mutable_item];
			}
		}
		else if (comment_id)
		{
			NSString *text = [obj valueForKey:@"content"];
			NSString *parent_id = [url replaceWithExpression:@"^(.*)#.*$" replace:@"\\1"];
			notify = _notifyComment;
			item_id = [NSString stringWithFormat:@"%@-%@", parent_id, comment_id];
			NSString *inReplyUser = [url replaceWithExpression:@"^http:/.*?jaiku\\.com/channel/(.*?)/.*$"
													   replace:@"#\\1"];
			inReplyUser = [inReplyUser replaceWithExpression:@"^http://(.*?)\\.jaiku\\.com.*$"
													 replace:@"\\1"];
			NSString *comment = [NSString stringWithFormat:@"(on %@: %@)",
								 inReplyUser,
								 [obj valueForKey:@"entry_title"]];
			text = [text replaceWithExpression:@"\\r\\n" replace:@"\n"];
			text = [text replaceWithExpression:@"\\r" replace:@"\n"];
			if (![nick isEqualToString:user_id])
			{
				hasKeyword = [self hasKeywords:
							  [NSArray arrayWithObjects:
							   text, nick, name, channel, nil]];
				if (hasKeyword == 0)
				{
					notify = GrowlAfficheurKeywordsNotify;
					first = NO;
				}
			}
			if (![user_id isEqualToString:nick] && [inReplyUser isEqualToString:user_id])
			{
				notify = _notifyReply;
				category = KindReply;
				first = NO;
			}
			else if (![nick isEqualToString:user_id] &&
					 ([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@$", user_id]] > 0) ||
					 ([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@[^!-~]", user_id]] > 0))
			{
				notify = _notifyReply;
				category = KindReply;
				first = NO;
			}
			else if (![user_id isEqualToString:nick] && [self begins:parent_id withService:Jaiku user:user_id])
			{
				notify = _notifyReply;
				category = KindReply;
				first = NO;
			}
			comment = [self convertText:comment];
			item = [self createItem:item_id
						   withUser:nick
							   name:name
							channel:channel
								rid:nil
							   text:text
							comment:comment
							 source:nil
						 created_at:[NSDate dateWithString:date]
					   user_profile:user_profile
							 notify:notify
							   kind:KindTimeline
						   category:category
						inReplyUser:inReplyUser
							keyword:hasKeyword
							  first:first];
		}
		//LOG(@"[%@(%@) parse]\n%@", [self className], _service, item);
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) parse] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return item;
}

- (NSString *)determinePost:(id)object
					withAPI:(JaikuAPI *)api
{
	id description = @"Post was failure.";
	@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 (![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
		{
			description = nil;
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) determinPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return description;
}

- (NSString *)determinePost:(id)object
					withAPI:(JaikuAPI *)api
					 status:(NSString *)status
{
	LOG(@"[%@(%@) determinPost] %@", [self className], _service, object);
	id description = @"Post was failure.";
	@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 (![object isKindOfClass:[NSDictionary class]])
		{
			NSString *strring = [NSString stringWithFormat:@"%@", object];
			NSString *title = [api titleOfHTML:strring];
			if (title)
			{
				strring = title;
			}
			description = [NSString stringWithFormat:@"%@\n(%@)", description, strring];
			id info = [api userInfo:nil];
			if ([info isKindOfClass:[NSDictionary class]])
			{
				id stream = [info valueForKey:@"stream"];
				id item = nil;
				id comment_id = nil;
				id title = nil;
				if (stream)
				{
					if ([stream isKindOfClass:[NSArray class]])
					{
						item = [stream objectAtIndex:0];
					}
					else
					{
						LOG(@"[%@(%@) determinPost] !arrat", [self className], _service);
					}
				}
				else
				{
					LOG(@"[%@(%@) determinPost] !stream", [self className], _service);
				}
				if (item)
				{
					LOG(@"[%@(%@) determinPost]\n%@", [self className], _service, item);
					comment_id = [item valueForKey:@"comment_id"];
				}
				if (!comment_id)
				{
					title = [item valueForKey:@"title"];
				}
				else
				{
					LOG(@"[%@(%@) determinPost] !comment", [self className], _service);
				}
				if (title)
				{
					if ([title isEqualToString:status])
					{
						description = nil;
						LOG(@"[%@(%@) determinPost] ==", [self className], _service);
					}
					else
					{
						LOG(@"[%@(%@) determinPost] !=", [self className], _service);
					}
				}
			}
			else
			{
				LOG(@"[%@(%@) determinPost] !dict\n%@", [self className], _service, info);
			}
		}
		else
		{
			NSString *result = [object valueForKey:@"status"];
			NSDictionary *rv = nil;
			NSDictionary *extra = nil;
			NSString *title = nil;
			if (result && [result isEqualTo:@"ok"])
			{
				rv = [object valueForKey:@"rv"];
			}
			else
			{
				NSString *message = [object valueForKey:@"message"];
				if (message)
				{
					message = [message replaceWithExpression:@"^(.*):.*$"
													 replace:@"\\1"];
					description = [NSString stringWithFormat:
								   @"Comment was failure.\n(%@)", message];
				}
			}
			if (rv)
			{
				extra = [rv valueForKey:@"extra"];
			}
			if (extra)
			{
				title = [extra valueForKey:@"title"];
			}
			if (title)
			{
				if ([status lengthOfRegularExpression:@"^#"])
				{
					status = [status replaceWithExpression:@"^#.*? (.*)$"
												   replace:@"\\1"];
				}
				NSString *status2 = [self convertText:status];
				NSString *title2 = [self convertText:title];
				if ([title2 isEqualTo:status2])
				{
					description = nil;
				}
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) determinPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return description;
}

- (JaikuAPI *)createAPI:(NSDictionary *)dic
{
	JaikuAPI *api = [[[JaikuAPI 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;
}

- (NSString *)fairingMessage:(NSString *)message
{
	while ([message lengthOfRegularExpression:@"\\s$"])
	{
		message = [message replaceWithExpression:@"\\s$"
									 replace:@""
									 options:OgreMultilineOption];
	}
	message = [message replaceWithExpression:@"\\r\\n" replace:@"\n"];
	message = [message replaceWithExpression:@"\\r" replace:@"\n"];
	message = [message replaceWithExpression:@"\\n" replace:@"\r\n"];
	if ([_preferences generalConvertEmoji])
	{
		message = [self stringFromEmoji:message];
	}
	return message;
}

- (void)performPost:(NSArray *)array
{
	@try
	{
		[_controller syncPost];
		JaikuAPI* api = [self createAPI:[array objectAtIndex:1]];
		if (api)
		{
			NSString *location = nil;
			if ([array count] > 3)
			{
				location = [array objectAtIndex:3];
			}
			NSString *status = [array objectAtIndex:0];
			status = [self fairingMessage:status];
			[self registComplete:status];
			NSString *icon = nil;
			NSNumber *iconNumber = [array objectAtIndex:2];
			int iconCode = [iconNumber intValue];
			if (iconCode > 0)
			{
				icon = [_JaikuIcon valueForKey:[iconNumber stringValue]];
			}
			id result = [api update:status
						   withIcon:icon
						   location:location];
			[self determinePost:[self determinePost:result withAPI:api status:status]
					  withTitle:_title
					   function:@"Post"];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[array release];
	}
}

- (void)post:(NSString *)status
	withIcon:(int)iconCode
{
	NSString *user = [_preferences accountJaikuUser];
	NSString *key = [_preferences accountJaikuOAuthKey];
	NSString *secret = [_preferences accountJaikuOAuthSecret];
	NSString *location = [_preferences accountJaikuLocation];
	NSNumber *icon = [NSNumber numberWithInt:iconCode];
	NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
						 user, JaikuUser,
						 key, JaikuKey,
						 secret, JaikuSecret,
						 nil];
	[self performPost:[[NSArray alloc] initWithObjects:status, dic, icon, location, nil]];
}

- (void)performComment:(NSArray *)array
{
	@try
	{
		[_controller syncPost];
		NSString *url = [array objectAtIndex:1];
		if ([url lengthOfRegularExpression:@"-"] > 0)
		{
			url = [url replaceWithExpression:@"^(.*)\\-(.*)$" replace:@"\\1"];
		}
		LOG(@"[%@(%@) performComment]\n%@", [self className], _service, url);
		JaikuAPI* api = [self createAPI:[array objectAtIndex:2]];
		NSString *description = @"Comment was failure.";
		if (api)
		{
			NSString *status = [array objectAtIndex:0];
			status = [self fairingMessage:status];
			[self registComplete:status];
			id result = [api comment:status withURL:url];
			int statusCode = [api statusCode];
			LOG(@"[%@(%@) performComment]\n%d\n%@", [self className], _service, statusCode, result);
			if ([result isKindOfClass:[NSError class]])
			{
				description = @"Comment was failure.";
				NSString *desc = [api errorLocalizedDescription:result];
				if (desc)
				{
					description = [NSString stringWithFormat:@"%@\n(%@)", description, desc];
				}
			}
			else if (statusCode > 200)
			{
				description = [NSString stringWithFormat:@"Comment was failure.\n(%d : %@)",
							   statusCode, [NSHTTPURLResponse localizedStringForStatusCode:statusCode]];
			}
			else if ([result isKindOfClass:[NSDictionary class]])
			{
				//LOG(@"[%@(%@) performComment]\n%@", [self className], _service, result);
				NSString *ret = [result valueForKey:@"status"];
				NSDictionary *rv = nil;
				NSDictionary *comment = nil;
				NSDictionary *extra = nil;
				NSString *content = nil;
				if (ret && [ret isEqualTo:@"ok"])
				{
					rv = [result valueForKey:@"rv"];
				}
				else
				{
					NSString *message = [result valueForKey:@"message"];
					if (message)
					{
						description = [NSString stringWithFormat:
									   @"Comment was failure.\n(%@)", message];
					}
				}
				if (rv)
				{
					comment = [rv valueForKey:@"comment"];
				}
				else
				{
					LOG(@"[%@(%@) performComment] no comment", [self className], _service);
				}
				if (comment)
				{
					extra = [comment valueForKey:@"extra"];
				}
				else
				{
					LOG(@"[%@(%@) performComment] no extra", [self className], _service);
				}
				if (extra)
				{
					content = [extra valueForKey:@"content"];
				}
				else
				{
					LOG(@"[%@(%@) performComment] no content", [self className], _service);
				}
				if (content)
				{
					if ([content isEqualToString:status])
					{
						description = nil;
					}
					else
					{
						LOG(@"[%@(%@) performComment] !=", [self className], _service);
					}
				}
			}
			else
			{
				id info = [api userInfo:nil];
				if ([info isKindOfClass:[NSDictionary class]])
				{
					id stream = [info valueForKey:@"stream"];
					id item = nil;
					id comment_id = nil;
					id item_url = nil;
					id content = nil;
					if (stream)
					{
						if ([stream isKindOfClass:[NSArray class]])
						{
							item = [stream objectAtIndex:0];
						}
						else
						{
							LOG(@"[%@(%@) performComment] !arrat", [self className], _service);
						}
					}
					else
					{
						LOG(@"[%@(%@) performComment] !stream", [self className], _service);
					}
					if (item)
					{
						LOG(@"[%@(%@) performComment]\n%@", [self className], _service, item);
						comment_id = [item valueForKey:@"comment_id"];
					}
					if (comment_id)
					{
						item_url = [item valueForKey:@"url"];
					}
					else
					{
						LOG(@"[%@(%@) performComment] !comment", [self className], _service);
					}
					if (item_url)
					{
						item_url = [item_url replaceWithExpression:@"^(.*)#c\\-(.*)$" replace:@"\\1"];
						if ([item_url isEqualToString:url])
						{
							content = [item valueForKey:@"content"];
						}
						else
						{
							LOG(@"[%@(%@) performComment] !url\n%@\n%@", [self className], _service, item_url, url);
						}
					}
					if (content)
					{
						if ([content isEqualToString:status])
						{
							description = nil;
							LOG(@"[%@(%@) performComment] ==", [self className], _service);
						}
						else
						{
							LOG(@"[%@(%@) performComment] !=", [self className], _service);
						}
					}
				}
				else
				{
					LOG(@"[%@(%@) performComment] !dict\n%@", [self className], _service, info);
				}
			}
		}
		[self determinePost:description withTitle:_title function:@"Comment"];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performComment] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[array release];
	}
}

- (void)comment:(NSString *)comment
	   withItem:(NSString *)item_id
{
	//LOG(@"[%@(%@) comment]\n%@", [self className], _service, item_id);
	NSString *user = [_preferences accountJaikuUser];
	NSString *key = [_preferences accountJaikuOAuthKey];
	NSString *secret = [_preferences accountJaikuOAuthSecret];
	NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
						 user, JaikuUser,
						 key, JaikuKey,
						 secret, JaikuSecret,
						 nil];
	[self performComment:[[NSArray alloc] initWithObjects:comment, item_id, dic, nil]];
}

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

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

- (void)performRetrieve:(JaikuAPI *)api
			 withObject:(id)object
				   kind:(int)kind
				  first:(BOOL)first
{
	//LOG(@"[%@(%@) 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:@"stream"];
			if (stream && [stream isKindOfClass:[NSArray class]])
			{
				NSMutableArray *list = [[NSMutableArray alloc] init];
				NSMutableArray *notify = [[NSMutableArray alloc] init];
				@try
				{
					int i = [stream count];
					while (i-- > 0)
					{
						id obj = [stream objectAtIndex:i];
						id item = [self parse:obj withUser:[api user] 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];
									}
								}
							}
							@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)performRetrieveContactsFeed:(NSDictionary *)dic
{
	//LOG(@"[%@(%@) performRetrieveContactsFeed]", [self className], _service);
	[self setupRetrieveDateTimeline:nil];
	@try
	{
		
		JaikuAPI *api = [self createAPI:dic];
		if (api)
		{
			[api setTimeoutInterval:30];
			id result;
			int retry = 3;
			while (retry--)
			{
				result = [api contactsFeed];
				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)retrieveContactsFeed
{
	//LOG(@"[%@(%@) retrieveContactsFeed]", [self className], _service);
	BOOL retrieve = NO;
	@synchronized(self)
	{
		if (_dateTimeline)
		{
			retrieve = YES;
			[self setupRetrieveDateTimeline:nil];
		}
	}
	if (retrieve)
	{
		@try
		{
			NSString *user = [_preferences accountJaikuUser];
			NSString *key = [_preferences accountJaikuOAuthKey];
			NSString *secret = [_preferences accountJaikuOAuthSecret];
			NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
								 user, JaikuUser,
								 key, JaikuKey,
								 secret, JaikuSecret,
								 nil];
			[self performRetrieveContactsFeed:dic];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) retrieveContactsFeed] 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);
		BOOL retrieve = NO;
		id obj = [_controller fetchTimelineALL:item_id withService:Jaiku];
		if (obj)
		{
			NSString *replyTo = @"";
			if ([item_id lengthOfRegularExpression:@"^http"])
			{
				item_id = [[item_id componentsSeparatedByString:@"-"] objectAtIndex:0];
				replyTo = [NSString stringWithFormat:@"Comment to: %@%@",
						   [obj valueForKey:KeyText], [obj valueForKey:KeyComment]];
			}
			else
			{
				if ([_preferences generalRetrieveWhenReply])
				{
					[self setDelayedReply:item_id];
					retrieve = YES;
				}
				item_id = @"";
			}
			LOG(@"[%@(%@) 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:Jaiku
						inReplyTo:item_id
							 text:text];
		}
		if (retrieve)
		{
			[self retrieveContactsFeed];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) doReply] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

- (NSString *)permalinkURL:(NSString *)item_id
{
	NSString *url = nil;
	@try
	{
		//LOG(@"[%@(%@) permalinkURL]\n%@", [self className], _service, item_id);
		url = [NSString stringWithString:item_id];
		if ([url lengthOfRegularExpression:@"-[0-9a-zA-Z]+$"])
		{
			url = [url replaceWithExpression:@"^(.*)(-[0-9a-zA-Z]+)$" replace:@"\\1#c\\2"];
		}
		//LOG(@"[%@(%@) permalinkURL]\n%@", [self className], _service, url);
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) permalinkURL] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return url;
}

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

- (void)performReceiveMessage:(NSXMLElement *)message
{
	@try
	{
		//LOG(@"[%@(%@) performReceiveMessage]", [self className], _service);
		NSDate *created_at = [NSDate date];
		BOOL valid = YES;
		NSString *stamp = nil;
		NSXMLElement *x = [message elementForName:@"x" xmlns:@"jabber:x:delay"];
		if (x)
		{
			//LOG(@"[%@(%@) performReceiveMessage]\nx = %@", [self className], _service, x);
			stamp = [[x attributeForName:@"stamp"] stringValue];
			if (stamp)
			{
				//LOG(@"[%@(%@) performReceiveMessage]\nstamp = %@", [self className], _service, stamp);
				if (![_preferences accountIdenticaOfflineXMPP])
				{
					valid = NO;
				}
				else if ([stamp lengthOfRegularExpression:@"\\d\\d\\d\\d\\d\\d\\d\\dT\\d\\d\\:\\d\\d\\:\\d\\d"] > 0)
				{
					//20090718T21:21:06
					stamp = [stamp replaceWithExpression:@"(....)(..)(..)T(..\\:..\\:..)"
												 replace:@"\\1-\\2-\\3 \\4 +0000"];
					created_at = [NSDate dateWithString:stamp];
					LOG(@"[%@(%@) performReceiveMessage]\ncreated_at = %@", [self className], _service, created_at);
				}
				else
				{
					valid = NO;
				}
			}
		}
		if (valid)
		{
			BOOL doRetrieve = NO;
			[self lockService];
			@try
			{
				LOG(@"[%@(%@) performReceiveMessage]\n%@", [self className], _service, message);
				NSString *body = [[message elementForName:@"body"] stringValue];
				LOG(@"[%@(%@) performReceiveMessage]\nbody = %@", [self className], _service, body);
				NSString *user = nil;
				NSString *reply = nil;
				NSString *channel = @"";
				NSString *text = nil;
				NSString *item_id = nil;
				NSString *comment = nil;
				NSString *parent_id = nil;
				NSString *date = [created_at descriptionWithCalendarFormat:@"%Y%m%d%H%M%S%F" timeZone:nil locale:nil];
				if ([body rangeOfRegularExpressionString:@"^\\S+#\\S+: .*\nLink"].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] #", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@"\\1" options:OgreMultilineOption];
					channel = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@" #\\2" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\nLink: (.*)$" replace:@"\\1" options:OgreMultilineOption];
					item_id = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\nLink: (.*)$" replace:@"\\2" options:OgreMultilineOption];
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+ \\(to \\S+ on #\\S+\\): "].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] #comment", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+) \\(to (\\S+) on #(\\S+)\\):.*$" replace:@"\\1" options:OgreMultilineOption];
					reply = [body replaceWithExpression:@"^(\\S+) \\(to (\\S+) on #(\\S+)\\):.*$" replace:@"\\2" options:OgreMultilineOption];
					channel = [body replaceWithExpression:@"^(\\S+) \\(to (\\S+) on #(\\S+)\\):.*$" replace:@" #\\3" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+ \\(to \\S+ on #\\S+\\): (.*)\nLink: (.*)$" replace:@"\\1" options:OgreMultilineOption];
					item_id = [body replaceWithExpression:@"^\\S+ \\(to \\S+ on #\\S+\\): (.*)\nLink: (.*)$" replace:@"\\2" options:OgreMultilineOption];
					parent_id = [NSString stringWithString:item_id];
					item_id = [item_id stringByAppendingFormat:@"-%@", date];
					comment = [text replaceWithExpression:@"^(.*) \\(on (.*)\\)" replace:@"\\2" options:OgreMultilineOption];
					text = [text replaceWithExpression:@"^(.*) \\(on (.*)\\)" replace:@"\\1" options:OgreMultilineOption];
					comment = [NSString stringWithFormat:@"(on%@: %@)", channel, comment];
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+#\\S+: .*\\(.*\\)$" options:OgreMultilineOption].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] #comment", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@"\\1" options:OgreMultilineOption];
					channel = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@" #\\2" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\\(.*\\)$" replace:@"\\1" options:OgreMultilineOption];
					comment = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\\(on (.*)\\)$" replace:@"\\2" options:OgreMultilineOption];
					//item_id = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\nLink: (.*)$" replace:@"\\2" options:OgreMultilineOption];
					item_id = date;
					comment = [NSString stringWithFormat:@"(on%@: %@)", channel, comment];
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+#\\S+: "].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] #", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@"\\1" options:OgreMultilineOption];
					channel = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@" #\\2" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+#\\S+: (.*)$" replace:@"\\1" options:OgreMultilineOption];
					//item_id = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\nLink: (.*)$" replace:@"\\2" options:OgreMultilineOption];
					item_id = date;
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+ \\(to \\S+\\): "].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] comment", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+) \\(to (\\S+)\\):.*$" replace:@"\\1" options:OgreMultilineOption];
					reply = [body replaceWithExpression:@"^(\\S+) \\(to (\\S+)\\):.*$" replace:@"\\2" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+ \\(to \\S+\\): (.*)\nLink: (.*)" replace:@"\\1" options:OgreMultilineOption];
					item_id = [body replaceWithExpression:@"^\\S+ \\(to \\S+\\): (.*)\nLink: (.*)" replace:@"\\2" options:OgreMultilineOption];
					parent_id = [NSString stringWithString:item_id];
					item_id = [item_id stringByAppendingFormat:@"-%@", date];
					comment = [text replaceWithExpression:@"^(.*) \\(on (.*)\\)" replace:@"\\2" options:OgreMultilineOption];
					text = [text replaceWithExpression:@"^(.*) \\(on (.*)\\)" replace:@"\\1" options:OgreMultilineOption];
					comment = [NSString stringWithFormat:@"(on %@: %@)", reply, comment];
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+: .* \\(on .*\\)$" options:OgreMultilineOption].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] comment", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+): (.*) \\(on (.*)\\)$" replace:@"\\1" options:OgreMultilineOption];
					//reply = [body replaceWithExpression:@"^(\\S+): (.*) \\(on (.*)\\)$" replace:@"\\2" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^(\\S+): (.*) \\(on (.*)\\)$" replace:@"\\2" options:OgreMultilineOption];
					//item_id = [body replaceWithExpression:@"^(\\S+): (.*) \\(on (.*)\\)" replace:@"\\2" options:OgreMultilineOption];
					//parent_id = [NSString stringWithString:item_id];
					//item_id = [item_id stringByAppendingFormat:@"-%@", date];
					comment = [body replaceWithExpression:@"^(\\S+): (.*) \\(on (.*)\\)$" replace:@"\\3" options:OgreMultilineOption];
					comment = [NSString stringWithFormat:@"(on %@)", comment];
					item_id = date;
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+: .*\nLink"].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] text", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+):.*$" replace:@"\\1" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+: (.*)\nLink: (.*)" replace:@"\\1" options:OgreMultilineOption];
					item_id = [body replaceWithExpression:@"^\\S+: (.*)\nLink: (.*)" replace:@"\\2" options:OgreMultilineOption];
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+: "].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] text", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+): (.*)$" replace:@"\\1" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^(\\S+): (.*)$" replace:@"\\2" options:OgreMultilineOption];
					//item_id = [body replaceWithExpression:@"^\\S+: (.*)\nLink: (.*)" replace:@"\\2" options:OgreMultilineOption];
					item_id = date;
				}
				text = [text replaceWithExpression:@"\\r\\n" replace:@"\n"];
				text = [text replaceWithExpression:@"\\r" replace:@"\n"];
				if (comment)
				{
					comment = [comment replaceWithExpression:@"\\r" replace:@""];
				}
				NSString *name = nil;
				NSString *icon = nil;
				NSString *xmpp_id = nil;
				NSString *published = nil;
				NSXMLElement *entry = [message elementForName:@"entry"];
				if (entry)
				{
					NSXMLElement *source = [entry elementForName:@"source"];
					if (source)
					{
						NSXMLElement *author = [source elementForName:@"author"];
						if (author)
						{
							name = [[author elementForName:@"name"] stringValue];
						}
						icon = [[source elementForName:@"icon"] stringValue];
					}
					xmpp_id = [[entry elementForName:@"id"] stringValue];
					published = [[entry elementForName:@"published"] stringValue];
					if (name)
					{
						user = name;
					}
					if (icon)
					{
						icon = [icon replaceWithExpression:@"^(.*)\\%40(.*)\\_None(.*)$"
												   replace:@"\\1@\\2_t\\3"];
					}
					if (xmpp_id)
					{
						if ([xmpp_id lengthOfRegularExpression:@".*\\#c\\-.*"])
						{
							item_id = [xmpp_id replaceWithExpression:@"^(.*)\\#c(.*)$"
															 replace:@"\\1\\2"];
						}
						else
						{
							item_id = xmpp_id;
						}
					}
					if (published)
					{
						published = [published replaceWithExpression:@"^(.*)T(.*)Z$"
															 replace:@"\\1 \\2 +0000"];
						created_at = [NSDate dateWithString:published];
					}
				}
				LOG(@"[%@(%@) performReceiveMessage]"\
					@"\nuser    = %@"\
					@"\nicon    = %@"\
					@"\nchannel = %@"\
					@"\ntext    = %@"\
					@"\nitem_id  = %@"\
					@"\nreply   = %@"\
					@"\ncomment = %@"\
					@"\ncreated_at = %@",
					[self className], _service, user, icon, channel, text, item_id, reply, comment, created_at);
				if (user && text && item_id)
				{
					NSString *category = KindTimeline;
					NSString *notify = _notify;
					NSString *user_id = [_preferences accountJaikuUser];
					if (![channel isEqualToString:@""])
					{
						notify = GrowlAfficheurJaikuChannelNotify;
					}
					int hasKeyword = -1;
					if (![user isEqualToString:user_id])
					{
						hasKeyword = [self hasKeywords:
									  [NSArray arrayWithObjects:
									   text, user, channel, nil]];
						if (hasKeyword == 0)
						{
							notify = GrowlAfficheurKeywordsNotify;
						}
					}
					if (![user_id isEqualToString:user] && reply && [reply isEqualToString:user_id])
					{
						notify = _notifyReply;
						category = KindReply;
					}
					if (![user_id isEqualToString:user] && parent_id && [self begins:parent_id withService:Jaiku user:user_id])
					{
						notify = _notifyReply;
						category = KindReply;
					}
					if (([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@$", user_id]] > 0) ||
						([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@[^!-~]", user_id]] > 0))
					{
						notify = _notifyReply;
						category = KindReply;
					}
					if (!icon && ![_userProfile valueForKey:user])
					{
						NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
											 [_preferences accountJaikuUser], JaikuUser,
											 [_preferences accountJaikuOAuthKey], JaikuKey,
											 [_preferences accountJaikuOAuthSecret], JaikuSecret,
											 nil];
						JaikuAPI *api = [self createAPI:dic];
						if (api)
						{
							[api setTimeoutInterval:10];
							id result = [api userInfo:user];
							if ([result isKindOfClass:[NSDictionary class]])
							{
								//LOG(@"[%@(%@) performReceiveMessage]\nuserInfo = %@", [self className], _service, result);
								NSString *avatar_user = [result valueForKey:@"user"];
								if (avatar_user)
								{
									NSString *avatar = [avatar_user valueForKey:@"avatar"];
									if (avatar)
									{
										avatar = [avatar replaceWithExpression:@"%40" replace:@"@"];
										[_userProfile setObject:avatar forKey:user];
									}
								}
							}
						}
						icon = [_userProfile valueForKey:user];
					}
					if (comment)
					{
						comment = [self convertText:comment];
					}
					NSDictionary *item = [self createItem:item_id
												 withUser:user
													 name:@""
												  channel:channel
													  rid:@""
													 text:text
												  comment:comment
												   source:IM
											   created_at:created_at
											 user_profile:icon
												   notify:notify
													 kind:KindTimeline
												 category:category
											  inReplyUser:reply
												  keyword:hasKeyword
													first:NO
													 xmpp:YES];
					if (item)
					{
						[item retain];
						if (![item valueForKey:KeyFrom])
						{
							LOG(@"[%@(%@) performReceiveMessage]\nitem = %@", [self className], _service, item);
						}
						NSMutableArray *list = [[NSMutableArray alloc] init];
						NSMutableArray *notify = [[NSMutableArray alloc] init];
						@try
						{
							if ([self addTimeline:item withFirst:NO])
							{
								//LOG(@"[%@(%@) performReceiveMessage]\n%@", [self className], _service, item);
								//LOG(@"[%@(%@) performReceiveMessage]\nnotify = %@", [self className], _service, [item valueForKey:KeyId]);
								if (![channel isEqualToString:@""])
								{
									[self registComplete:channel];
								}
								[notify insertObject:item atIndex:0];
								if (!stamp)
								{
									_xmppCount++;
								}
							}
							[list addObject:item];
							[self finishAddTimeline:list withNotify:notify];
							int retrieveCount = [_preferences accountIdenticaRetrieveXMPP];
							//LOG(@"[%@(%@) performReceiveMessage]\nretrieve = %d", [self className], _service, retrieve);
							if (retrieveCount > 0)
							{
								if (_xmppCount >= retrieveCount)
								{
									LOG(@"[%@(%@) performReceiveMessage#lock] retrieve", [self className], _service);
									_xmppCount = 0;
									doRetrieve = YES;
									//[self retrieveTimeline];
								}
							}
							else
							{
								_xmppCount = 0;
							}
						}
						@catch (NSException *exception)
						{
							EXPLOG(@"[%@(%@) performReceiveMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
						}
						@finally
						{
							[notify release];
							[list release];
							[item release];
						}
					}
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performReceiveMessage#lock] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				[self unlockService];
			}
			if (doRetrieve)
			{
				LOG(@"[%@(%@) performReceiveMessage#lock] doRetrieve", [self className], _service);
				[self retrieveContactsFeed];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performReceiveMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

@end
