AtChat/Pods/XMPPFramework/Extensions/XEP-0153/XMPPvCardAvatarModule.m
//
//  XMPPvCardAvatarModule.h
//  XEP-0153 vCard-Based Avatars
//
//  Created by Eric Chamberlain on 3/9/11.
//  Copyright 2011 RF.com. All rights reserved.

#import "XMPPvCardAvatarModule.h"

#import "NSData+XMPP.h"
#import "NSXMLElement+XMPP.h"
#import "XMPPLogging.h"
#import "XMPPPresence.h"
#import "XMPPStream.h"
#import "XMPPvCardTempModule.h"
#import "XMPPvCardTemp.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_WARN; // | XMPP_LOG_FLAG_TRACE;
#else
  static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif

NSString *const kXMPPvCardAvatarElement = @"x";
NSString *const kXMPPvCardAvatarNS = @"vcard-temp:x:update";
NSString *const kXMPPvCardAvatarPhotoElement = @"photo";


@implementation XMPPvCardAvatarModule

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Init/dealloc
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (id)init
{
	// This will cause a crash - it's designed to.
	// Only the init methods listed in XMPPvCardAvatarModule.h are supported.
	
	return [self initWithvCardTempModule:nil dispatchQueue:NULL];
}

- (id)initWithDispatchQueue:(dispatch_queue_t)queue
{
	// This will cause a crash - it's designed to.
	// Only the init methods listed in XMPPvCardAvatarModule.h are supported.
	
	return [self initWithvCardTempModule:nil dispatchQueue:NULL];
}

- (id)initWithvCardTempModule:(XMPPvCardTempModule *)xmppvCardTempModule
{
  return [self initWithvCardTempModule:xmppvCardTempModule dispatchQueue:NULL];
}

- (id)initWithvCardTempModule:(XMPPvCardTempModule *)xmppvCardTempModule dispatchQueue:(dispatch_queue_t)queue
{
	NSParameterAssert(xmppvCardTempModule != nil);

	if ((self = [super initWithDispatchQueue:queue])) {
		_xmppvCardTempModule = xmppvCardTempModule;

		// we don't need to call the storage configureWithParent:queue: method,
		// because the vCardTempModule already did that.
		_moduleStorage = (id <XMPPvCardAvatarStorage>)xmppvCardTempModule.xmppvCardTempModuleStorage;

		[_xmppvCardTempModule addDelegate:self delegateQueue:moduleQueue];
		
		_autoClearMyvcard = YES;
	}
	return self;
}


- (void)dealloc {
	[_xmppvCardTempModule removeDelegate:self];

	_moduleStorage = nil;
}

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


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

- (void)setAutoClearMyvcard:(BOOL)flag
{
	dispatch_block_t block = ^{
		_autoClearMyvcard = flag;
	};
	
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_async(moduleQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Public
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (NSData *)photoDataForJID:(XMPPJID *)jid 
{
	// This is a public method, so it may be invoked on any thread/queue.
	// 
	// The vCardTempModule is thread safe.
	// The moduleStorage should be thread safe. (User may be using custom module storage class).
	// The multicastDelegate is NOT thread safe.
	
	__block NSData *photoData;
	
	dispatch_block_t block = ^{ @autoreleasepool {
		
		photoData = [_moduleStorage photoDataForJID:jid xmppStream:xmppStream];
		
		if (photoData == nil) 
		{
			[_xmppvCardTempModule vCardTempForJID:jid shouldFetch:YES];
		}
		
	}};
	
	if (dispatch_get_specific(moduleQueueTag))
		block();
	else
		dispatch_sync(moduleQueue, block);
	
	return photoData;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XMPPStreamDelegate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)xmppStreamDidConnect:(XMPPStream *)sender {
	XMPPLogTrace();
	
	if(self.autoClearMyvcard)
	{
		/*
		 * XEP-0153 Section 4.2 rule 1
		 *
		 * A client MUST NOT advertise an avatar image without first downloading the current vCard. 
		 * Once it has done this, it MAY advertise an image. 
		 */
		[_moduleStorage clearvCardTempForJID:[sender myJID] xmppStream:sender];
	}
}


- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
	XMPPLogTrace();
	[_xmppvCardTempModule fetchvCardTempForJID:[sender myJID] ignoreStorage:YES];
}


- (XMPPPresence *)xmppStream:(XMPPStream *)sender willSendPresence:(XMPPPresence *)presence {
	XMPPLogTrace();
    
	NSXMLElement *currentXElement = [presence elementForName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS];
	
	//If there is already a x element then remove it
	if(currentXElement)
	{
	    NSUInteger currentXElementIndex = [[presence children] indexOfObject:currentXElement];
	    
	    if(currentXElementIndex != NSNotFound)
	    {
	        [presence removeChildAtIndex:currentXElementIndex];
	    }
	}
	// add our photo info to the presence stanza
	NSXMLElement *photoElement = nil;
	NSXMLElement *xElement = [NSXMLElement elementWithName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS];

	NSString *photoHash = [_moduleStorage photoHashForJID:[sender myJID] xmppStream:sender];

	if (photoHash != nil)
    {
	    photoElement = [NSXMLElement elementWithName:kXMPPvCardAvatarPhotoElement stringValue:photoHash];
	} else {
	    photoElement = [NSXMLElement elementWithName:kXMPPvCardAvatarPhotoElement];
	}

	[xElement addChild:photoElement];
	[presence addChild:xElement];
    
	// Question: If photoElement is nil, should we be adding xElement?
	
	return presence;
}


- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence  {
	XMPPLogTrace();

	NSXMLElement *xElement = [presence elementForName:kXMPPvCardAvatarElement xmlns:kXMPPvCardAvatarNS];

	if (xElement == nil) {
		return;
	}

	NSXMLElement *photoElement = [xElement elementForName:kXMPPvCardAvatarPhotoElement];

	if (photoElement == nil) {
		return;
	}
    
    NSString *photoHash = [photoElement stringValue];

	XMPPJID *jid = [presence from];
    
    NSString *savedPhotoHash = [_moduleStorage photoHashForJID:jid xmppStream:xmppStream];

	// check the hash
    if ([photoHash caseInsensitiveCompare:savedPhotoHash] != NSOrderedSame
        && !([photoHash length] == 0 && [savedPhotoHash length] == 0)) {
		[_xmppvCardTempModule fetchvCardTempForJID:jid ignoreStorage:YES];
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XMPPvCardTempModuleDelegate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule 
        didReceivevCardTemp:(XMPPvCardTemp *)vCardTemp 
                     forJID:(XMPPJID *)jid
{
	XMPPLogTrace();
	
	if (vCardTemp.photo != nil)
	{
	#if TARGET_OS_IPHONE
		UIImage *photo = [UIImage imageWithData:vCardTemp.photo];
	#else
		NSImage *photo = [[NSImage alloc] initWithData:vCardTemp.photo];
	#endif
		
		if (photo != nil)
		{
			[multicastDelegate xmppvCardAvatarModule:self
			                         didReceivePhoto:photo
			                                  forJID:jid];
		}
	}
	
	/*
	 * XEP-0153 4.1.3
	 * If the client subsequently obtains an avatar image (e.g., by updating or retrieving the vCard), 
	 * it SHOULD then publish a new <presence/> stanza with character data in the <photo/> element.
	 */
    
	if ([[xmppStream myJID] isEqualToJID:jid options:XMPPJIDCompareBare])
	{
		XMPPPresence *presence = xmppStream.myPresence;
        
        if(presence)
        {
            [xmppStream sendElement:presence];
        }


	}
}

- (void)xmppvCardTempModuleDidUpdateMyvCard:(XMPPvCardTempModule *)vCardTempModule{
    //The vCard has been updated on the server so we need to cache it
    [_xmppvCardTempModule fetchvCardTempForJID:[xmppStream myJID] ignoreStorage:NO];
}

- (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule failedToUpdateMyvCard:(NSXMLElement *)error{
		//The vCard failed to update so we fetch the current one from the server
    [_xmppvCardTempModule fetchvCardTempForJID:[xmppStream myJID] ignoreStorage:YES];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Getter/setter
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@synthesize xmppvCardTempModule = _xmppvCardTempModule;


@end
