//
//  ServiceIdentica.m
//  Afficheur
//
//  Created by kichi on 08/10/06.
//  Copyright 2008 Katsuhiko Ichinose. All rights reserved.
//

#import "ServiceIdentica.h"
#import "AfficheurController.h"
#import "AfficheurPreferences.h"
#import "AfficheurTimeline.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 ServiceIdentica

- (id)init
{
	self = (id)[super init];
	if (self)
	{
	}
	return self;
}

- (void)dealloc
{
	[super dealloc];
}

- (id)init:(NSString *)service
WithController:(AfficheurController *)controller
preferences:(AfficheurPreferences *)preferences
{
	self = [super init:service
		WithController:controller
		   preferences:preferences];
	if (self)
	{
		_notifyGroup = GrowlAfficheurIdenticaGroupNotify;
	}
	return self;
}

- (void)setupXMPP:(ServiceXMPP *)xmpp
{
	[xmpp addService:Identica withObject:self];
	if ([_preferences accountIdenticaUseXMPP])
	{
		[xmpp addXMPP:Identica
			  withJid:[_preferences accountIdenticaJidXMPP]
				 pass:[_preferences accountIdenticaPassXMPP]
			   server:[_preferences accountIdenticaServerXMPP]
				 port:[_preferences accountIdenticaPortXMPP]
			  offline:[_preferences accountIdenticaOfflineXMPP]];
	}
	else
	{
		[xmpp removeXMPP:Identica];
	}
}

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

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

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

- (NSString *)stringChannel:(NSString *)text
{
	if ([text rangeOfRegularExpressionString:@"\\!(?:[a-zA-Z][\\!-~]+|[a-zA-Z])"].length > 0)
	{
		return [text replaceWithExpression:@"^.*(\\!(?:[a-zA-Z][\\!-~]+|[a-zA-Z])).*$"
								   replace:@"\\1"
								   options:OgreMultilineOption];
	}
	return nil;
}

- (NSDictionary *)apiParam
{
	NSString *user = [_preferences accountIdenticaUser];
	NSString *pass = [_preferences accountIdenticaPass];
	return [NSDictionary dictionaryWithObjectsAndKeys:
			@"identi.ca", TwitterHost,
			@"http://identi.ca/api", TwitterURL,
			user, TwitterUser,
			pass, TwitterPass,
			Afficheur, TwitterSource,
			nil];
}


- (void)directMessage:(NSString *)message
{
	@synchronized(self)
	{
		@try
		{
			NSDictionary *dic = [self apiParam];
			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];
				}
				NSDictionary *dic = [self apiParam];
				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)isChannel:(NSDictionary *)object
{
	return [[object valueForKey:KeyNotify] isEqualToString:_notifyGroup];
}

- (NSDictionary *)reply:(NSString *)in_reply
			   withUser:(NSString *)user_id
				   kind:(int)kind
				  first:(BOOL)first
{
	NSDictionary *reply = nil;
	int loop = (kind >> 8) & 0xfffff;
	kind &= 0xff;
	TwitterAPI *api = [[TwitterAPI alloc] initWithDictionary:
					   [self apiParam]];
	if (api)
	{
		id notice = [api show:in_reply];
		LOG(@"[%@(%@) reply:%d] reply: %d / %@", [self className], _service, kind, loop, in_reply);
		if (notice && [notice isKindOfClass:[NSDictionary class]])
		{
			id reply_item = nil;
			if (loop < 50)
			{
				reply_item = [self parse:notice
								withUser:user_id
									kind:kind | (((loop + 1) << 8) & 0xfffff00)
								   first:first];
			}
			if (reply_item)
			{
				if ([self addTimeline:reply_item withFirst:first])
				{
					[_controller  queueingParsePhotoURL:reply_item];
				}
				reply = reply_item;
			}
			else
			{
				reply = [NSDictionary dictionaryWithObjectsAndKeys:
						 [[notice valueForKey:@"user"] valueForKey:@"screen_name"], KeyUser,
						 [notice valueForKey:@"text"], KeyText,
						 nil];
			}
		}
	}
	return reply;
}

- (NSDictionary *)parse:(NSDictionary *)obj
			   withUser:(NSString *)user_id
				   kind:(int)kind
				  first:(BOOL)first
{
	if (![obj isKindOfClass:[NSDictionary class]])
	{
		return nil;
	}
	int loop = (kind >> 8) & 0xfffff;
	kind &= 0xff;
	//LOG(@"[%@(%@) parse:%d]\n%@", [self className], _service, kind, obj);
	NSDictionary *item = nil;
	@try
	{
		BOOL first1 = first;
		NSDictionary *org = obj;
		NSDictionary *retweet = [obj valueForKey:@"retweeted_status"];
		NSString *retweetedBy = nil;
		NSString *retweetedId = nil;
		int hasKeyword = -1;
		if (retweet)
		{
			NSDictionary *byUser = [obj valueForKey:@"user"];
			retweetedBy = [byUser valueForKey:@"screen_name"];
			obj = retweet;
			LOG(@"[%@(%@) parse:%d]\n%@", [self className], _service, kind, org);
			LOG(@"[%@(%@) parse:%d]\n%@", [self className], _service, kind, retweetedBy);
		}
		NSString *item_id = [NSString stringWithFormat:@"%@", [obj valueForKey:@"id"]];
		NSDictionary *user = [obj valueForKey:@"user"];
		if (!user)
		{
			user = [obj valueForKey:@"sender"];
		}
		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;
		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 ((kind == kindTimeline) || (kind == kindChannel))
		{
			channel = [self stringChannel:text];
			if (channel)
			{
				notify = _notifyGroup;
				channel = [NSString stringWithFormat:@" %@", channel];
			}
			else
			{
				channel = @"";
			}
		}
		if (![screen_name isEqualToString:user_id])
		{
			hasKeyword = [self hasKeywords:
						  [NSArray arrayWithObjects:
						   text, screen_name, user_name, channel, nil]];
			if (hasKeyword == 0)
			{
				notify = GrowlAfficheurKeywordsNotify;
				first1 = 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))
				{
					notify = _notifyReply;
					category = KindReply;
					first1 = NO;
				}
			}
		}
		else
		{
			NSDictionary *reply = [self fetchALL:[NSString stringWithFormat:@"%@", in_reply]
								  withService:_service];
			if (!reply)
			{
				reply = [self reply:in_reply
						   withUser:user_id
							   kind:(loop << 8) | kind
							  first:first];
			}
			if (reply)
			{
				inReplyUser = [reply valueForKey:KeyUser];
				comment = [NSString stringWithFormat:@"(on %@: %@)",
						   [reply valueForKey:KeyUser], [reply valueForKey:KeyText]];
				if ((kind == kindTimeline) || (kind == kindChannel))
				{
					if (![screen_name isEqualToString:user_id] &&[[reply valueForKey:KeyUser] isEqualToString:user_id])
					{
						notify = _notifyReply;
						category = KindReply;
						first1 = 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))
				{
					notify = _notifyReply;
					category = KindReply;
					first1 = NO;
				}
			}
		}
	//	if (kind == kindTimeline)
	//	{
	//		NSDate *created_at = [NSDate dateWithNaturalLanguageString:date];
	//		int i = 0;
	//		while (i < [_since count])
	//		{
	//			NSDate *since = [_since objectAtIndex:i];
	//			if ([since compare:created_at] != NSOrderedDescending)
	//			{
	//				break;
	//			}
	//			i++;
	//		}
	//		[_since insertObject:created_at atIndex:i];
	//	}
		if (kind == kindDM)
		{
			item_id = [NSString stringWithFormat:@"DM%@", item_id];
			user_profile = [NSString stringWithFormat:@"%@ %@", user_profile, Mail];
		}
		LOG(@"[%@(%@) parse:%d]\n%@\n%@", [self className], _service, kind, text, [self convertText:text]);
		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:NO
						  first:first1];
		if (retweet)
		{
			LOG(@"[%@(%@) parse:%d]\n%@", [self className], _service, kind, retweetedBy);
			LOG(@"[%@(%@) parse:%d]\n%@", [self className], _service, kind, retweetedId);
			LOG(@"[%@(%@) parse:%d]\n%@", [self className], _service, kind, item);
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) parse:%d] EXCEPTION\n%@: %@", [self className], _service, kind, [exception name], [exception reason]);
	}
	return item;
}

- (void)retrieveTimeline
{
	//LOG(@"[%@(%@) retrieveTimeline]", [self className], _service);
	BOOL retrieve = NO;
	@synchronized(self)
	{
		if (_dateTimeline)
		{
			retrieve = YES;
			[self setupRetrieveDateTimeline:nil];
		}
	}
	if (retrieve)
	{
		@try
		{
			_xmppCount = 0;
			NSDictionary *dic = [[self apiParam] retain];
			[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
		{
			NSDictionary *dic = [[self apiParam] retain];
			[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
		{
			NSDictionary *dic = [[self apiParam] retain];
			[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 = [self fetchALL: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 obj = [self fetchALL:item_id withService:_service];
			if (obj && ![[obj valueForKey:KeyKind] isEqualToString:KindDM])
			{
				id retweetedId = [obj valueForKey:KeyRetweetedId];
				if (retweetedId && ![retweetedId isEqualToString:@""])
				{
					LOG2(@"[%@(%@) favorite] RetweetedId\n%@", [self className], _service, retweetedId);
					item_id = retweetedId;
				}
				NSDictionary *dic = [self apiParam];
				[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 fetchALL:item_id withService:_service];
		if (item)
		{
			if ([[item valueForKey:KeyKind] isEqualToString:KindDM])
			{
				NSString *user = [_preferences accountIdenticaUser];
				url = [NSString stringWithFormat:@"http://identi.ca/%@/inbox", user];
			}
			else
			{
				url = [NSString stringWithFormat:@"http://identi.ca/notice/%@", 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])
			{
				id retweetedId = [item valueForKey:KeyRetweetedId];
				if (retweetedId && ![retweetedId isEqualToString:@""])
				{
					LOG2(@"[%@(%@) doRetweetViaAPI] RetweetedId\n%@", [self className], _service, retweetedId);
					item_id = retweetedId;
				}
				NSDictionary *dic = [self apiParam];
				[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 = [self fetchALL: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]);
	}
}

- (void)performReceiveMessage:(NSXMLElement *)message
{
	@try
	{
		//LOG(@"[%@(%@) performReceiveMessage]", [self className], _service);
		//LOG(@"[%@(%@) performReceiveMessage]\n%@", [self className], _service, message);
		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;
				}
			}
		}
		if (valid)
		{
			BOOL doRetrieve = NO;
			[self lockService];
			@try
			{
				LOG(@"[%@(%@) performReceiveMessage] message\n%@", [self className], _service, message);
				NSString *body = [[message elementForName:@"body"] stringValue];
				NSString *screen_name = nil;
				NSString *name = nil;
				NSString *icon = nil;
				NSString *xmpp_id = nil;
				NSString *item_id = nil;
				NSString *published = nil;
				NSString *in_reply_to = nil;
				NSString *source_info = IM;
				NSString *retweetedBy = nil;
				NSString *retweetedId = nil;
				NSXMLElement *entry = [message elementForName:@"entry"];
				if (entry)
				{
					NSXMLElement *source = [entry elementForName:@"source"];
					if (source)
					{
						NSXMLElement *author = [source elementForName:@"author"];
						if (author)
						{
							screen_name = [[author elementForName:@"name"] stringValue];
						}
						NSXMLElement *title = [source elementForName:@"title"];
						if (title)
						{
							name = [title stringValue];
						}
						icon = [[source elementForName:@"icon"] stringValue];
					}
					NSXMLElement *author = [entry elementForName:@"author"];
					if (author)
					{
						LOG(@"[%@(%@) performReceiveMessage] author\n%@", [self className], _service, author);
						screen_name = [[author elementForName:@"name"] stringValue];
						name = [[author elementForName:@"displayName" xmlns:@"http://portablecontacts.net/spec/1.0"] stringValue];
						NSArray *elements = [author elementsForName:@"link"];
						NSEnumerator *enumerator = [elements objectEnumerator];
						id element;
						while ((element = [enumerator nextObject]))
						{
							NSXMLNode *node = [element attributeForName:@"ns1:width"];
							if ([[node stringValue] isEqualToString:@"48"])
							{
								icon = [[element attributeForName:@"href"] stringValue];
							}
						}
						LOG(@"[%@(%@) performReceiveMessage] author\nscreen_name='%@'\nname='%@'\nicon='%@'", [self className], _service, screen_name, name, icon);
					}
					xmpp_id = [[entry elementForName:@"id"] stringValue];
					published = [[entry elementForName:@"published"] stringValue];
					NSXMLElement *thr_in_reply_to = [entry elementForName:@"thr:in-reply-to"];
					if (!thr_in_reply_to)
					{
						thr_in_reply_to = [entry elementForName:@"in-reply-to"  xmlns:@"http://purl.org/syndication/thread/1.0"];
						//LOG(@"[%@(%@) performReceiveMessage]\nentry = %@", [self className], _service, entry);
						//LOG(@"[%@(%@) performReceiveMessage]\nthr_reply = %@", [self className], _service, thr_in_reply_to);
					}
					if (thr_in_reply_to)
					{
						in_reply_to = [[thr_in_reply_to attributeForName:@"ref"] stringValue];
					}
					//LOG(@"[%@(%@) performReceiveMessage]\nreply = %@", [self className], _service, in_reply_to);
				}
				NSXMLElement *notice_info = [entry elementForName:@"notice_info" xmlns:@"http://status.net/schema/api/1/"];
				if (notice_info)
				{
					LOG(@"[%@(%@) performReceiveMessage] notice_info\n%@", [self className], _service, notice_info);
					NSString *attribute = [[notice_info attributeForName:@"source"] stringValue];
					if (attribute)
					{
						LOG(@"[%@(%@) performReceiveMessage]\nsource = %@", [self className], _service, attribute);
						source_info = attribute;
					}
					attribute = [[notice_info attributeForName:@"local_id"] stringValue];
					if (attribute)
					{
						item_id = attribute;
					}
#if 0
					attribute = [[notice_info attributeForName:@"repeat_of"] stringValue];
					if (attribute)
					{
						LOG(@"[%@(%@) performReceiveMessage]\nrepeat_of = %@", [self className], _service, attribute);
						retweetedId = attribute;
						retweetedBy = screen_name;
						TwitterAPI *api = [self createAPI:[self apiParam]];
						id show = [api show:retweetedId];
						LOG(@"[%@(%@) performReceiveMessage]show\n%@", [self className], _service, show);
						if ([show isKindOfClass:[NSDictionary class]])
						{
							LOG(@"[%@(%@) performReceiveMessage]show\n%@", [self className], _service, show);
							icon = [[show valueForKey:@"user"] valueForKey:@"profile_image_url"];
						}
						NSXMLElement *html_body = [[message elementForName:@"html"] elementForName:@"body" xmlns:@"http://www.w3.org/1999/xhtml"];
						if (html_body)
						{
							LOG(@"[%@(%@) performReceiveMessage]html/body\n%@", [self className], _service, html_body);
							LOG(@"[%@(%@) performReceiveMessage]html/body\n%@", [self className], _service, [html_body stringValue]);
							NSXMLElement *html_body_span = [html_body elementForName:@"span"];
							if (html_body_span)
							{
								LOG(@"[%@(%@) performReceiveMessage]html/body/span\n%@", [self className], _service, html_body_span);
								NSString *span_class = [[html_body_span attributeForName:@"class"] stringValue];
								if (span_class && [span_class isEqualToString:@"vcard"])
								{
									NSXMLElement *span_a = [html_body_span elementForName:@"a"];
									if (span_a)
									{
										NSXMLNode *a_title = [span_a attributeForName:@"title"];
										if (a_title)
										{
											LOG(@"[%@(%@) performReceiveMessage]title\n%@", [self className], _service, a_title);
											name = [a_title stringValue];
										}
										NSXMLElement *a_span = [span_a elementForName:@"span"];
										if (a_span)
										{
											LOG(@"[%@(%@) performReceiveMessage]span\n%@", [self className], _service, a_span);
											screen_name = [a_span stringValue];
										}
									}
								}
							}
						}
						body = [body replaceWithExpression:[NSString stringWithFormat:@"^.*?: RT @%@ (.*)$", [screen_name regularizeString]]
												   replace:[NSString stringWithFormat:@"%@: \\1", screen_name]
												   options:OgreMultilineOption];
						LOG(@"[%@(%@) performReceiveMessage]span\n%@", [self className], _service, body);
					}
#endif
				}
				if (body && screen_name && icon && xmpp_id && published)
				{
					//LOG(@"[%@(%@) performReceiveMessage]\nname = %@", [self className], _service, name);
					//LOG(@"[%@(%@) performReceiveMessage]\nicon = %@", [self className], _service, icon);
					//LOG(@"[%@(%@) performReceiveMessage]\nid   = %@", [self className], _service, xmpp_id);
					//LOG(@"[%@(%@) performReceiveMessage]\npub  = %@", [self className], _service, published);
					if ([icon isEqualToString:@"http://identi.ca/theme/stoica/default-avatar-profile.png"])
					{
						icon = @"http://theme.identi.ca/identica/default-avatar-stream.png";
					}
					if (!item_id)
					{
						item_id = [xmpp_id replaceWithExpression:@".*/([0-9]+)$" replace:@"\\1"];
					}
					NSString *exp = [NSString stringWithFormat:@"^%@: (.*)$", [screen_name regularizeString]];
					NSString *text = [body replaceWithExpression:exp
														 replace:@"\\1"
														 options:OgreMultilineOption];
					text = [text replaceWithExpression:[NSString stringWithFormat:@"^(.*)\\s+\\[%@\\]$", item_id]
											   replace:@"\\1"
											   options:OgreMultilineOption];
					NSString *date = [published replaceWithExpression:@"^(.*?)T(.*)\\+(.*):(.*)$"
															  replace:@"\\1 \\2 +\\3\\4"];
					NSDate *created_at = [NSDate dateWithNaturalLanguageString:date];
					NSString *channel = [self stringChannel:text];
					NSString *category = KindTimeline;
					NSString *notify = _notify;
					//LOG(@"[%@(%@) performReceiveMessage]\nexp  = %@", [self className], _service, exp);
					//LOG(@"[%@(%@) performReceiveMessage]\nid   = %@", [self className], _service, item_id);
					//LOG(@"[%@(%@) performReceiveMessage]\ntext = %@", [self className], _service, text);
					//LOG(@"[%@(%@) performReceiveMessage]\ndate   = %@", [self className], _service, date);
					//LOG(@"[%@(%@) performReceiveMessage]\nat   = %@", [self className], _service, created_at);
					NSString *user_id = [_preferences accountIdenticaUser];
					int hasKeyword = -1;
					if (![screen_name isEqualToString:user_id])
					{
						hasKeyword = [self hasKeywords:
									  [NSArray arrayWithObjects:
									   text, screen_name, channel, name, nil]];
						if (hasKeyword == 0)
						{
							notify = GrowlAfficheurKeywordsNotify;
						}
					}
					if (channel)
					{
						notify = GrowlAfficheurIdenticaGroupNotify;
						channel = [NSString stringWithFormat:@" %@", channel];
					}
					else
					{
						channel = @"";
					}
					if (([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@$", user_id]] > 0) ||
						([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@[^!-~]", user_id]] > 0))
					{
						notify = _notifyReply;
						category = KindReply;
					}
					text = [text replaceWithExpression:@"\\r" replace:@""];
					NSString *comment = @"";
					NSString *inReplyUser = @"";
					if (in_reply_to)
					{
						in_reply_to = [in_reply_to replaceWithExpression:@".*/([0-9]+)$" replace:@"\\1"];
						NSDictionary *reply = [self fetchALL:[NSString stringWithFormat:@"%@", in_reply_to]
												 withService:_service];
						if (!reply)
						{
							reply = [self reply:in_reply_to
									   withUser:user_id
										   kind:kindTimeline
										  first:NO];
						}
						if (reply)
						{
							inReplyUser = [reply valueForKey:KeyUser];
							comment = [NSString stringWithFormat:@"(on %@: %@)",
									   [reply valueForKey:KeyUser], [reply valueForKey:KeyText]];
							if (![screen_name isEqualToString:user_id] &&
								[[reply valueForKey:KeyUser] isEqualToString:user_id])
							{
								notify = _notifyReply;
								category = KindReply;
							}
						}
						LOG(@"[%@(%@) performReceiveMessage]\ninReplyUser = %@\ncomment     = %@", [self className], _service, inReplyUser, comment);
					}
					if (name && (![name isEqualToString:@""]) &&(![name isEqualToString:screen_name]))
					{
						name = [NSString stringWithFormat:@" / %@", name];
					}
					else
					{
						name = @"";
					}
					LOG(@"[%@(%@) performReceiveMessage] author\nscreen_name='%@'\nname='%@'\nicon='%@'", [self className], _service, screen_name, name, icon);
					NSDictionary *item = [self createItem:item_id
												 withUser:screen_name
													 name:name
												  channel:channel
													  rid:@""
													 text:text
												 text_raw:text
												  comment:comment
												   source:source_info
											   created_at:created_at
											 user_profile:icon
												   notify:notify
													 kind:KindTimeline
												 category:category
											  inReplyUser:inReplyUser
											  retweetedBy:retweetedBy
											  retweetedId:retweetedId
												  keyword:hasKeyword
												  protect:NO
													first:NO
													 xmpp:YES];
					if (item)
					{
						[item retain];
						NSMutableArray *list = [[NSMutableArray alloc] init];
						NSMutableArray *notify = [[NSMutableArray alloc] init];
						@try
						{
							if ([self addTimeline:item withFirst:NO])
							{
								LOG(@"[%@(%@) performReceiveMessage]\nnotify = %@", [self className], _service, [item valueForKey:KeyId]);
								if (![channel isEqualToString:@""])
								{
									[self registCompleteIdentica:channel];
								}
								[self registCompleteIdentica:text];
								[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, retrieveCount);
							if (retrieveCount > 0)
							{
								if (_xmppCount >= retrieveCount)
								{
									LOG(@"[%@(%@) performReceiveMessage] 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] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				[self unlockService];
			}
			if (doRetrieve)
			{
				LOG(@"[%@(%@) performReceiveMessage] doRetrieve", [self className], _service);
				[self retrieveTimeline];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performReceiveMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

@end
