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

#import "ServiceWassr.h"
#import "WassrAPI.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 ServiceWassr

- (id)init
{
	self = [super init];
	if (self)
	{
		_userProfile = [[NSMutableDictionary alloc] init];
		_channelList = [[NSMutableDictionary alloc] init];
		_channelListL = [[NSMutableDictionary alloc] init];
		_dateChannel = nil;
		_name = nil;
		_firstChannel = YES;
	}
	return self;
}

- (void)dealloc
{
	[_userProfile release];
	[_channelList release];
	if (_name)
	{
		[_name release];
	}
	[super dealloc];
}

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

- (void)setupXMPP:(ServiceXMPP *)xmpp
{
	[xmpp addService:Wassr withObject:self];
	if ([_preferences accountWassrUseXMPP])
	{
		[xmpp addXMPP:Wassr
			  withJid:[_preferences accountWassrJidXMPP]
				 pass:[_preferences accountWassrPassXMPP]
			   server:[_preferences accountWassrServerXMPP]
				 port:[_preferences accountWassrPortXMPP]
			  offline:[_preferences accountWassrOfflineXMPP]];
	}
	else
	{
		[xmpp removeXMPP:Wassr];
	}
}

- (void)setChannelL:(NSString *)channel
		 withNameEN:(NSString *)name_en
{
	@synchronized(_channelListL)
	{
		[_channelListL setValue:[name_en lowercaseString]
						 forKey:channel];
	}
}

- (NSString *)channelL:(NSString *)name_en
{
	NSArray *channels = nil;
	NSString *channel = nil;
	@synchronized(_channelList)
	{
		channels = [_channelList allKeysForObject:[name_en lowercaseString]];
	}
	if (channels && [channels count] > 0)
	{
		channel = [channels objectAtIndex:0];
	}
	return channel;
}

- (NSString *)nameEnL:(NSString *)channel
{
	NSString *name_en = nil;
	@synchronized(_channelListL)
	{
		name_en = [_channelListL valueForKey:[channel lowercaseString]];
	}
	return name_en;
}

- (void)setChannel:(NSString *)channel
		withNameEN:(NSString *)name_en
{
	@synchronized(_channelList)
	{
		[_channelList setValue:name_en
						forKey:channel];
	}
	[self setChannelL:channel
		   withNameEN:name_en];
}

- (NSString *)channel:(NSString *)name_en
{
	NSArray *channels = nil;
	NSString *channel = nil;
	@synchronized(_channelList)
	{
		channels = [_channelList allKeysForObject:name_en];
	}
	if (channels && [channels count] > 0)
	{
		channel = [channels objectAtIndex:0];
	}
	return channel;
}

- (NSString *)nameEn:(NSString *)channel
{
	NSString *name_en = nil;
	@synchronized(_channelList)
	{
		name_en = [_channelList valueForKey:channel];
	}
	return name_en;
}

- (NSDictionary *)channels
{
	return [NSDictionary dictionaryWithDictionary:_channelList];
}

- (BOOL)isSetedChannels
{
	return ([[self channels] count] > 0);
}

- (void)setChannelList
{
	NSString *user = [_preferences accountWassrUser];
	NSString *pass = [_preferences accountWassrPass];
	NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
						 @"api.wassr.jp", TwitterHost,
						 @"http://api.wassr.jp", TwitterURL,
						 user, TwitterUser,
						 pass, TwitterPass,
						 nil];
	WassrAPI *api = [[[WassrAPI alloc] initWithDictionary:dic] autorelease];
	if (api)
	{
		[api setTimeoutInterval:30];
		id result = [api channel_list];
		//LOG(@"[%@(%@) setChannelList]\n%@", [self className], _service, result);
		if (result && [result isKindOfClass:[NSDictionary class]])
		{
			NSArray *channels = [result valueForKey:@"channels"];
			if (channels && [channels isKindOfClass:[NSArray class]])
			{
				NSEnumerator *enumerator = [channels objectEnumerator];
				id obj;
				while ((obj = [enumerator nextObject]))
				{
					if ([obj isKindOfClass:[NSDictionary class]])
					{
						NSString *channel = [obj valueForKey:@"title"];
						NSString *name_en = [obj valueForKey:@"name_en"];
						if (channel && name_en)
						{
							//LOG(@"[%@(%@) setChannelList]\n%@: %@", [self className], _service, channel, name_en);
							[self setChannel:[channel lowercaseString]
								  withNameEN:name_en];
						}
					}
				}
			}
		}
	}
}

- (NSString *)stringChannel:(NSString *)channel
{
	return [channel replaceWithExpression:@"^ #(.*)$"
								  replace:@"\\1"
								  options:OgreMultilineOption];
}

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

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

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

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

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

- (void)performPostChannel:(NSArray *)array
{
	@try
	{
		[_controller syncPost];
		WassrAPI *api = [[[WassrAPI alloc] initWithDictionary:[array objectAtIndex:1]] autorelease];
		if (api)
		{
			NSString *channel = [array objectAtIndex:2];
			if ([channel lengthOfRegularExpression:@"\\=$"])
			{
				[self determinePost:@"Post was failure.\n\nChannel not found."
						  withTitle:_title
						   function:@"Post"];
			}
#if defined(_D_E_B_U_G_)
			else if (NO)
			{
				[self determinePost:nil
						  withTitle:_title
						   function:@"Post"];
			}
#endif //defined(_D_E_B_U_G_)
			else
			{
				[self registComplete:[array objectAtIndex:0]];
				id result = [api channel_update:[array objectAtIndex:0]
									withChannel:channel
											rid:[array objectAtIndex:3]];
				LOG(@"[%@(%@) performPostChannel]\n%@", [self className], _service, result);
				[self determinePost:[self determinePost:result withAPI:api function:@"Post"]
						  withTitle:_title
						   function:@"Post"];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performPostChannel] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[array release];
	}
}

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

- (void)post:(NSString *)status
   withReply:(NSString *)reply
{
	//@synchronized(self)
	{
		@try
		{
			NSString *channel = nil;
			if (![reply isEqualToString:@""])
			{
				NSString *reply_rid = @"&reply_status_rid=%@";
				NSDictionary *reply_item = [_controller fetchTimelineRid:reply withService:_service];
				if (reply_item)
				{
					NSString *reply_channel = [reply_item valueForKey:KeyChannel];
					if (reply_channel && ![reply_channel isEqualToString:@""])
					{
						reply_rid = @"&reply_channel_message_rid=%@";
						channel = [reply_channel replaceWithExpression:@"^.*#(.*)$"
															   replace:@"\\1"
															   options:OgreMultilineOption];
						LOG(@"[%@(%@) post] channel:\n'%@'\n'%@'", [self className], _service, reply_channel, channel);
					}
				}
				reply = [NSString stringWithFormat:reply_rid, reply];
			}
			NSString *user = [_preferences accountWassrUser];
			NSString *pass = [_preferences accountWassrPass];
			if (channel || [status lengthOfRegularExpression:@"^#.*? "])
			{
				if (!channel)
				{
					channel = [status replaceWithExpression:@"^#(.*?) .*$"
													replace:@"\\1"
													options:OgreMultilineOption];
					status = [status replaceWithExpression:@"^#.*? (.*)$"
												   replace:@"\\1"
												   options:OgreMultilineOption];
				}
				if ([[self channels] count] <= 0)
				{
					[self setChannelList];
				}
				if (![self channel:channel])
				{
					LOG(@"[%@(%@) post] channel: '%@'", [self className], _service, channel);
					NSString *nameEn = [self channelL:channel];
					LOG(@"[%@(%@) post]\nchannel:[channel lowercaseString]: '%@'", [self className], _service, nameEn);
					if (nameEn)
					{
						channel = [channel lowercaseString];
					}
					else
					{
						nameEn = [self nameEnL:channel];
						LOG(@"[%@(%@) post]\nnameEn:[channel lowercaseString]: '%@'", [self className], _service, nameEn);
						if (nameEn)
						{
							channel = nameEn;
						}
						else
						{
							channel = @"";
						}
					}
				}
				LOG(@"[%@(%@) post] channel: '%@'", [self className], _service, channel);
				NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
									 user, TwitterUser,
									 pass, TwitterPass,
									 @"api.wassr.jp", TwitterHost,
									 @"http://api.wassr.jp", TwitterURL,
									 @"body", TwitterUpdate,
									 Afficheur, TwitterSource,
									 nil];
				[self performPostChannel:[[NSArray alloc] initWithObjects:status, dic, channel, reply, nil]];
			}
			else
			{
				NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
									 user, TwitterUser,
									 pass, TwitterPass,
									 @"api.wassr.jp", TwitterHost,
									 @"http://api.wassr.jp", TwitterURL,
									 Afficheur, TwitterSource,
									 nil];
				[self performPost:[[NSArray alloc] initWithObjects:status, dic, reply, nil]];
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) post] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (NSImage *)imageWithUnichar:(unichar)unicode
{
	//LOG(@"[%@(%@) imageWithUnichar] %X", [self className], _service, unicode);
	return [NSImage imageNamed:[NSString stringWithFormat:@"emoji%04X", unicode]];
}

- (NSString *)stringRaw:(NSString *)str
{
	NSString *text = nil;
	NSMutableString *raw = [[[NSMutableString alloc] initWithString:@""] autorelease];
	//BOOL test = NO;
	@try
	{
		int len = [str length];
		unichar *buf = malloc(len * sizeof(unichar));
		if (buf)
		{
			@try
			{
				[str getCharacters:buf];
				unichar *end = buf + len;
				unichar *top = buf;
				unichar *p = buf;
				while (p < end)
				{
					if (((*p >= 0xe001) && (*p <= 0xf0ff)))
					{
						//test = YES;
						if ((p - top) > 0)
						{
							[raw appendString:
							 [NSString stringWithCharacters:top length:p - top]];
						}
						NSString *emoji = [_DoCoMo valueForKey:[NSString stringWithFormat:@"%04X", *p]];
						if (emoji)
						{
							//LOG(@"[%@(%@) stringRaw]\nDoCoMo: %@", [self className], _service, [keys objectAtIndex:0]);
							[raw appendFormat:@"[[%@]]", emoji];
						}
						else
						{
							emoji = [_EZweb valueForKey:[NSString stringWithFormat:@"%04X", *p]];
							if (emoji)
							{
								//LOG(@"[%@(%@) stringRaw]\nEZweb %@", [self className], _service, [keys objectAtIndex:0]);
								[raw appendFormat:@"[[%@]]", emoji];
							}
							else
							{
								emoji = [_SoftBank valueForKey:[NSString stringWithFormat:@"%04X", *p]];
								if (emoji)
								{
									//LOG(@"[%@(%@) stringRaw]\nSoftBank %@", [self className], _service, [keys objectAtIndex:0]);
									[raw appendFormat:@"[[%@]]", emoji];
								}
								else
								{
									LOG(@"[%@(%@) stringRaw]\n{emoji:%04X}\n%@\n%@", [self className], _service, *p, str, raw);
									[raw appendFormat:@"{emoji:%04X}", *p];
								}
							}
						}
						top = p + 1;
					}
					p++;
				}
				if (top < end)
				{
					[raw appendString:
					 [NSString stringWithCharacters:top length:end - top]];
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) stringRaw] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				free(buf);
			}
		}
		text = [NSString stringWithString:raw];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) stringRaw] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return text;
}

- (NSDictionary *)parse:(NSDictionary *)obj
			   withUser:(NSString *)user_id
				   kind:(int)kind
				  first:(BOOL)first
{
	if (![obj isKindOfClass:[NSDictionary class]])
	{
		return nil;
	}
	NSDictionary *item = nil;
	@try
	{
		LOG(@"[%@(%@) parse]\n%@", [self className], _service, obj);
		int hasKeyword = -1;
		NSString *item_id = [NSString stringWithFormat:@"%@", [obj valueForKey:@"id"]];
		NSDictionary *user = [obj valueForKey:@"user"];
		NSString *screen_name = [obj valueForKey:@"user_login_id"];
		NSString *user_name = [user valueForKey:@"screen_name"];
		//LOG(@"[%@(%@) parse]\n%@\n%@", [self className], _service, screen_name, user_name);
		if (!screen_name || [screen_name isKindOfClass:[NSNull class]])
		{
			screen_name = [user valueForKey:@"login_id"];
			user_name = [user valueForKey:@"nick"];
		}
		if (user_name && screen_name && [screen_name isEqualToString:user_id])
		{
			//LOG(@"[%@(%@) parse]\n%@", [self className], _service, user_name);
			if (!_name)
			{
				_name = [user_name copy];
			}
		}
		if (!user_name || [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];
		}
		//LOG(@"[%@(%@) parse]\n%@\n%@\n%@", [self className], _service, screen_name, user_name, name);
		id protected = [user valueForKey:@"protected"];
		NSString *user_profile = [user valueForKey:@"profile_image_url"];
		NSString *date = [NSString stringWithFormat:@"%@", [NSDate dateWithTimeIntervalSinceNow:0]];
		NSString *channel = [obj valueForKey:@"channel"];
		NSString *text = [obj valueForKey:@"text"];
		NSString *comment = @"";
		NSString *notify = _notify;
		NSString *kindStr = KindTimeline;
		NSString *category = KindTimeline;
		NSString *inReplyUser = nil;
#if 0 //def _D_E_B_U_G_
		id at = [obj valueForKey:@"created_at"];
		if (at && ![at isKindOfClass:[NSNull class]])
		{
			LOG(@"[%@(%@) parse]\n%@", [self className], _service, obj);
		}
#endif
		[_userProfile setObject:user_profile forKey:screen_name];
		if (!text || [text isKindOfClass:[NSNull class]])
		{
			text = @"";
		}
		if (channel && ![channel isKindOfClass:[NSNull class]])
		{
			NSString *title = [channel valueForKey:@"title"];
			NSString *name_en = [channel valueForKey:@"name_en"];
			if (![self isSetedChannels])
			{
				[self setChannelList];
			}
			if (![self nameEn:title])
			{
				[self setChannelList];
			}
			if (![self channel:name_en])
			{
				[self setChannelList];
			}
			[self setChannel:title withNameEN:name_en];
			notify = GrowlAfficheurWassrChannelNotify;
			item_id = [obj valueForKey:@"rid"];
			text = [obj valueForKey:@"body"];
			date = [obj valueForKey:@"created_on"];
			channel = [NSString stringWithFormat:@" #%@", title];
		}
		else
		{
			id epoch = [obj valueForKey:@"epoch"];
			if (epoch && ![epoch isKindOfClass:[NSNull class]])
			{
				//LOG(@"[%@(%@) parse]\n%@\n%@", [self className], _service, [epoch className], epoch);
				date = [NSString stringWithFormat:@"%@",
						[NSDate dateWithTimeIntervalSince1970:[epoch doubleValue]]];
			}
			else
			{
				LOG(@"[%@(%@) parse]\n%@", [self className], _service, obj);
			}
			channel = @"";
		}
		if (![screen_name isEqualToString:user_id])
		{
			hasKeyword = [self hasKeywords:
						  [NSArray arrayWithObjects:
						   text, screen_name, user_name, channel, nil]];
			if (hasKeyword == 0)
			{
				notify = GrowlAfficheurKeywordsNotify;
				first = NO;
			}
		}
		if (kind == kindReply)
		{
			notify = _notifyReply;
			kindStr = KindReply;
			category = KindReply;
		}
		else if (kind == kindChannel)
		{
			notify = GrowlAfficheurWassrChannelNotify;
			kindStr = KindChannel;
		}
		else if (kind == kindDM)
		{
			notify = _notifyDM;
			kindStr = KindDM;
			category = KindDM;
		}
		id in_reply = [obj valueForKey:@"reply_status_url"];
		if (in_reply && ![in_reply isKindOfClass:[NSNull class]])
		{
			inReplyUser = [obj valueForKey:@"reply_user_login_id"];
			comment = [NSString stringWithFormat:@"(on %@: %@)",
					   inReplyUser,
					   [obj valueForKey:@"reply_message"]];
			if (![screen_name isEqualToString:user_id] && [inReplyUser isEqualToString:user_id])
			{
				notify = _notifyReply;
				category = KindReply;
			}
		}
		in_reply = [obj valueForKey:@"reply"];
		if (in_reply && ![in_reply isKindOfClass:[NSNull class]])
		{
			inReplyUser = [[in_reply valueForKey:@"user"] valueForKey:@"login_id"];
			comment = [NSString stringWithFormat:@"(on %@: %@)",
					   inReplyUser,
					   [in_reply valueForKey:@"body"]];
			comment = [self convertText:comment];
			if (![screen_name isEqualToString:user_id] && [inReplyUser isEqualToString:user_id])
			{
				notify = _notifyReply;
				category = KindReply;
			}
		}
		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;
			}
		}
	//	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];
		}
		NSString *rid = [obj valueForKey:@"rid"];
		NSString *text_raw = [self stringRaw:text];
		item = [self createItem:item_id
					   withUser:screen_name
						   name:name
						channel:channel
							rid:rid
						   text:text
					   text_raw:text_raw
						comment:comment
						 source:[obj valueForKey:@"source"]
					 created_at:[NSDate dateWithNaturalLanguageString:date]
				   user_profile:user_profile
						 notify:notify
						   kind:kindStr
					   category:category
					inReplyUser:inReplyUser
						keyword:hasKeyword
						protect:[protected intValue]
						  first:first];
		if (item)
		{
			NSString *photo_url = [obj valueForKey:@"photo_url"];
			if (photo_url && ![photo_url isKindOfClass:[NSNull class]] && ![photo_url isEqualToString:@""])
			{
				NSMutableDictionary *photo = [NSMutableDictionary dictionaryWithDictionary:item];
				NSString *text = [NSString stringWithFormat:@"%@\n%@",
								  [item valueForKey:KeyText], photo_url];
				[photo setValue:photo_url forKey:KeyPhotoURL];
				[photo setValue:text forKey:KeyText];
				[photo setValue:text forKey:KeyTextEx];
				item = [NSDictionary dictionaryWithDictionary:photo];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) parse] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return item;
}

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

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

- (void)retrieveTimeline
{
	//LOG(@"[%@(%@) retrieveTimeline]", [self className], _service);
	BOOL retrieve = NO;
	@synchronized(self)
	{
		if (_dateTimeline)
		{
			retrieve = YES;
			[self setupRetrieveDateTimeline:nil];
		}
	}
	if (retrieve)
	{
		@try
		{
			NSString *user = [_preferences accountWassrUser];
			NSString *pass = [_preferences accountWassrPass];
			NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
								 @"api.wassr.jp", TwitterHost,
								 @"http://api.wassr.jp", TwitterURL,
								 user, TwitterUser,
								 pass, TwitterPass,
								 nil];
			[self performRetrieveTimeline:dic];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) retrieveTimeline] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (void)retrieveReplies
{
	//LOG(@"[%@(%@) retrieveTimeline]", [self className], _service);
	BOOL retrieve = NO;
	@synchronized(self)
	{
		if (_dateReplies)
		{
			retrieve = YES;
			[self setupRetrieveDateReplies:nil];
		}
	}
	if (retrieve)
	{
		@try
		{
			NSString *user = [_preferences accountWassrUser];
			NSString *pass = [_preferences accountWassrPass];
			NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
								 @"api.wassr.jp", TwitterHost,
								 @"http://api.wassr.jp", TwitterURL,
								 user, TwitterUser,
								 pass, TwitterPass,
								 nil];
			[self performRetrieveReplies:dic];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) retrieveReplies] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (void)retrieveDirectMessage
{
}

- (void)retrieveChannel
{
	//LOG(@"[%@(%@) retrieveChannel]", [self className], _service);
	BOOL retrieve = NO;
	@synchronized(self)
	{
		if (_dateChannel)
		{
			retrieve = YES;
			[self setupRetrieveDateChannel:nil];
		}
	}
	if (retrieve)
	{
		@try
		{
			NSString *user = [_preferences accountWassrUser];
			NSString *pass = [_preferences accountWassrPass];
			NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
								 @"api.wassr.jp", TwitterHost,
								 @"http://api.wassr.jp", TwitterURL,
								 user, TwitterUser,
								 pass, TwitterPass,
								 nil];
			[self performRetrieveChannel:dic];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) retrieveChannel] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (id)getWithAuth:(NSString *)url
{
	//LOG(@"[%@(%@) getWithAuth]\n%@", [self className], _service, url);
	id result = nil;
	@synchronized(self)
	{
		@try
		{
			NSString *http = [url replaceWithExpression:@"(http://)(.*?)/(.*)$"
												replace:@"\\1"];
			NSString *host = [url replaceWithExpression:@"(http://)(.*?)/(.*)$"
												replace:@"\\2"];
			//NSString *path = [url replaceWithExpression:@"(http://)(.*?)/(.*)$"
			//									replace:@"\\3"];
			//LOG(@"[%@(%@) getWithAuth]\n%@\n%@\n%@", [self className], _service, http, host, path);
			NSString *user = [_preferences accountWassrUser];
			NSString *pass = [_preferences accountWassrPass];
			WassrAPI *api = [[[WassrAPI alloc] initWithDictionary:
							  [NSDictionary dictionaryWithObjectsAndKeys:
							   host, TwitterHost,
							   [NSString stringWithFormat:@"%@%@", http, host], TwitterURL,
							   user, TwitterUser,
							   pass, TwitterPass,
							   nil]] autorelease];
			NSMutableDictionary *header = [NSMutableDictionary dictionary];
			[api setRedirect:NO];
			[api head:url header:header body:@""];
			[api setRedirect:YES];
			if (![[api headers] valueForKey:@"Location"])
			{
				result = [api get:url header:header body:@""];
				return result;
			}
			[api setShouldHandleCookies:YES];
			id html = [api get:[NSString stringWithFormat:@"%@%@", http, host]
					   header:header
						 body:@""];
			//LOG(@"[%@(%@) getWithAuth] wassr.jp\n%@", [self className], _service, [api headers]);
			html =[[[NSString alloc] initWithData:html
										 encoding:NSUTF8StringEncoding] autorelease];
			NSString *name = nil;
			NSString *value = nil;
			if ([HTTP isHTML:html])
			{
				//LOG(@"[%@(%@) getWithAuth] wassr.jp", [self className], _service);
				html = [html replaceWithExpression:@"<!--.*?-->"
										   replace:@""
										   options:OgreMultilineOption];
				//LOG(@"[%@(%@) getWithAuth]\n%@", [self className], _service, html);
				if ([html lengthOfRegularExpression:@"<form method=\"post\" action=\"/account/login\" .*?</form>"
										withOptions:OgreMultilineOption] > 0)
				{
					NSString *form = [html replaceWithExpression:@"^.*(<form method=\"post\" action=\"/account/login\" .*?</form>).*$"
														 replace:@"\\1"
														 options:OgreMultilineOption];
					//LOG(@"[%@(%@) getWithAuth] wassr.jp\n%@", [self className], _service, form);
					name = [form replaceWithExpression:@"^.*<form method=\"post\" action=\"/account/login\" .*?<input .*? name=\"(.*?)\" value=\"(.*?)\".*$"
											   replace:@"\\1"
											   options:OgreMultilineOption];
					value = [form replaceWithExpression:@"^.*<form method=\"post\" action=\"/account/login\" .*?<input .*? name=\"(.*?)\" value=\"(.*?)\".*$"
												replace:@"\\2"
												options:OgreMultilineOption];
					//LOG(@"[%@(%@) getWithAuth]\nname = %@\nvalue = %@", [self className], _service, name, value);
				}
			}
			if (name)
			{
				html = [api post:[NSString stringWithFormat:@"%@%@/account/login", http, host]
						  header:header
							body:[NSString stringWithFormat:
								  @"%@=%@&login_id=%@&login_pw=%@",
								  name,
								  value,
								  user,
								  pass]];
				//LOG(@"[%@(%@) getWithAuth] login\n%@", [self className], _service, [api headers]);
				html =[[[NSString alloc] initWithData:html
											 encoding:NSUTF8StringEncoding] autorelease];
				//LOG(@"[%@(%@) getWithAuth]\n%d", [self className], _service, [api statusCode]);
				//LOG(@"[%@(%@) getWithAuth]\n%@", [self className], _service, [api headers]);
				//LOG(@"[%@(%@) getWithAuth]\n%@", [self className], _service, html);
			}
			name = nil;
			if ([HTTP isHTML:html])
			{
				//LOG(@"[%@(%@) getWithAuth] login", [self className], _service);
				html = [html replaceWithExpression:@"<!--.*?-->"
										   replace:@""
										   options:OgreMultilineOption];
				//LOG(@"[%@(%@) getWithAuth]\n%@", [self className], _service, html);
				if ([html lengthOfRegularExpression:@"<form method=\"post\" action=\"/account/logout\" .*?</form>"
										withOptions:OgreMultilineOption] > 0)
				{
					NSString *form = [html replaceWithExpression:@"^.*(<form method=\"post\" action=\"/account/logout\" .*?</form>).*$"
														 replace:@"\\1"
														 options:OgreMultilineOption];
					//LOG(@"[%@(%@) getWithAuth] login\n%@", [self className], _service, form);
					name = [form replaceWithExpression:@"^.*<form method=\"post\" action=\"/account/logout\" .*?<input .*? name=\"(.*?)\" value=\"(.*?)\".*$"
											   replace:@"\\1"
											   options:OgreMultilineOption];
					value = [form replaceWithExpression:@"^.*<form method=\"post\" action=\"/account/logout\" .*?<input .*? name=\"(.*?)\" value=\"(.*?)\".*$"
												replace:@"\\2"
												options:OgreMultilineOption];
					//LOG(@"[%@(%@) getWithAuth]\nname = %@\nvalue = %@", [self className], _service, name, value);
				}
			}
			if (name)
			{
				//LOG(@"[%@(%@) getWithAuth] get", [self className], _service);
				result = [api get:url header:header body:@""];
				html = [api post:[NSString stringWithFormat:@"%@%@/account/logout", http, host]
						  header:header
							body:[NSString stringWithFormat:
								  @"%@=%@",
								  name,
								  value]];
				//LOG(@"[%@(%@) getWithAuth] logout\n%@", [self className], _service, [api headers]);
				html =[[[NSString alloc] initWithData:html
											 encoding:NSUTF8StringEncoding] autorelease];
				//LOG(@"[%@(%@) getWithAuth]\n%d", [self className], _service, [api statusCode]);
				//LOG(@"[%@(%@) getWithAuth]\n%@", [self className], _service, [api headers]);
				//LOG(@"[%@(%@) getWithAuth]\n%@", [self className], _service, html);
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) getWithAuth] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
	return result;
}

- (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:_service];
		NSString *channel = [obj valueForKey:KeyChannel];
		if (obj)
		{
			NSString *rid = [obj valueForKey:KeyRId];
			NSString *replyTo = @"";
			NSString *text = @"";
			if (rid && ![rid isEqualToString:@""])
			{
				item_id = [NSString stringWithFormat:@"%@", rid];
				replyTo = [NSString stringWithFormat:@"Reply to: %@%@",
						   [obj valueForKey:KeyText], [obj valueForKey:KeyComment]];
			}
			else
			{
				if ([_preferences generalRetrieveWhenReply])
				{
					[self setDelayedReply:item_id];
					retrieve = YES;
				}
				item_id = @"";
			}
			NSString *name_en = nil;
			LOG(@"[%@(%@) doReply]\nchannel: '%@'", [self className], _service, channel);
			if (channel && ![channel isEqualToString:@""])
			{
				name_en = [self nameEn:[self stringChannel:channel]];
			}
			if (withText)
			{
				text = [NSString stringWithFormat:@"@%@ ",
						[obj valueForKey:KeyUser]];
				if (name_en)
				{
					text = [NSString stringWithFormat:@"#%@ %@", name_en, text];
				}
			}
			[_controller setReply:[self replyAttributedString:replyTo]
					  withService:_service
						inReplyTo:item_id
							 text:text];
		}
		if (retrieve)
		{
			if (channel && ![channel isEqualToString:@""])
			{
				[self retrieveChannel];
			}
			else
			{
				[self retrieveTimeline];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) doReply] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

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

- (void)favorite:(NSString *)item_id
{
	LOG(@"[%@(%@) favorite] %@", [self className], _service, item_id);
	if ([item_id rangeOfString:@"-"].length == 0)
	{
		id obj = [_controller fetchTimelineRid:item_id withService:_service];
		if (obj)
		{
			LOG(@"[%@(%@) favorite] %@", [self className], _service, [obj valueForKey:KeyUser]);
			@synchronized(self)
			{
				@try
				{
					NSString *user = [_preferences accountWassrUser];
					NSString *pass = [_preferences accountWassrPass];
					NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
										 @"api.wassr.jp", TwitterHost,
										 @"http://api.wassr.jp", TwitterURL,
										 user, TwitterUser,
										 pass, TwitterPass,
										 nil];
					NSString *channel = [obj valueForKey:KeyChannel];
					SEL favorite = @selector(performFavorite:);
					if (channel && ![channel isEqualToString:@""])
					{
						favorite = @selector(performFavoriteChannel:);
					}
					[self performSelector:favorite
							   withObject:[[NSArray alloc] initWithObjects:item_id, dic, nil]];
				}
				@catch (NSException *exception)
				{
					EXPLOG(@"[%@(%@) favorite] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
				}
			}
		}
	}
	else if ([_preferences generalRetrieveWhenReply])
	{
		[self retrieveChannel];
		[self retrieveTimeline];
	}
}

- (NSString *)permalinkURL:(NSString *)item_id
{
	LOG(@"[%@(%@) permalinkURL] %@", [self className], _service, item_id);
	NSString *url = nil;
	@try
	{
		id item = [self fetchALL:item_id withService:_service];
		if (item)
		{
			NSString *channel = [item valueForKey:KeyChannel];
			if (channel && ![channel isEqualToString:@""])
			{
				if ([item_id rangeOfString:@"-"].length)
				{
					LOG(@"[%@(%@) permalinkURL] channel: %@", [self className], _service, channel);
					url = [NSString stringWithFormat:@"http://wassr.jp/channel/%@",
						   [channel replaceWithExpression:@"^ \\#(.*)$" replace:@"\\1"]];
				}
				else
				{
					LOG(@"[%@(%@) permalinkURL] channel: %@", [self className], _service, [channel substringFromIndex:2]);
					url = [NSString stringWithFormat:@"http://wassr.jp/channel/%@/messages/%@",
						   [[channel substringFromIndex:2] lowercaseString], [item valueForKey:KeyRId]];
				}
			}
			else if ([item_id rangeOfString:@"-"].length)
			{
				NSString *user = [item valueForKey:KeyUser];
				LOG(@"[%@(%@) permalinkURL] user: %@", [self className], _service, user);
				url = [NSString stringWithFormat:@"http://wassr.jp/user/%@/", user];
			}
			else
			{
				NSString *user = [item valueForKey:KeyUser];
				LOG(@"[%@(%@) permalinkURL] user: %@", [self className], _service, user);
				url = [NSString stringWithFormat:@"http://wassr.jp/user/%@/statuses/%@",
					   user, [item valueForKey:KeyRId]];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) permalinkURL] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return url;
}

- (void)permalink:(NSString *)item_id
{
	LOG(@"[%@(%@) permalink] %@", [self className], _service, 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]);
	}
}

- (NSString*)emojiStringFromEmojiXMPP:(OGRegularExpressionMatch*)aMatch
						  contextInfo:(id)contextInfo
{
	//LOG(@"[%@(%@) emojiStringFromEmojiXMPP]\n%@", [self className], _service, [aMatch matchedString]);
	NSString *str = [aMatch matchedString];
	NSString *code = [str replaceWithExpression:@"\\[\\[(.*?)\\]\\]"
										  replace:@"\\1"];
	id obj = [_DoCoMo allKeysForObject:code];
	if ((obj == nil) || ([obj count] == 0))
	{
		obj = [_EZweb allKeysForObject:code];
		if ((obj == nil) || ([obj count] == 0))
		{
			obj = [_SoftBank allKeysForObject:code];
		}
	}
	if (obj && ([obj count] > 0))
	{
		NSScanner *scanner = [NSScanner scannerWithString:[obj objectAtIndex:0]];
		unichar unicode = 0;
		if ([scanner scanHexInt:(unsigned*)&unicode])
		{
			str = [NSString stringWithCharacters:&unicode length:1];
		}
	}
	else
	{
		LOG(@"[%@(%@) emojiStringFromEmojiXMPP]\n%@", [self className], _service, [aMatch matchedString]);
	}
	return str;
}

- (NSString *)stringFromEmojiXMPP:(NSString *)str
{
	//LOG(@"[%@(%@) stringFromEmojiXMPP]\n%@", [self className], _service, str);
	@synchronized([NSApplication sharedApplication])
	{
		@try
		{
			OGRegularExpression *regex = [OGRegularExpression
										  regularExpressionWithString:@"\\[\\[.*?\\]\\]"
										  options:OgreMultilineOption];
			str = [regex replaceAllMatchesInString:str
										  delegate:self
								   replaceSelector:@selector(emojiStringFromEmojiXMPP:contextInfo:)
									   contextInfo:nil];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) stringFromEmojiXMPP] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
	//LOG(@"[%@(%@) stringFromEmojiXMPP]\n%@", [self className], _service, str);
	//LOG(@"[%@(%@) stringFromEmojiXMPP] %@", [self className], _service, str);
	return str;
}

- (NSString *)userName
{
	NSString *user_name = nil;
	@try
	{
		NSString *user = [_preferences accountWassrUser];
		NSString *pass = [_preferences accountWassrPass];
		WassrAPI *api = [[[WassrAPI alloc] initWithDictionary:
						  [NSDictionary dictionaryWithObjectsAndKeys:
						   @"wassr.jp", TwitterHost,
						   @"http://wassr.jp", TwitterURL,
						   user, TwitterUser,
						   pass, TwitterPass,
						   nil]] autorelease];
		NSMutableDictionary *header = [NSMutableDictionary dictionaryWithObjectsAndKeys:
									   @"wassr.jp", @"Host",
									   nil];
		[api setShouldHandleCookies:YES];
		id html = [api get:@"http://wassr.jp"
					header:header
					  body:@""];
		html =[[[NSString alloc] initWithData:html
									 encoding:NSUTF8StringEncoding] autorelease];
		NSString *name = nil;
		NSString *value = nil;
		if ([HTTP isHTML:html])
		{
			//LOG(@"[%@(%@) userName] wassr.jp", [self className], _service);
			html = [html replaceWithExpression:@"<!--.*?-->"
									   replace:@""
									   options:OgreMultilineOption];
			//LOG(@"[%@(%@) userName]\n%@", [self className], _service, html);
			if ([html lengthOfRegularExpression:@"<form method=\"post\" action=\"/account/login\" .*?</form>"
									withOptions:OgreMultilineOption] > 0)
			{
				NSString *form = [html replaceWithExpression:@"^.*(<form method=\"post\" action=\"/account/login\" .*?</form>).*$"
													 replace:@"\\1"
													 options:OgreMultilineOption];
				//LOG(@"[%@(%@) userName] wassr.jp\n%@", [self className], _service, form);
				name = [form replaceWithExpression:@"^.*<form method=\"post\" action=\"/account/login\" .*?<input .*? name=\"(.*?)\" value=\"(.*?)\".*$"
										   replace:@"\\1"
										   options:OgreMultilineOption];
				value = [form replaceWithExpression:@"^.*<form method=\"post\" action=\"/account/login\" .*?<input .*? name=\"(.*?)\" value=\"(.*?)\".*$"
											replace:@"\\2"
											options:OgreMultilineOption];
				//LOG(@"[%@(%@) userName]\nname = %@\nvalue = %@", [self className], _service, name, value);
			}
		}
		if (name)
		{
			html = [api post:@"http://wassr.jp/account/login"
					  header:header
						body:[NSString stringWithFormat:
							  @"%@=%@&login_id=%@&login_pw=%@",
							  name,
							  value,
							  user,
							  pass]];
			//LOG(@"[%@(%@) userName] login\n%@", [self className], _service, [api headers]);
			html =[[[NSString alloc] initWithData:html
										 encoding:NSUTF8StringEncoding] autorelease];
			//LOG(@"[%@(%@) userName]\n%d", [self className], _service, [api statusCode]);
			//LOG(@"[%@(%@) userName]\n%@", [self className], _service, [api headers]);
			//LOG(@"[%@(%@) userName]\n%@", [self className], _service, html);
		}
		name = nil;
		if ([HTTP isHTML:html])
		{
			//LOG(@"[%@(%@) userName] login", [self className], _service);
			html = [html replaceWithExpression:@"<!--.*?-->"
									   replace:@""
									   options:OgreMultilineOption];
			//LOG(@"[%@(%@) userName]\n%@", [self className], _service, html);
			if ([html lengthOfRegularExpression:@"<form method=\"post\" action=\"/account/logout\" .*?</form>"
									withOptions:OgreMultilineOption] > 0)
			{
				NSString *form = [html replaceWithExpression:@"^.*(<form method=\"post\" action=\"/account/logout\" .*?</form>).*$"
													 replace:@"\\1"
													 options:OgreMultilineOption];
				//LOG(@"[%@(%@) userName] login\n%@", [self className], _service, form);
				name = [form replaceWithExpression:@"^.*<form method=\"post\" action=\"/account/logout\" .*?<input .*? name=\"(.*?)\" value=\"(.*?)\".*$"
										   replace:@"\\1"
										   options:OgreMultilineOption];
				value = [form replaceWithExpression:@"^.*<form method=\"post\" action=\"/account/logout\" .*?<input .*? name=\"(.*?)\" value=\"(.*?)\".*$"
											replace:@"\\2"
											options:OgreMultilineOption];
				//LOG(@"[%@(%@) userName]\nname = %@\nvalue = %@", [self className], _service, name, value);
			}
		}
		if (name)
		{
			//LOG(@"[%@(%@) getWithAuth] get", [self className], _service);
			html = [html replaceWithExpression:@"<!--.*?-->"
									   replace:@""
									   options:OgreMultilineOption];
			user_name = [html replaceWithExpression:@"^.*?<title>(.*?) - Wassr \\[.*$"
											replace:@"\\1"
											options:OgreMultilineOption];
			//LOG(@"[%@(%@) userName]\n%@", [self className], _service, user_name);
			html = [api post:@"http://wassr.jp/account/logout"
					  header:header
						body:[NSString stringWithFormat:
							  @"%@=%@",
							  name,
							  value]];
			//LOG(@"[%@(%@) userName] logout\n%@", [self className], _service, [api headers]);
			html =[[[NSString alloc] initWithData:html
										 encoding:NSUTF8StringEncoding] autorelease];
			//LOG(@"[%@(%@) userName]\n%d", [self className], _service, [api statusCode]);
			//LOG(@"[%@(%@) userName]\n%@", [self className], _service, [api headers]);
			//LOG(@"[%@(%@) userName]\n%@", [self className], _service, html);
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) getWithAuth] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return user_name;
}

- (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
			{
				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 *date = [created_at descriptionWithCalendarFormat:@"%Y%m%d%H%M%S%F" timeZone:nil locale:nil];
				if ([body rangeOfRegularExpressionString:[NSString stringWithFormat:@"^%@ ", ProfilePhoto]].length > 0)
				{
					if (_previousMessage)
					{
						NSString *photoURL = [body replaceWithExpression:[NSString stringWithFormat:@"^%@ (.*)$", ProfilePhoto]
																 replace:@"\\1"];
						photoURL = [photoURL replaceWithExpression:@"photo_html$"
														   replace:@"photo"];
						photoURL = [NSString stringWithFormat:@"%@%@", ProfilePhoto, photoURL];
						LOG(@"[%@(%@) performReceiveMessage]\nphoto = %@", [self className], _service, photoURL);
						id item = [_controller replaceTimeline:[_previousMessage valueForKey:KeyId]
												   withService:[_previousMessage valueForKey:KeyService]
													  photoURL:photoURL];
						if (item)
						{
							@try
							{
								[self finishAddTimeline:[NSArray arrayWithObjects:item, nil]
											 withNotify:[NSArray arrayWithObjects:nil]];
							}
							@catch (NSException *exception)
							{
								EXPLOG(@"[%@(%@) performReceiveMessage#previousMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
							}
							@finally
							{
								[item release];
							}
						}
						[_previousMessage release];
						_previousMessage = nil;
					}
				}
				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:@"\\1" options:OgreMultilineOption];
						LOG(@"[%@(%@) performReceiveMessage]\nuser=\'%@\'", [self className], _service, user);
						LOG(@"[%@(%@) performReceiveMessage]\ntext=\'%@\'", [self className], _service, text);
						if ([text rangeOfRegularExpressionString:@"^(#[0-9a-z_]+) (.*)$" options:OgreMultilineOption].length > 0)
						{
							channel = [text replaceWithExpression:@"^(#[0-9a-z_]+) (.*)$" replace:@" \\1" options:OgreMultilineOption];
							text = [text replaceWithExpression:@"^(#[0-9a-z_]+) (.*)$" replace:@"\\2" options:OgreMultilineOption];
						}
						else if ([text rangeOfRegularExpressionString:@"^(.*) > (.*)$" options:OgreMultilineOption].length > 0)
						{
							comment = [text replaceWithExpression:@"^(.*) > (.*)$" replace:@"\\2" options:OgreMultilineOption];
							text = [text replaceWithExpression:@"^(.*) > (.*)$" replace:@"\\1" options:OgreMultilineOption];
							reply = [comment replaceWithExpression:@"^(.*?):.*$" replace:@"\\1" options:OgreMultilineOption];
							comment = [NSString stringWithFormat:@"(on %@:)", comment];
						}
						item_id = [NSString stringWithFormat:@"-%@", date];
					}
					text = [text replaceWithExpression:@"\\r" replace:@""];
					if (comment)
					{
						comment = [comment replaceWithExpression:@"\\r" replace:@""];
					}
					NSString *text_raw = text;
					//[self stringFromEmojiXMPP:@"[[雪]][[猫]]"];
					text = [self stringFromEmojiXMPP:text];
					LOG(@"[%@(%@) performReceiveMessage]"\
						@"\nuser    = %@"\
						@"\nchannel = %@"\
						@"\ntext    = %@"\
						@"\nitem_id  = %@"\
						@"\nreply   = %@"\
						@"\ncomment = %@"\
						@"\ncreated_at = %@",
						[self className], _service, user, channel, text, item_id, reply, comment, created_at);
					if (user && text && item_id)
					{
						NSString *category = KindTimeline;
						NSString *notify = _notify;
						NSString *user_id = [_preferences accountWassrUser];
						if (channel && ![channel isEqualToString:@""])
						{
							if (![self isSetedChannels])
							{
								[self setChannelList];
							}
							NSString *title = [self channel:[self stringChannel:channel]];
							if (!title)
							{
								[self setChannelList];
								title = [self channel:[self stringChannel:channel]];
							}
							if (title)
							{
								channel = [NSString stringWithFormat:@" #%@", title];
							}
							notify = GrowlAfficheurWassrChannelNotify;
						}
						int hasKeyword = -1;
						if (![user isEqualToString:user_id])// && [self hasKeyword:text])
						{
							hasKeyword = [self hasKeywords:
										  [NSArray arrayWithObjects:
										   text, user, channel, nil]];
							if (hasKeyword == 0)
							{
								notify = GrowlAfficheurKeywordsNotify;
							}
						}
						if (!_name)
						{
							NSString *name = [self userName];
							if (name)
							{
								_name = [name copy];
							}
						}
						if (![user_id isEqualToString:user] && reply && [reply isEqualToString:_name])
						{
							notify = _notifyReply;
							category = KindReply;
						}
						if (([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@$", user_id]] > 0) ||
							([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@[^!-~]", user_id]] > 0))
						{
							notify = _notifyReply;
							category = KindReply;
						}
						if (![_userProfile valueForKey:user])
						{
							NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
												 @"api.wassr.jp", TwitterHost,
												 @"http://api.wassr.jp", TwitterURL,
												 [_preferences accountWassrUser], TwitterUser,
												 [_preferences accountWassrPass], TwitterPass,
												 nil];
							WassrAPI *api = [[[WassrAPI alloc] initWithDictionary:dic] autorelease];
							if (api)
							{
								NSString *profile_image_url = nil;
								[api setTimeoutInterval:10];
								id result = [api show:user];
								//LOG(@"[%@(%@) performReceiveMessage]\nresult = %@", [self className], _service, [result className]);
								if ([result isKindOfClass:[NSArray class]])
								{
									//LOG(@"[%@(%@) performReceiveMessage]\nresult = %@", [self className], _service, result);
									NSDictionary *user_dic = [[result objectAtIndex:0] valueForKey:@"user"];
									if (user_dic)
									{
										//LOG(@"[%@(%@) performReceiveMessage]\nuser_dic = %@", [self className], _service, user_dic);
										profile_image_url = [user_dic valueForKey:@"profile_image_url"];
									}
								}
								if (!profile_image_url)
								{
									id html = [api get:[NSString stringWithFormat:@"http://wassr.jp/user/%@", user]
												header:[NSMutableDictionary dictionaryWithObjectsAndKeys:
														@"wassr.jp", @"Host",
														nil]
												  body:@""];
									if ([html isKindOfClass:[NSData class]])
									{
										html = [[[NSString alloc] initWithData:html
																	  encoding:NSUTF8StringEncoding]
												autorelease];
										if ([HTTP isHTML:html])
										{
											if ([html lengthOfRegularExpression:
												 @"<div class=\"image\">.*?<img src=\"/user/.*?/profile_img.png.128.(.*?)\".*?</a></div>"])
											{
												profile_image_url = [NSString stringWithFormat:
																	 @"http://wassr.jp/user/%@/profile_img.png.64.%@",
																	 user,
																	 [html replaceWithExpression:@"^.*<div class=\"image\">.*?<img src=\"/user/.*?/profile_img.png.128.(.*?)\".*?</a></div>.*$"
																						 replace:@"\\1"
																						 options:OgreMultilineOption]];
											}
										}
									}
								}
								if (profile_image_url)
								{
									LOG(@"[%@(%@) performReceiveMessage]\nprofile_image_url = %@", [self className], _service, profile_image_url);
									[_userProfile setObject:profile_image_url forKey:user];
								}
							}
						}
						NSDictionary *item = [self createItem:item_id
													 withUser:user
														 name:@""
													  channel:channel
														  rid:@""
														 text:text
													 text_raw:text_raw
													  comment:comment
													   source:IM
												   created_at:created_at
												 user_profile:[_userProfile valueForKey:user]
													   notify:notify
														 kind:KindTimeline
													 category:category
												  inReplyUser:reply
													  keyword:hasKeyword
													  protect:NO
														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])
								{
									if (_previousMessage)
									{
										[_previousMessage release];
									}
									_previousMessage = [item copy];
									//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 retrieveTimeline];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performReceiveMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

@end
