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

#import "ServiceXMPPClient.h"
#import "NSStringOgreKit.h"


@implementation ServiceXMPPClient

#define	KEEP_PRESENCE	180

- (ServiceXMPPClient *)initWithJid:(NSString *)jid
							  pass:(NSString *)pass
							server:(NSString *)server
							  port:(int)port
						   offline:(BOOL)offline
						  resource:(NSString *)resource
{
	_delegate = nil;
	_jid = [jid copy];
	_pass = [pass copy];
	_server = [server copy];
	_port = port;
	_offline = offline;
	if (resource)
	{
		_resource = [resource copy];
	}
	else
	{
		_resource = @"Afficheur";
	}
	_connecting = NO;
	_authenticating = NO;
	_xmpp = [[XMPPStream alloc] init];
	if (_xmpp)
	{
		[_xmpp setDelegate:self];
		[_xmpp setAllowsSelfSignedCertificates:NO];
		[_xmpp setAllowsSSLHostNameMismatch:YES];
		[self connect];
	}
	_keepDate = nil;
	return self;
}

- (ServiceXMPPClient *)initWithJid:(NSString *)jid
							  pass:(NSString *)pass
							server:(NSString *)server
							  port:(int)port
						   offline:(BOOL)offline
{
	return [self initWithJid:jid
						pass:pass
					  server:server
						port:port
					 offline:offline
					resource:@"Afficheur"];
}

- (void)dealloc
{
	[_jid release];
	[_pass release];
	[_server release];
	[_resource release];
	if (_xmpp)
	{
		if ([_xmpp isConnected])
		{
			[self disconnect];
		}
		[_xmpp release];
	}
	[_keepDate release];
	[super dealloc];
}

- (void)setDelegate:(id)delegate
{
	_delegate = delegate;
}

- (XMPPStream *)xmpp
{
	return _xmpp;
}

- (NSString *)jid
{
	return _jid;
}

- (NSString *)pass
{
	return _pass;
}

- (NSString *)server
{
	return _server;
}

- (int)port
{
	return _port;
}

- (void)online
{
	NSXMLElement *presence = [NSXMLElement elementWithName:@"presence"];
	//NSXMLElement *priority = [NSXMLElement elementWithName:@"priority"];
	//[priority setStringValue:@"0"];
	//[presence addChild:priority];
	[_xmpp sendElement:presence];
}

- (void)offline
{
	NSXMLElement *presence = [NSXMLElement elementWithName:@"presence"];
	[presence addAttribute:[NSXMLNode attributeWithName:@"type" stringValue:@"unavailable"]];
	[_xmpp sendElement:presence];
}

- (void)connect
{
	LOG(@"[%@(%@) connect]", [self className], _jid);
	@try
	{
		_authenticating = NO;
		_connecting = YES;
		NSString *vHost = [[_jid componentsSeparatedByString:@"@"] objectAtIndex:1];
		LOG(@"[%@(%@) connect] %@", [self className], _jid, vHost);
		[_xmpp connectToHost:_server
					  onPort:_port
			 withVirtualHost:vHost];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) connect] EXCEPTION\n%@: %@", [self className], _jid, [exception name], [exception reason]);
	}
}

- (void)disconnect
{
	LOG(@"[%@(%@) disconnect]", [self className], _jid);
	[_xmpp setDelegate:nil];
	[self offline];
	[_xmpp disconnect];
}

- (void)authenticate
{
	@try
	{
		_connecting = NO;
		_authenticating = YES;
		NSString *user = [[_jid componentsSeparatedByString:@"@"] objectAtIndex:0];
		[_xmpp authenticateUser:user
				   withPassword:_pass
					   resource:_resource];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) authenticate] EXCEPTION\n%@: %@", [self className], _jid, [exception name], [exception reason]);
	}
}

- (BOOL)diag
{
	BOOL result = NO;
	@try
	{
		NSHost *host = [NSHost hostWithName:_server];
		NSInputStream *iStr;
		NSOutputStream *oStr;
		[NSStream getStreamsToHost:host port:_port inputStream:&iStr outputStream:&oStr];
		CFNetDiagnosticRef diag = CFNetDiagnosticCreateWithStreams(NULL, (CFReadStreamRef)iStr, (CFWriteStreamRef)oStr);
		CFNetDiagnosticStatus status = CFNetDiagnosticCopyNetworkStatusPassively(diag, NULL);
		CFRelease(diag);
		if (status == kCFNetDiagnosticConnectionUp)
		{
			result = YES;
			//LOG(@"[%@ diag] UP", [self className]);
		}
		else
		{
			//LOG(@"[%@ diag] DOWN", [self className]);
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) diag] EXCEPTION\n%@: %@", [self className], _jid, [exception name], [exception reason]);
	}
	return result;
}

- (void)keepConnection:(NSDate *)date
{
	@try
	{
		if (![_xmpp isConnected] && !_connecting && !_authenticating)
		{
			//LOG(@"[%@ keepConnection] diag", [self className]);
			if ([self diag])
			{
				LOG(@"[%@ keepConnection] connect", [self className]);
				[self performSelectorOnMainThread:@selector(connect)
									   withObject:nil
									waitUntilDone:NO];
			}
		}
		else
		{
			@synchronized(_keepDate)
			{
				if (_keepDate)
				{
					//LOG(@"[%@ keepConnection] %d", [self className], [date retainCount]);
					if ([date timeIntervalSinceDate:_keepDate] > KEEP_PRESENCE)
					{
						LOG(@"[%@(%@) keepConnection] presence", [self className], _jid);
						[self performSelectorOnMainThread:@selector(online)
											   withObject:nil
											waitUntilDone:NO];
						[_keepDate release];
						_keepDate = nil;
					}
				}
				if (_keepDate == nil)
				{
					_keepDate = [date copy];
				}
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) keepConnection] EXCEPTION\n%@: %@", [self className], _jid, [exception name], [exception reason]);
	}
}

- (void)post:(NSString *)msg
	 withJID:(NSString *)jid
{
	@try
	{
		LOG(@"[%@(%@) post]\n%@", [self className], _jid, msg);
		NSXMLElement *body = [NSXMLElement elementWithName:@"body"];
		[body setStringValue:msg];
		
		NSXMLElement *message = [NSXMLElement elementWithName:@"message"];
		[message addAttribute:[NSXMLNode attributeWithName:@"type" stringValue:@"chat"]];
		[message addAttribute:[NSXMLNode attributeWithName:@"to" stringValue:jid]];
		[message addChild:body];
		
		LOG(@"[%@(%@) post]\n%@", [self className], _jid, message);
		[_xmpp sendElement:message];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) post] EXCEPTION\n%@: %@", [self className], _jid, [exception name], [exception reason]);
	}
}

#pragma mark XMPPStream Delegates
/**
 * This method is called after an XML stream has been opened.
 * More precisely, this method is called after an opening <xml/> and <stream:stream/> tag have been sent and received,
 * and after the stream features have been received, and any required features have been fullfilled.
 * At this point it's safe to begin communication with the server.
 **/
- (void)xmppStreamDidOpen:(XMPPStream *)xs
{
	//LOG(@"[%@(%@) xmppStreamDidOpen]\n%@", [self className], _jid, xs);
	LOG(@"[%@(%@) xmppStreamDidOpen]", [self className], _jid);
	[self authenticate];
}

/**
 * This method is called after registration of a new user has successfully finished.
 * If registration fails for some reason, the xmppStream:didReceiveError: method will be called instead.
 **/
- (void)xmppStreamDidRegister:(XMPPStream *)xs
{
	//LOG(@"[%@(%@) xmppStreamDidRegister]\n%@", [self className], _jid, xs);
	LOG(@"[%@(%@) xmppStreamDidRegister]", [self className], _jid);
}

/**
 * This method is called after authentication has successfully finished.
 * If authentication fails for some reason, the xmppStream:didReceiveError: method will be called instead.
 **/
- (void)xmppStreamDidAuthenticate:(XMPPStream *)xs
{
	//LOG(@"[%@(%@) xmppStreamDidAuthenticate]\n%@", [self className], _jid, xs);
	//LOG(@"[%@(%@) xmppStreamDidAuthenticate]", [self className], _jid);
	_authenticating = NO;
	if (_offline)
	{
		NSXMLElement *query = [NSXMLElement elementWithName:@"query"];
		[query addAttribute:[NSXMLNode attributeWithName:@"xmlns" stringValue:@"jabber:iq:roster"]];
		NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"];
		[iq addAttribute:[NSXMLNode attributeWithName:@"type" stringValue:@"get"]];
		[iq addChild:query];
		[_xmpp sendElement:iq];
	}
	else
	{
		[self online];
	}
}

/**
 * These methods are called after their respective XML elements are received on the stream.
 **/
- (void)xmppStream:(XMPPStream *)xs didReceiveIQ:(NSXMLElement *)iq
{
	LOG(@"[%@(%@) xmppStream:didReceiveIQ]\n%@", [self className], _jid, iq);
	//LOG(@"[%@(%@) xmppStream:didReceiveIQ]", [self className], jid);
	[self online];
}

- (void)xmppStream:(XMPPStream *)xs didReceiveMessage:(NSXMLElement *)message
{
	//LOG(@"[%@(%@) xmppStream:didReceiveMessage]\n%@\n%@", [self className], _jid, xs, message);
	LOG(@"[%@(%@) xmppStream:didReceiveMessage]", [self className], _jid);
	if (_delegate && [_delegate respondsToSelector:@selector(serviceXMPPClient:didReceiveMessage:)])
	{
		[_delegate performSelector:@selector(serviceXMPPClient:didReceiveMessage:)
						withObject:self
						withObject:[message retain]];
	}
	@synchronized(_keepDate)
	{
		if (_keepDate)
		{
			[_keepDate release];
			_keepDate = [[[NSDate alloc] initWithTimeIntervalSinceNow:0] retain];
		}
	}
}

- (void)xmppStream:(XMPPStream *)xs didReceivePresence:(NSXMLElement *)presence
{
	LOG(@"[%@(%@) xmppStream:didReceivePresence]\n%@\n%@", [self className], _jid, xs, presence);
	//LOG(@"[%@(%@) xmppStream:didReceivePresence]", [self className], _jid);
}

/**
 * There are two types of errors: TCP errors and XMPP errors.
 * If a TCP error is encountered (failure to connect, broken connection, etc) a standard NSError object is passed.
 * If an XMPP error is encountered (<stream:error> for example) an NSXMLElement object is passed.
 * 
 * Note that standard errors (<iq type='error'/> for example) are delivered normally,
 * via the other didReceive...: methods.
 **/
- (void)xmppStream:(XMPPStream *)xs didReceiveError:(id)error
{
	LOG(@"[%@(%@) xmppStream:didReceiveError]", [self className], _jid);
	//LOG(@"[%@(%@) xmppStream:didReceiveError]\n%@\n%@ = %@\nrootElement = %@", [self className], _jid, xs, [error className], error, [xs rootElement]);
	//LOG(@"[%@(%@) xmppStream:didReceiveError]\ndelegate = %@", [self className], _jid, _delegate);
	if (_delegate && [_delegate respondsToSelector:@selector(serviceXMPPClient:didReceiveError:)])
	{
		//LOG(@"[%@(%@) xmppStream:didReceiveError] perform", [self className], _jid);
		[_delegate performSelector:@selector(serviceXMPPClient:didReceiveError:)
						withObject:self
						withObject:error];
	}
}

/**
 * This method is called after the stream is closed.
 **/
- (void)xmppStreamDidClose:(XMPPStream *)xs
{
	//LOG(@"[%@(%@) xmppStreamDidClose]\n%@", [self className], _jid, xs);
	LOG(@"[%@(%@) xmppStreamDidClose]", [self className], _jid);
	_connecting = NO;
}

@end
