#import <Foundation/Foundation.h>
#import "XMPP.h"

#define _XMPP_CAPABILITIES_H

@protocol XMPPCapabilitiesStorage;
@class GCDTimerWrapper;

/**
 * This class provides support for capabilities discovery.
 * 
 * It collects our capabilities and publishes them according to the XEP by:
 * - Injecting the <c/> element into outgoing presence stanzas
 * - Responding to incoming disco#info queries
 * 
 * It also collects the capabilities of available resources,
 * provides a mechanism to persistently store XEP-0115 hased caps,
 * and makes available a simple API to query (disco#info) a resource or server.
**/
@interface XMPPCapabilities : XMPPModule
{
	__strong id <XMPPCapabilitiesStorage> xmppCapabilitiesStorage;
    
    NSString *myCapabilitiesNode;
	
	NSXMLElement *myCapabilitiesQuery; // Full list of capabilites <query/>
	NSXMLElement *myCapabilitiesC;     // Hashed element <c/>
	BOOL collectingMyCapabilities;
	
	NSMutableSet *discoRequestJidSet;
	NSMutableDictionary *discoRequestHashDict;
	NSMutableDictionary<XMPPJID*,GCDTimerWrapper*> *discoTimerJidDict;
	
	BOOL autoFetchHashedCapabilities;
	BOOL autoFetchNonHashedCapabilities;
	BOOL autoFetchMyServerCapabilities;
	
	NSTimeInterval capabilitiesRequestTimeout;
	
	NSMutableSet *timers;
}

- (id)initWithCapabilitiesStorage:(id <XMPPCapabilitiesStorage>)storage;
- (id)initWithCapabilitiesStorage:(id <XMPPCapabilitiesStorage>)storage dispatchQueue:(dispatch_queue_t)queue;

@property (nonatomic, strong, readonly) id <XMPPCapabilitiesStorage> xmppCapabilitiesStorage;

/**
 * Defines the node attribute in a <c/> element qualified by the 'http://jabber.org/protocol/caps' namespace.
 *
 * It is RECOMMENDED for the value of the 'node' attribute to be an HTTP URL
 * at which a user could find further information about the software product, 
 * such as "http://github.com/robbiehanson/XMPPFramework"
 *
 * This MUST NOT be nil
 *
 * The default value is http://github.com/robbiehanson/XMPPFramework
**/

@property (nonatomic, copy) NSString *myCapabilitiesNode;

/**
 * Defines fetching behavior for entities using the XEP-0115 standard.
 * 
 * XEP-0115 defines a technique for hashing capabilities (disco info responses),
 * and broadcasting them within a presence element.
 * Due to the standardized hashing technique, capabilities associated with a hash may be persisted indefinitely.
 * 
 * The end result is that capabilities need to be fetched less often
 * since they are already known due to the caching of responses.
 * 
 * The default value is YES.
**/
@property (assign) BOOL autoFetchHashedCapabilities;

/**
 * Defines fetching behavior for entities NOT using the XEP-0115 standard.
 * 
 * Because the capabilities are not associated with a standardized hash,
 * it is not possible to cache the capabilities between sessions.
 * 
 * The default value is NO.
 * 
 * It is recommended you leave this value set to NO unless you
 * know that you'll need the capabilities of every resource,
 * and that fetching of the capabilities cannot be delayed.
 * 
 * You may always fetch the capabilities (if/when needed) via the fetchCapabilitiesForJID method.
**/
@property (assign) BOOL autoFetchNonHashedCapabilities;

/**
 * Auto fetch the capabilities of the server upon authentication.
 * This uses the non hashed approach outlined in XEP-0030: Service Discovery.
 *
 * The default value is NO.
**/

@property (assign) BOOL autoFetchMyServerCapabilities;

/**
 * Manually fetch the capabilities for the given jid.
 * 
 * The jid must be a full jid (user@domain/resource) or a domain JID (domain without user or resource).
 * You would pass a full jid if you wanted to know the capabilities of a particular user's resource.
 * You would pass a domain jid if you wanted to know the capabilities of a particular server.
 * 
 * If there is an existing disco request associated with the given jid, this method does nothing.
 * 
 * When the capabilities are received,
 * the xmppCapabilities:didDiscoverCapabilities:forJID: delegate method is invoked.
**/
- (void)fetchCapabilitiesForJID:(XMPPJID *)jid;

/**
 * This module automatically collects my capabilities.
 * See the xmppCapabilities:collectingMyCapabilities: delegate method.
 * 
 * The design of XEP-115 is such that capabilites are expected to remain rather static.
 * However, if the capabilities change, this method may be used to perform a manual update.
**/
- (void)recollectMyCapabilities;

@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@protocol XMPPCapabilitiesStorage <NSObject>
@required

// 
// 
// -- PUBLIC METHODS --
// 
// 

/**
 * Returns whether or not we know the capabilities for a given jid.
 * 
 * The stream parameter is optional.
 * If given, the jid must have been registered via the given stream.
 * Otherwise it will match the given jid from any stream this storage instance is managing.
**/
- (BOOL)areCapabilitiesKnownForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream;

/**
 * Returns the capabilities for the given jid.
 * The returned element is the <query/> element response to a disco#info request.
 * 
 * The stream parameter is optional.
 * If given, the jid must have been registered via the given stream.
 * Otherwise it will match the given jid from any stream this storage instance is managing.
**/
- (NSXMLElement *)capabilitiesForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream;

/**
 * Returns the capabilities for the given jid.
 * The returned element is the <query/> element response to a disco#info request.
 * 
 * The given jid should be a full jid (user@domain/resource) or a domin JID (domain without user or resource).
 * 
 * If the jid has broadcast capabilities via the legacy format of XEP-0115,
 * the extension list may optionally be retrieved via the ext parameter.
 * 
 * For example, the jid may send a presence element like:
 * 
 * <presence from="jid">
 *   <c node="imclient.com/caps" ver="1.2" ext="rdserver rdclient avcap"/>
 * </presence>
 * 
 * In the above example, the ext string would be set to "rdserver rdclient avcap".
 * 
 * You may pass nil for extPtr if you don't care about the legacy attributes,
 * or you could simply use the capabilitiesForJID: method above.
 * 
 * The stream parameter is optional.
 * If given, the jid must have been registered via the given stream.
 * Otherwise it will match the given jid from any stream this storage instance is managing.
**/
- (NSXMLElement *)capabilitiesForJID:(XMPPJID *)jid ext:(NSString **)extPtr xmppStream:(XMPPStream *)stream;

// 
// 
// -- PRIVATE METHODS --
// 
// These methods are designed to be used ONLY by the XMPPCapabilities class.
// 
// 

/**
 * Configures the capabilities storage class, passing it's parent and parent's dispatch queue.
 * 
 * This method is called by the init methods of the XMPPCapabilities class.
 * This method is designed to inform the storage class of it's parent
 * and of the dispatch queue the parent will be operating on.
 * 
 * A storage class may choose to operate on the same queue as it's parent,
 * as the majority of the time it will be getting called by the parent.
 * If both are operating on the same queue, the combination may run faster.
 * 
 * Some storage classes support multiple xmppStreams,
 * and may choose to operate on their own internal queue.
 * 
 * This method should return YES if it was configured properly.
 * It should return NO only if configuration failed.
 * For example, a storage class designed to be used only with a single xmppStream is being added to a second stream.
 * The XMPPCapabilites class is configured to ignore the passed
 * storage class in it's init method if this method returns NO.
**/
- (BOOL)configureWithParent:(XMPPCapabilities *)aParent queue:(dispatch_queue_t)queue;

/**
 * Sets metadata for the given jid.
 * 
 * This method should return:
 * - YES if the capabilities for the given jid are known.
 * - NO if the capabilities for the given jid are NOT known.
 * 
 * If the hash and algorithm are given, and an associated set of capabilities matches the hash/algorithm,
 * this method should link the jid to the capabilities and return YES.
 * 
 * If the linked set of capabilities was not previously linked to the jid,
 * the newCapabilities parameter shoud be filled out.
 * 
 * This method may be called multiple times for a given jid with the same information.
 * If this method sets the newCapabilitiesPtr parameter,
 * the XMPPCapabilities module will invoke the xmppCapabilities:didDiscoverCapabilities:forJID: delegate method.
 * This delegate method is designed to be invoked only when the capabilities for the given JID have changed.
 * That is, the capabilities for the jid have been discovered for the first time (jid just signed in)
 * or the capabilities for the given jid have changed (jid broadcast new capabilities).
**/
- (BOOL)setCapabilitiesNode:(NSString *)node
                        ver:(NSString *)ver
                        ext:(NSString *)ext
                       hash:(NSString *)hash
                  algorithm:(NSString *)hashAlg
                     forJID:(XMPPJID *)jid
                 xmppStream:(XMPPStream *)stream
      andGetNewCapabilities:(NSXMLElement **)newCapabilitiesPtr;

/**
 * Fetches the associated capabilities hash for a given jid.
 * 
 * If the jid is not associated with a capabilities hash, this method should return NO.
 * Otherwise it should return YES, and set the corresponding variables.
**/
- (BOOL)getCapabilitiesHash:(NSString **)hashPtr
                  algorithm:(NSString **)hashAlgPtr
                     forJID:(XMPPJID *)jid
                 xmppStream:(XMPPStream *)stream;

/**
 * Clears any associated hash from a jid.
 * If the jid is linked to a set of capabilities, it should be unlinked.
 * 
 * This method should not clear the actual capabilities information itself.
 * It should simply unlink the connection between the jid and the capabilities.
**/
- (void)clearCapabilitiesHashAndAlgorithmForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream;

/**
 * Gets the metadata for the given jid.
 * 
 * If the capabilities are known, the areCapabilitiesKnown boolean should be set to YES.
**/
- (void)getCapabilitiesKnown:(BOOL *)areCapabilitiesKnownPtr
					  failed:(BOOL *)haveFailedFetchingBeforePtr
                        node:(NSString **)nodePtr
                         ver:(NSString **)verPtr
                         ext:(NSString **)extPtr
                        hash:(NSString **)hashPtr
                   algorithm:(NSString **)hashAlgPtr
                      forJID:(XMPPJID *)jid
                  xmppStream:(XMPPStream *)stream;

/**
 * Sets the capabilities associated with a given hash.
 * 
 * Since the capabilities are linked to a hash, these capabilities (and associated hash)
 * should be persisted to disk and persisted between multiple sessions/streams.
 * 
 * It is the responsibility of the storage implementation to link the
 * associated jids (those with the given hash) to the given set of capabilities.
 * 
 * Implementation Note:
 * 
 * If we receive multiple simultaneous presence elements from
 * multiple jids all broadcasting the same capabilities hash:
 * 
 * - A single disco request will be sent to one of the jids.
 * - When the response comes back, the setCapabilities:forHash:algorithm: method will be invoked.
 * 
 * The setCapabilities:forJID: method will NOT be invoked for each corresponding jid.
 * This is by design to allow the storage implementation to optimize itself.
**/
- (void)setCapabilities:(NSXMLElement *)caps forHash:(NSString *)hash algorithm:(NSString *)hashAlg;

/**
 * Sets the capabilities for a given jid.
 * 
 * The jid is guaranteed NOT to be associated with a capabilities hash.
 * 
 * Since the capabilities are NOT linked to a hash,
 * these capabilities should not be persisted between multiple sessions/streams.
 * See the various clear methods below.
**/
- (void)setCapabilities:(NSXMLElement *)caps forJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream;

/**
 * Marks the disco fetch request as failed so we know not to bother trying again.
 * 
 * This is temporary metadata associated with the jid.
 * It should be cleared when we go unavailable or offline, or if the given jid goes unavailable.
 * See the various clear methods below.
**/
- (void)setCapabilitiesFetchFailedForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream;

/**
 * This method is called when we go unavailable or offline.
 * 
 * This method should clear all metadata (node, ver, ext, hash, algorithm, failed) from all jids in the roster.
 * All jids should be unlinked from associated capabilities.
 * 
 * If the associated capabilities are persistent, they should not be cleared.
 * That is, if the associated capabilities are associated with a hash, they should be persisted.
 * 
 * Non persistent capabilities (those not associated with a hash)
 * should be cleared at this point as they will no longer be linked to any users.
**/
- (void)clearAllNonPersistentCapabilitiesForXMPPStream:(XMPPStream *)stream;

/**
 * This method is called when the given jid goes unavailable.
 * 
 * This method should clear all metadata (node, ver, ext, hash ,algorithm, failed) from the given jid.
 * The jid should be unlinked from associated capabilities.
 * 
 * If the associated capabilities are persistent, they should not be cleared.
 * That is, if the associated capabilities are associated with a hash, they should be persisted.
 * 
 * Non persistent capabilities (those not associated with a hash) should be cleared.
**/
- (void)clearNonPersistentCapabilitiesForJID:(XMPPJID *)jid xmppStream:(XMPPStream *)stream;

@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@protocol XMPPCapabilitiesDelegate
@optional

/**
 * Use this delegate method to add specific capabilities.
 * This method in invoked automatically when the stream is connected for the first time,
 * or if the module detects an outgoing presence element and my capabilities haven't been collected yet
 * 
 * The design of XEP-115 is such that capabilites are expected to remain rather static.
 * However, if the capabilities change, the recollectMyCapabilities method may be used to perform a manual update.
**/
- (void)xmppCapabilities:(XMPPCapabilities *)sender collectingMyCapabilities:(NSXMLElement *)query;


/**
 * Use this delegate method to return the feature you want to have in your capabilities e.g. @[@"urn:xmpp:archive"]
 * Duplicate features are automatically discarded
 * For more control over your capablities use xmppCapabilities:collectingMyCapabilities:
**/
- (NSArray *)myFeaturesForXMPPCapabilities:(XMPPCapabilities *)sender;

/**
 * Invoked when capabilities have been discovered for an available JID.
 * 
 * The caps element is the <query/> element response to a disco#info request.
**/
- (void)xmppCapabilities:(XMPPCapabilities *)sender didDiscoverCapabilities:(NSXMLElement *)caps forJID:(XMPPJID *)jid;

@end
