#import "XMPPMessageCarbons.h"
#import "XMPP.h"
#import "XMPPFramework.h"
#import "XMPPLogging.h"
#import "XMPPIDTracker.h"
#import "NSXMLElement+XEP_0297.h"
#import "XMPPMessage+XEP_0280.h"
#import "XMPPInternal.h"

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

// Log levels: off, error, warn, info, verbose
// Log flags: trace
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_VERBOSE; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif


#define XMLNS_XMPP_MESSAGE_CARBONS @"urn:xmpp:carbons:2"

@implementation XMPPMessageCarbons

- (id)initWithDispatchQueue:(dispatch_queue_t)queue
{
    if((self = [super initWithDispatchQueue:queue]))
    {
        autoEnableMessageCarbons = YES;
    }
    return self;
}

- (BOOL)activate:(XMPPStream *)aXmppStream
{
	XMPPLogTrace();
	
	if ([super activate:aXmppStream])
	{
		XMPPLogVerbose(@"%@: Activated", THIS_FILE);
		
        xmppIDTracker = [[XMPPIDTracker alloc] initWithDispatchQueue:moduleQueue];
        
		return YES;
	}
	
	return NO;
}

- (void)deactivate
{
	XMPPLogTrace();
    
    dispatch_block_t block = ^{ @autoreleasepool {
        
		[xmppIDTracker removeAllIDs];
		xmppIDTracker = nil;
        
	}};
    
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_sync(moduleQueue, block);
    
	[super deactivate];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Properties
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (BOOL)autoEnableMessageCarbons
{
	__block BOOL result = NO;
	
	dispatch_block_t block = ^{
		result = autoEnableMessageCarbons;
	};
	
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_sync(moduleQueue, block);
	
	return result;
}

- (void)setAutoEnableMessageCarbons:(BOOL)flag
{
	dispatch_block_t block = ^{
		autoEnableMessageCarbons = flag;
	};
	
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_async(moduleQueue, block);
}

- (BOOL)isMessageCarbonsEnabled
{
    __block BOOL result = NO;
	
	dispatch_block_t block = ^{
		result = messageCarbonsEnabled;
	};
	
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_sync(moduleQueue, block);
	
	return result;
}

- (BOOL)allowsUntrustedMessageCarbons
{
	__block BOOL result = NO;
	
	dispatch_block_t block = ^{
		result = allowsUntrustedMessageCarbons;
	};
	
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_sync(moduleQueue, block);
	
	return result;
}

- (void)setAllowsUntrustedMessageCarbons:(BOOL)flag
{
	dispatch_block_t block = ^{
		allowsUntrustedMessageCarbons = flag;
	};
	
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_async(moduleQueue, block);
}

- (void)enableMessageCarbons
{
    dispatch_block_t block = ^{
        
        if(!messageCarbonsEnabled && [xmppIDTracker numberOfIDs] == 0)
        {
            NSString *elementID = [XMPPStream generateUUID];
            XMPPIQ *iq = [XMPPIQ iqWithType:@"set" elementID:elementID];
            [iq setXmlns:@"jabber:client"];
            
            NSXMLElement *enable = [NSXMLElement elementWithName:@"enable" xmlns:XMLNS_XMPP_MESSAGE_CARBONS];
            [iq addChild:enable];
            
            [xmppIDTracker addElement:iq
                               target:self
                             selector:@selector(enableMessageCarbonsIQ:withInfo:)
                              timeout:XMPPIDTrackerTimeoutNone];
            
            [xmppStream sendElement:iq];
        }
    };
    
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_sync(moduleQueue, block);
}

- (void)disableMessageCarbons
{
    dispatch_block_t block = ^{
        
        if(messageCarbonsEnabled && [xmppIDTracker numberOfIDs] == 0)
        {
            NSString *elementID = [XMPPStream generateUUID];
            
            XMPPIQ *iq = [XMPPIQ iqWithType:@"set" elementID:elementID];
            [iq setXmlns:@"jabber:client"];
            
            NSXMLElement *enable = [NSXMLElement elementWithName:@"disable" xmlns:XMLNS_XMPP_MESSAGE_CARBONS];
            [iq addChild:enable];
            
            [xmppIDTracker addElement:iq
                               target:self
                             selector:@selector(disableMessageCarbonsIQ:withInfo:)
                              timeout:XMPPIDTrackerTimeoutNone];
            
            [xmppStream sendElement:iq];
        }
    };
    
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_sync(moduleQueue, block);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XMPPStream Delegate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
	XMPPLogTrace();
        
    messageCarbonsEnabled = NO;

    if(self.autoEnableMessageCarbons)
    {
        [self enableMessageCarbons];
    }
}

- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
{
    messageCarbonsEnabled = NO;
}

- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
{
    [xmppIDTracker invokeForID:[iq elementID] withObject:iq];

	return NO;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XMPPStream Delegate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)enableMessageCarbonsIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)trackerInfo
{
    XMPPLogTrace();
    
    if([iq isResultIQ])
    {
        messageCarbonsEnabled = YES;
    }
}

- (void)disableMessageCarbonsIQ:(XMPPIQ *)iq withInfo:(XMPPBasicTrackingInfo *)trackerInfo
{
    XMPPLogTrace();
    
    if([iq isResultIQ])
    {
        messageCarbonsEnabled = NO;
    }
}

- (XMPPMessage *)xmppStream:(XMPPStream *)sender willReceiveMessage:(XMPPMessage *)message
{
	XMPPLogTrace();
	
    if([message isTrustedMessageCarbonForMyJID:sender.myJID] ||
       ([message isMessageCarbon] && allowsUntrustedMessageCarbons))
    {
        BOOL outgoing = [message isSentMessageCarbon];
        
        XMPPMessage *messageCarbonForwardedMessage = [message messageCarbonForwardedMessage];
        
        [multicastDelegate xmppMessageCarbons:self
                           willReceiveMessage:messageCarbonForwardedMessage
                                     outgoing:outgoing];
    }
    
    return message;
}

- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
	XMPPLogTrace();
	
    if([message isTrustedMessageCarbonForMyJID:sender.myJID] ||
       ([message isMessageCarbon] && allowsUntrustedMessageCarbons))
    {
        BOOL outgoing = [message isSentMessageCarbon];
        
        XMPPMessage *messageCarbonForwardedMessage = [message messageCarbonForwardedMessage];
                
        [multicastDelegate xmppMessageCarbons:self
                            didReceiveMessage:messageCarbonForwardedMessage
                                     outgoing:outgoing];
    }
}


@end
