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

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

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

- (void)dealloc
{
	[_userProfile 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];
	}
	return self;
}

- (void)setupXMPP:(ServiceXMPP *)xmpp
{
	[xmpp addService:Jisko withObject:self];
	if ([_preferences accountJiskoUseXMPP])
	{
		[xmpp addXMPP:Jisko
			  withJid:[_preferences accountJiskoJidXMPP]
				 pass:[_preferences accountJiskoPassXMPP]
			   server:[_preferences accountJiskoServerXMPP]
				 port:[_preferences accountJiskoPortXMPP]
			  offline:[_preferences accountJiskoOfflineXMPP]
			 resource:@"Talk."];
	}
	else
	{
		[xmpp removeXMPP:Jisko];
	}
}

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

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

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

- (void)post:(NSString *)status
   withReply:(NSString *)reply
{
	//@synchronized(self)
	{
		@try
		{
			if (![reply isEqualToString:@""])
			{
				reply = [NSString stringWithFormat:@"&in_reply_to_status_id=%@", reply];
			}
			NSString *user = [_preferences accountJiskoUser];
			NSString *pass = [_preferences accountJiskoPass];
			NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
								 @"jisko.net", TwitterHost,
								 @"http://jisko.net", TwitterURL,
								 user, TwitterUser,
								 pass, TwitterPass,
								 Afficheur, TwitterSource,
								 nil];
			if ([_preferences generalConvertEmoji])
			{
				status = [self stringFromEmoji:status];
			}
			[self performPost:[[NSArray alloc] initWithObjects:status, dic, reply, nil]];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) post] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (NSDictionary *)parse:(NSDictionary *)obj
			   withUser:(NSString *)user_id
				   kind:(int)kind
				  first:(BOOL)first
{
	if (![obj isKindOfClass:[NSDictionary class]])
	{
		return nil;
	}
	//LOG(@"[%@(%@) parse:%d]\n%@", [self className], _service, kind, obj);
	NSDictionary *item = nil;
	@try
	{
		NSString *item_id = [NSString stringWithFormat:@"%@", [obj valueForKey:@"id"]];
		//LOG(@"[%@(%@) parse:%d] %@", [self className], _service, kind, item_id);
		NSDictionary *user = [obj valueForKey:@"user"];
		if (!user)
		{
			user = [obj valueForKey:@"sender"];
		}
		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;
		[_userProfile setObject:user_profile forKey:screen_name];
		if (!text || [text isKindOfClass:[NSNull class]])
		{
			text = @"";
		}
		if (![screen_name isEqualToString:user_id] &&
			([self hasKeyword:text] ||
			 [self hasKeyword:screen_name] ||
			 [self hasKeyword:user_name] ||
			 [self hasKeyword:channel]))
		{
			notify = GrowlAfficheurKeywordsNotify;
			first = NO;
		}
		if (kind == kindReply)
		{
			notify = _notifyReply;
			kindStr = KindReply;
			category = KindReply;
		}
		else if (kind == kindDM)
		{
			notify = _notifyDM;
			kindStr = KindDM;
			category = KindDM;
		}
		id in_reply = [obj valueForKey:@"in_reply_to_status_id"];
		if (!in_reply || [in_reply isKindOfClass:[NSNull class]])
		{
			if ((kind == kindTimeline) || (kind == kindChannel))
			{
				if (![screen_name isEqualToString:user_id] &&
					([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@$", user_id]] > 0) ||
					([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@[^!-~]", user_id]] > 0))
				{
					notify = _notifyReply;
					category = KindReply;
					first = NO;
				}
			}
		}
		else
		{
			NSDictionary *reply = [self fetch:[NSString stringWithFormat:@"%@", in_reply]
								  withService:_service];
			if (reply)
			{
				inReplyUser = [reply valueForKey:KeyUser];
				comment = [NSString stringWithFormat:@"(on %@: %@)",
						   [reply valueForKey:KeyUser], [reply valueForKey:KeyText]];
				if ((kind == kindTimeline) || (kind == kindChannel))
				{
					if (![screen_name isEqualToString:user_id] && [[reply valueForKey:KeyUser] isEqualToString:user_id])
					{
						notify = _notifyReply;
						category = KindReply;
						first = NO;
					}
				}
			}
			else if ((kind == kindTimeline) || (kind == kindChannel))
			{
				if (![screen_name isEqualToString:user_id] &&
					([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@$", user_id]] > 0) ||
					([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@[^!-~]", user_id]] > 0))
				{
					notify = _notifyReply;
					category = KindReply;
					first = 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"];
		//LOG(@"[%@(%@) parse:%d]\n%@ = %@", [self className], _service, kind, screen_name, user_profile);
		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
						  first:first];
	}
	@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
		{
			NSString *user = [_preferences accountJiskoUser];
			NSString *pass = [_preferences accountJiskoPass];
			NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
								 @"jisko.net", TwitterHost,
								 @"http://jisko.net", 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 accountJiskoUser];
			NSString *pass = [_preferences accountJiskoPass];
			NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
								 @"jisko.net", TwitterHost,
								 @"http://jisko.net", 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
{
	//LOG(@"[%@(%@) retrieveDirectMessage]", [self className], _service);
	BOOL retrieve = NO;
	@synchronized(self)
	{
		if (_dateDM)
		{
			retrieve = YES;
			[self setupRetrieveDateDM:nil];
		}
	}
	if (retrieve)
	{
		@try
		{
			NSString *user = [_preferences accountJiskoUser];
			NSString *pass = [_preferences accountJiskoPass];
			NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
								 @"jisko.net", TwitterHost,
								 @"http://jisko.net", TwitterURL,
								 user, TwitterUser,
								 pass, TwitterPass,
								 nil];
			[self performRetrieveDirectMessage:dic];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) retrieveDirectMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

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

- (void)favorite:(NSString *)item_id
{
	LOG(@"[%@(%@) favorite] %@", [self className], _service, item_id);
	if ([item_id rangeOfString:@"-"].length == 0)
	{
		@synchronized(self)
		{
			@try
			{
				id obj = [_controller fetchTimeline:item_id withService:_service];
				if (obj && ![[obj valueForKey:KeyKind] isEqualToString:KindDM])
				{
					NSString *user = [_preferences accountJiskoUser];
					NSString *pass = [_preferences accountJiskoPass];
					NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
										 @"jisko.net", TwitterHost,
										 @"http://jisko.net", TwitterURL,
										 user, TwitterUser,
										 pass, TwitterPass,
										 nil];
					[self performFavorite:[[NSArray alloc] initWithObjects:item_id, dic, nil]];
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) favorite] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
		}
	}
}

- (void)permalink:(NSString *)item_id
{
	LOG(@"[%@(%@) permalink] %@", [self className], _service, item_id);
	if ([item_id rangeOfString:@"-"].length == 0)
	{
		@try
		{
			id item = [self fetch:item_id withService:_service];
			if (item)
			{
				if ([[item valueForKey:KeyKind] isEqualToString:KindDM])
				{
					[self doOpenURL:@"http://jisko.net/notes/private"];
				}
				else
				{
					NSString *user = [item valueForKey:KeyUser];
					[self doOpenURL:[NSString stringWithFormat:@"http://jisko.net/%@/%@", user, item_id]];
				}
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) permalink] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

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

- (void)performReceiveMessage:(NSXMLElement *)message
{
	@try
	{
		//LOG(@"[%@(%@) performReceiveMessage]", [self className], _service);
		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
			{
				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;
				NSDate *created_at = [NSDate date];
				NSString *date = [created_at descriptionWithCalendarFormat:@"%Y%m%d%H%M%S%F" timeZone:nil locale:nil];
				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];
					item_id = [NSString stringWithString:date];
				}
				text = [text replaceWithExpression:@"\\r" replace:@""];
				if (comment)
				{
					comment = [comment replaceWithExpression:@"\\r" replace:@""];
				}
				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 accountJaikuUser];
					if (![user isEqualToString:user_id] &&
						([self hasKeyword:text] ||
						 [self hasKeyword:user] ||
						 [self hasKeyword:channel]))
					{
						notify = GrowlAfficheurKeywordsNotify;
					}
					if (![user_id isEqualToString:user] && reply && [reply isEqualToString: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 (![_userProfile valueForKey:user])
					{
						NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
											 [_preferences accountJiskoUser], TwitterUser,
											 [_preferences accountJiskoPass], TwitterPass,
											 nil];
						JiskoAPI *api = [[[JiskoAPI alloc] initWithDictionary:dic] autorelease];
						if (api)
						{
							[api setTimeoutInterval:10];
							id result = [api avatar:user];
							LOG(@"[%@(%@) performReceiveMessage]\navatar = %@\n%@", [self className], _service, [result className], result);
							if (result)
							{
								[_userProfile setObject:result forKey:user];
							}
						}
					}
					NSDictionary *item = [self createItem:item_id
												 withUser:user
													 name:@""
												  channel:channel
													  rid:@""
													 text:text
												  comment:comment
												   source:IM
											   created_at:created_at
											 user_profile:[_userProfile valueForKey:user]
												   notify:notify
													 kind:KindTimeline
												 category:category
											  inReplyUser:reply
													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]\nnotify = %@", [self className], _service, [item valueForKey:KeyId]);
								[self registComplete: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, 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
