//
//  ServiceXMPP.m
//  Afficheur
//
//  Created by kichi on 08/11/15.
//  Copyright 2008 Katsuhiko Ichinose. All rights reserved.
//

#import "AfficheurController.h"
#import "AfficheurGrowl.h"
#import "ServiceXMPP.h"
#import "ServiceXMPPClient.h"
#import "Service.h"
#import "NSStringOgreKit.h"


@implementation ServiceXMPP

NSString *JidJaiku = @"jaiku@jaiku.com";
NSString *JidWassr = @"wassr-bot@wassr.jp";
NSString *JidIdentica = @"update@identi.ca";
//NSString *JidJisko = @"bot@jisko.net";

NSString *XmppXlc = @"XmppXlc";
NSString *XmppName = @"XmppName";
NSString *XmppMsg = @"XmppMsg";

- (void)getMainThread:(id)object
{
	_mainThread = [NSThread currentThread];
}

- (id)init
{
	self = [super init];
	if (self)
	{
		_from = [[NSDictionary dictionaryWithObjectsAndKeys:
				  Jaiku, JidJaiku,
				  Wassr, JidWassr,
				  Identica, JidIdentica,
//				  Jisko, JidJisko,
				  nil] retain];
		_jid = [[NSDictionary dictionaryWithObjectsAndKeys:
				 JidJaiku, Jaiku,
				 Wassr, JidWassr,
				 JidIdentica, Identica,
//				 Jisko, JidJisko,
				 nil] retain];
		_service = [[NSMutableDictionary alloc] init];
		_xmpp = [[NSMutableDictionary alloc] init];
		
		_mainThread = nil;
		_queuePost = [[NSMutableArray alloc] init];
		_lockQueuePost = [[NSLock alloc] init];
		_lockedPost = NO;
		_lockPost = [[NSLock alloc] init];
		_queueError = [[NSMutableArray alloc] init];
		_lockQueueError = [[NSLock alloc] init];
		_lockedError = NO;
		_lockError = [[NSLock alloc] init];

		_lockXMPP = [[NSLock alloc] init];
		_lockedXMPP = NO;
		_queueXMPP = [[NSMutableArray alloc] init];
		
		[self performSelectorOnMainThread:@selector(getMainThread:)
							   withObject:nil
							waitUntilDone:YES];
		[NSThread detachNewThreadSelector:@selector(threadPost:)
								 toTarget:self
							   withObject:nil];
		[self lockXMPP];
		[NSThread detachNewThreadSelector:@selector(threadReceiveMessage:)
								 toTarget:self
							   withObject:nil];
	}
	return self;
}

- (void)dealloc
{
	if (_service)
	{
		[_service release];
	}
	if (_xmpp)
	{
		[_xmpp release];
	}
	[_queuePost release];
	[_lockQueuePost release];
	[_lockPost release];
	[_queueError release];
	[_lockQueueError release];
	[_lockError release];
	[_lockXMPP release];
	[_queueXMPP release];
	[super dealloc];
}

- (void)setController:(AfficheurController *)controller
{
	_controller = controller;
}

- (void)addService:(NSString *)service
		  withObject:(Service *)object
{
	[_service setObject:object forKey:service];
}

- (void)addXMPP:(NSString *)service
		withJid:(NSString *)jid
		   pass:(NSString *)pass
		 server:(NSString *)server
		   port:(int)port
		offline:(BOOL)offline
	   resource:(NSString *)resource
{
	ServiceXMPPClient *client = nil;
	NSEnumerator *enumerator = [[_xmpp allValues] objectEnumerator];
	ServiceXMPPClient *obj;
	while ((obj = [enumerator nextObject]))
	{
		if (![[obj jid] compare:jid options:NSCaseInsensitiveSearch] &&
			![[obj server] compare:server options:NSCaseInsensitiveSearch] &&
			[[obj pass] isEqualToString:pass] && ([obj port] == port))
		{
			LOG(@"[%@ addXMPP] found:%@", [self className], jid);
			client = obj;
			break;
		}
	}
	if (client == nil)
	{
		[self removeXMPP:service];
		LOG(@"[%@ addXMPP] alloc:%@", [self className], jid);
		client = [[ServiceXMPPClient alloc] initWithJid:jid
												   pass:pass
												 server:server
												   port:port
												offline:offline
											   resource:resource];
		[client setDelegate:self];
		if ([client diag])
		{
			[client connect];
		}
	}
	[_xmpp setObject:client forKey:service];
}

- (void)addXMPP:(NSString *)service
		withJid:(NSString *)jid
		   pass:(NSString *)pass
		 server:(NSString *)server
		   port:(int)port
		offline:(BOOL)offline
{
	[self addXMPP:service withJid:jid pass:pass server:server port:port offline:offline resource:nil];
}

- (void)removeXMPP:(NSString *)service
{
	ServiceXMPPClient *client = [_xmpp valueForKey:service];
	if (client)
	{
		NSArray *keys = [_xmpp allKeysForObject:client];
		if ([keys count] == 1)
		{
			LOG(@"[%@ removeXMPP] disconnect:%@", [self className], service);
			[client disconnect];
			[client release];
		}
		[_xmpp removeObjectForKey:service];
	}
}

- (void)disconnectAll
{
	NSEnumerator *enumerator = [[_xmpp allValues] objectEnumerator];
	ServiceXMPPClient *client;
	while ((client = [enumerator nextObject]))
	{
		[client disconnect];
	}
}

- (void)keepConnections:(NSDate *)date
{
	NSEnumerator *enumerator = [[_xmpp allValues] objectEnumerator];
	id obj;
	while ((obj = [enumerator nextObject]))
	{
		if ([obj isKindOfClass:[ServiceXMPPClient class]])
		{
			[obj keepConnection:date];
		}
	}
}

- (void)performLockPost:(id)object
{
	@synchronized(_lockPost)
	{
		_lockedPost = YES;
		[_lockPost lock];
	}
}

- (void)lockPost
{
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performLockPost:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performLockPost:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)performUnlockPost:(id)object
{
	@synchronized(_lockPost)
	{
		if (_lockedPost)
		{
			_lockedPost = NO;
			[_lockPost unlock];
		}
	}
}

- (void)unlockPost
{
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performUnlockPost:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performUnlockPost:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)threadPost:(id)object
{
	//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		for (;;)
		{
			NSAutoreleasePool *internalPool = [[NSAutoreleasePool alloc] init];
			[_lockPost lock];
			@try
			{
				[_lockQueuePost lock];
				while ([_queuePost count])
				{
					NSArray *array = [_queuePost objectAtIndex:0];
					[_lockQueuePost unlock];
					@try
					{
						NSString *msg = [array objectAtIndex:0];
						NSString *service = [array objectAtIndex:1];
						ServiceXMPPClient *client = [_xmpp valueForKey:service];
						if (client)
						{
							NSString *jid = [_jid valueForKey:service];
							if (jid)
							{
								LOG(@"[%@ post] %@", [self className], jid);
								[client post:msg withJID:jid];
								[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
								Service* svc = [_service valueForKey:service];
								if (svc)
								{
									[svc determinePost:nil];
								}
							}
						}
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@ threadTickTimer] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
					}
					[_lockQueuePost lock];
					[_queuePost removeObjectAtIndex:0];
				}
				[_lockQueuePost unlock];
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@ threadPost] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
			}
			[_lockPost unlock];
			[self lockPost];
			[internalPool release];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ threadPost] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	//[pool release];
	[NSThread exit];
}

- (void)post:(NSString *)msg
 withService:(NSString *)service
{
	@try
	{
		LOG(@"[%@ post] %@", [self className], service);
		[_lockQueuePost lock];
		[_queuePost addObject:[NSArray arrayWithObjects:msg, service, nil]];
		[_lockQueuePost unlock];
		[self unlockPost];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ post] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)performLockError:(id)object
{
	@synchronized(_lockError)
	{
		_lockedError = YES;
		[_lockError lock];
	}
}

- (void)lockError
{
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performLockError:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performLockError:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)performUnlockError:(id)object
{
	@synchronized(_lockError)
	{
		if (_lockedError)
		{
			_lockedError = NO;
			[_lockError unlock];
		}
	}
}

- (void)unlockError
{
	BOOL unlock = NO;
	@synchronized(_lockError)
	{
		if (!_alert)
		{
			_alert = YES;
			unlock = YES;
		}
	}
	if (unlock)
	{
		if ([[NSThread currentThread] isEqual:_mainThread])
		{
			[self performUnlockError:nil];
		}
		else
		{
			[self performSelectorOnMainThread:@selector(performUnlockError:)
								   withObject:nil
								waitUntilDone:YES];
		}
	}
}

- (void)performLockXMPP:(id)object
{
	@synchronized(_lockXMPP)
	{
		//LOG(@"[%@ lockXMPP]", [self className]);
		_lockedXMPP = YES;
		[_lockXMPP lock];
	}
}

- (void)lockXMPP
{
	//LOG(@"[%@ lockXMPP]", [self className]);
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performLockXMPP:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performLockXMPP:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)performUnlockXMPP:(id)object
{
	@synchronized(_lockXMPP)
	{
		if (_lockedXMPP)
		{
			//LOG(@"[%@ unlockXMPP]", [self className]);
			_lockedXMPP = NO;
			[_lockXMPP unlock];
		}
	}
}

- (void)unlockXMPP
{
	//LOG(@"[%@ unlockXMPP]", [self className]);
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performUnlockXMPP:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performUnlockXMPP:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)threadReceiveMessage:(id)object
{
	//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		for (;;)
		{
			NSAutoreleasePool *internalPool = [[NSAutoreleasePool alloc] init];
			[_lockXMPP lock];
			@try
			{
				while ([_queueXMPP count] > 0)
				{
					id object = nil;
					@synchronized(_queueXMPP)
					{
						object = [_queueXMPP objectAtIndex:0];
					}
					if ([object isKindOfClass:[NSDictionary class]])
					{
						//LOG(@"[%@(%@) threadReceiveMessage]\n%d\n%@", [self className], _service, [message retainCount], message);
						@try
						{
							NSXMLElement *message = [object valueForKey:XmppMsg];
							NSString *name = [object valueForKey:XmppName];
							ServiceXMPPClient *xc = [object valueForKey:XmppXlc];
							id service = [_service valueForKey:name];
							LOG(@"[%@ serviceXMPPClient:didReceiveMessage]\nservice = %@", [self className], service);
							if (service && [service respondsToSelector:@selector(serviceXMPP:didReceiveMessage:)])
							{
								[service performSelector:@selector(serviceXMPP:didReceiveMessage:)
											  withObject:xc withObject:message];
							}
						}
						@catch (NSException *exception)
						{
							EXPLOG(@"[%@(%@) threadReceiveMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
						}
					}
					@synchronized(_queueXMPP)
					{
						[_queueXMPP removeObjectAtIndex:0];
					}
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) threadReceiveMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			[_lockXMPP unlock];
			[self lockXMPP];
			[internalPool release];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) threadReceiveMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	//[pool release];
	[NSThread exit];
}

#pragma mark ServiceXMPPClient Delegates

- (void)serviceXMPPClient:(ServiceXMPPClient *)xc
		didReceiveMessage:(NSXMLElement *)message
{
	if (message)
	{
		@try
		{
			//LOG(@"[%@ serviceXMPPClient:didReceiveMessage]\n%@\n%@", [self className], xc, message);
			//LOG(@"[%@ serviceXMPPClient:didReceiveMessage]\n%d\n%@", [self className], [message retainCount], message);
			NSString *fromAndResource = [[message attributeForName:@"from"] stringValue];
			//LOG(@"[%@ serviceXMPPClient:didReceiveMessage]\nfromAndResource = %@", [self className], fromAndResource);
			NSString *from = [[fromAndResource componentsSeparatedByString:@"/"] objectAtIndex:0];
			LOG(@"[%@ serviceXMPPClient:didReceiveMessage]\nfrom = %@", [self className], from);
			NSString *name = [_from valueForKey:from];
			LOG(@"[%@ serviceXMPPClient:didReceiveMessage]\nservice = %@", [self className], name);
			if (name && ![name isKindOfClass:[NSNull class]])
			{
				@synchronized(_queueXMPP)
				{
					[_queueXMPP addObject:[NSDictionary dictionaryWithObjectsAndKeys:
										   message, XmppMsg,
										   name, XmppName,
										   xc, XmppXlc,
										   nil]];
				}
				[self unlockXMPP];
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@ serviceXMPPClient:didReceiveMessage] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		}
		@finally
		{
			[message release];
		}
	}
}

- (void)serviceXMPPClient:(ServiceXMPPClient *)xc
		  didReceiveError:(id)error
{
	//LOG(@"[%@ serviceXMPPClient:didReceiveError]\n%@ = %@\nrootElement = %@", [self className], [error className], error, [[xc xmpp] rootElement]);
	NSString *message = nil;
	if ([error isKindOfClass:[NSError class]])
	{
		message = [NSString stringWithFormat:@"%@",
				   [error localizedDescription]];
	}
	else if ([error isKindOfClass:[NSXMLElement class]])
	{
		message = [NSString stringWithFormat:@"%@",
				   [[[error children] objectAtIndex:0] name]];
	}
	else if (error)
	{
		message = [NSString stringWithFormat:@"%@",
				   error];
	}
	else
	{
		message = @"XMPP Error.";
	}
	if (message)
	{
		[_controller notifyWithTitle:[xc jid]
						 description:message
					notificationName:GrowlAfficheurFailureNotify
							iconData:nil
						clickContext:nil];
	}
	//LOG(@"[%@ serviceXMPPClient:didReceiveError] done", [self className]);
}

@end
