#import "XMPPModule.h"
#import "XMPPStream.h"
#import "XMPPLogging.h"

#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif


@implementation XMPPModule
@synthesize multicastDelegate, moduleQueue, xmppStream, moduleQueueTag;

/**
 * Standard init method.
**/
- (id)init
{
	return [self initWithDispatchQueue:NULL];
}

/**
 * Designated initializer.
**/
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
{
	if ((self = [super init]))
	{
		if (queue)
		{
			moduleQueue = queue;
			#if !OS_OBJECT_USE_OBJC
			dispatch_retain(moduleQueue);
			#endif
		}
		else
		{
			const char *moduleQueueName = [[self moduleName] UTF8String];
			moduleQueue = dispatch_queue_create(moduleQueueName, NULL);
		}
		
		moduleQueueTag = &moduleQueueTag;
		dispatch_queue_set_specific(moduleQueue, moduleQueueTag, moduleQueueTag, NULL);
		
		multicastDelegate = [[GCDMulticastDelegate alloc] init];
	}
	return self;
}

- (void)dealloc
{
	#if !OS_OBJECT_USE_OBJC
	dispatch_release(moduleQueue);
	#endif
}

/**
 * The activate method is the point at which the module gets plugged into the xmpp stream.
 *
 * It is recommended that subclasses override didActivate, instead of this method,
 * to perform any custom actions upon activation.
**/
- (BOOL)activate:(XMPPStream *)aXmppStream
{
	__block BOOL result = YES;
	
	dispatch_block_t block = ^{
		
		if (self->xmppStream != nil)
		{
			result = NO;
		}
		else
		{
			self->xmppStream = aXmppStream;
			
			[self->xmppStream addDelegate:self delegateQueue:self->moduleQueue];
			[self->xmppStream registerModule:self];
			
			[self didActivate];
		}
	};
	
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_sync(moduleQueue, block);
	
	return result;
}

/**
 * It is recommended that subclasses override this method (instead of activate:)
 * to perform tasks after the module has been activated.
 * 
 * This method is only invoked if the module is successfully activated.
 * This method is always invoked on the moduleQueue.
**/
- (void)didActivate
{
	// Override me to do custom work after the module is activated
}

/**
 * The deactivate method unplugs a module from the xmpp stream.
 * When this method returns, no further delegate methods on this module will be dispatched.
 * However, there may be delegate methods that have already been dispatched.
 * If this is the case, the module will be properly retained until the delegate methods have completed.
 * If your custom module requires that delegate methods are not run after the deactivate method has been run,
 * then simply check the xmppStream variable in your delegate methods.
 *
 * It is recommended that subclasses override didDeactivate, instead of this method,
 * to perform any custom actions upon deactivation.
**/
- (void)deactivate
{
	dispatch_block_t block = ^{
		
		if (self->xmppStream)
		{
			[self willDeactivate];
			
			[self->xmppStream removeDelegate:self delegateQueue:self->moduleQueue];
			[self->xmppStream unregisterModule:self];
			
			self->xmppStream = nil;
		}
	};
	
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_sync(moduleQueue, block);
}

/**
 * It is recommended that subclasses override this method (instead of deactivate:)
 * to perform tasks after the module has been deactivated.
 *
 * This method is only invoked if the module is transitioning from activated to deactivated.
 * This method is always invoked on the moduleQueue.
**/
- (void)willDeactivate
{
	// Override me to do custom work after the module is deactivated
}

- (dispatch_queue_t)moduleQueue
{
	return moduleQueue;
}

- (void *)moduleQueueTag
{
	return moduleQueueTag;
}

- (XMPPStream *)xmppStream
{
	if (dispatch_get_specific(moduleQueueTag))
	{
		return xmppStream;
	}
	else
	{
		__block XMPPStream *result;
		
		dispatch_sync(moduleQueue, ^{
			result = self->xmppStream;
		});
		
		return result;
	}
}

- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
	// Asynchronous operation (if outside xmppQueue)
	
	dispatch_block_t block = ^{
		[self->multicastDelegate addDelegate:delegate delegateQueue:delegateQueue];
	};
	
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_async(moduleQueue, block);
}

- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue synchronously:(BOOL)synchronously
{
	dispatch_block_t block = ^{
		[self->multicastDelegate removeDelegate:delegate delegateQueue:delegateQueue];
	};
	
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else if (synchronously)
		dispatch_sync(moduleQueue, block);
	else
		dispatch_async(moduleQueue, block);
	
}
- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
	// Synchronous operation (common-case default)
	
	[self removeDelegate:delegate delegateQueue:delegateQueue synchronously:YES];
}

- (void)removeDelegate:(id)delegate
{
	// Synchronous operation (common-case default)
	
	[self removeDelegate:delegate delegateQueue:NULL synchronously:YES];
}

- (NSString *)moduleName
{
	// Override me (if needed) to provide a customized module name.
	// This name is used as the name of the dispatch_queue which could aid in debugging.
	
	return NSStringFromClass([self class]);
}

@end

@implementation XMPPModule(Synchronization)
/** Executes block synchronously on moduleQueue */
- (void) performBlock:(dispatch_block_t)block {
    NSParameterAssert(block);
    if (!block) { return; }
    if (dispatch_get_specific(moduleQueueTag))
        block();
    else
        dispatch_sync(moduleQueue, block);
}

/** Executes block asynchronously on moduleQueue */
- (void) performBlockAsync:(dispatch_block_t)block {
    NSParameterAssert(block);
    if (!block) { return; }
    if (dispatch_get_specific(moduleQueueTag))
        block();
    else
        dispatch_async(moduleQueue, block);
}
@end
