//
//  IndyCallbacks.m
//  libindy
//

#include "indy_core.h"
#import "IndyCallbacks.h"
#import "NSError+IndyError.h"
#import "IndyTypes.h"

static NSString* commandCallbackKey    =  @"commandCallback";
static NSString* connectionCallbackKey =  @"connectionCallback";
static NSString* messageCallbackKey    =  @"messageCallback";
static NSString* connectionHandleKey   =  @"connectionHandle";
static NSString* connectionsKey        =  @"connections";


@interface IndyCallbacks ()

@property (strong, readwrite) NSMutableDictionary *commandCompletions;
@property                     indy_i32_t         commandHandleCounter;
@property (strong, readwrite) NSMutableDictionary *agentConnectCompletions;
@property (strong, readwrite) NSMutableDictionary *agentListenCompletions;
@property (strong, readwrite) NSMutableDictionary *listenerForConnection;      // used to determine listener handle for corresponded connection handle
@property (strong, readwrite) NSRecursiveLock     *globalLock;

@end

@implementation IndyCallbacks

- (IndyCallbacks *)init
{
    self = [super init];
    if (self)
    {
        self.commandHandleCounter = 0;
        self.commandCompletions = [[NSMutableDictionary alloc] init];
        self.agentConnectCompletions = [[NSMutableDictionary alloc] init];
        self.agentListenCompletions = [[NSMutableDictionary alloc] init];
        self.listenerForConnection = [[NSMutableDictionary alloc] init];
        self.globalLock = [NSRecursiveLock new];
    }
    return self;
}

- (void) addConnection:(indy_handle_t) connection  forListener:(indy_handle_t) listener
{
    NSNumber *nl = [NSNumber numberWithInt: listener];
    NSNumber *nc = [NSNumber numberWithInt: connection];
    
    @synchronized(self.globalLock)
    {
        NSMutableDictionary *dict = [self.agentConnectCompletions objectForKey: nl ];
        if(dict)
        {
            NSMutableDictionary *listenerParams = [dict objectForKey: connectionsKey];
            if(listenerParams)
            {
                [listenerParams setObject:nc forKey:nc];
            }
        }
    }
}


- (void) addListener:(indy_handle_t) listener forConnection:(indy_handle_t) connection
{
    NSNumber *nl = [NSNumber numberWithInt: listener];
    NSNumber *nc = [NSNumber numberWithInt: connection];
    
    @synchronized(self.globalLock)
    {
        [self.listenerForConnection setObject:nl forKey: nc];
    }
}

- (NSNumber*) listenerForConnection:(indy_handle_t) connection
{
    NSNumber *ret = nil;
    @synchronized(self.globalLock)
    {
        ret = [self.listenerForConnection objectForKey: [NSNumber numberWithInt:connection]];
    }
    return ret;
}

- (void) removeListenerForConnection:(indy_handle_t) connection
{
    NSNumber *nc = [NSNumber numberWithInt: connection];
    @synchronized(self.globalLock)
    {
        if( [self.listenerForConnection objectForKey: nc])
        {
            [self.listenerForConnection removeObjectForKey: nc];
        }
    }
}

- (void) rememberListenHandle:(indy_handle_t) listenHandle withDictionary:(NSMutableDictionary*) callbacks
{
    NSNumber *key = [NSNumber numberWithInt:listenHandle];
    
    @synchronized(self.globalLock)
    {
        [self.agentListenCompletions setObject:callbacks forKey:key];
    }
}

-(void) forgetListenHandle:(indy_handle_t) listenHandle
{
    NSNumber *key = [NSNumber numberWithInt:listenHandle];
    @synchronized(self.globalLock)
    {
        if ([self.agentListenCompletions objectForKey:key])
        {
            NSMutableDictionary *dict = [self.agentListenCompletions objectForKey:key];
            if(dict && [dict objectForKey:connectionsKey])
            {
                NSArray *connections = [[dict objectForKey:connectionsKey] allKeys];
                for(NSNumber *n in connections)
                {
                    [self removeListenerForConnection:[n intValue]];
                }
            }
            [self.agentListenCompletions removeObjectForKey:key];
        }
    }
}

- (NSMutableDictionary*)listenCompletionsFor:(indy_handle_t)handle
{
    NSNumber *key = [NSNumber numberWithInt:handle];
    NSMutableDictionary *val = nil;
    @synchronized(self.globalLock)
    {
        val = [self.agentListenCompletions objectForKey: key];
    }
    return val;
}

- (void) rememberConnectHandle:(indy_handle_t) connectionHandle withCallback:(void*) callback
{
    NSValue *val = [NSValue valueWithPointer:callback];
    NSNumber *key = [NSNumber numberWithInt:connectionHandle];
    
    @synchronized(self.globalLock)
    {
        [self.agentConnectCompletions setObject:val forKey:key];
    }
}

-(void) forgetConnectHandle:(indy_handle_t) connectionHandle
{
    NSNumber *key = [NSNumber numberWithInt:connectionHandle];
    @synchronized(self.globalLock)
    {
        if ([self.agentConnectCompletions objectForKey:key])
        {
            [self.agentConnectCompletions removeObjectForKey:key];
        }
    }
}

- (void *)connectCompletionFor:(indy_handle_t)handle
{
    NSNumber *key = [NSNumber numberWithInt:handle];
    NSValue *val = nil;
    @synchronized(self.globalLock)
    {
        val = [self.agentConnectCompletions objectForKey: key];
    }
    return val ? [val pointerValue] : NULL;
}


- (indy_handle_t)createCommandHandleFor:(void *)callback
{
    NSValue *cmdVal = [NSValue valueWithPointer:callback];
    NSNumber *handle = nil;
    
    @synchronized(self.globalLock)
    {
        handle = [NSNumber numberWithInt:self.commandHandleCounter];
        self.commandHandleCounter++;
        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: cmdVal, commandCallbackKey, nil];
        [self.commandCompletions setObject:dict forKey:handle];
    }
    return (indy_handle_t)[handle integerValue];
}

- (indy_handle_t)createCommandHandleFor:(void *)callback
                   withConnectionHandle:(indy_handle_t)connectionHandle
{
    NSValue  *cmdVal = [NSValue valueWithPointer:callback];
    NSNumber *conVal = [NSNumber numberWithInt:connectionHandle];
    
    NSNumber *handle = nil;
    
    @synchronized(self.globalLock)
    {
        handle = [NSNumber numberWithInt:self.commandHandleCounter];
        self.commandHandleCounter++;
        NSMutableDictionary *dict = [ NSMutableDictionary dictionaryWithObjectsAndKeys: cmdVal, commandCallbackKey,
                                     conVal, connectionHandleKey, nil];
        [self.commandCompletions setObject:dict forKey:handle];
    }
    return (indy_handle_t)[handle integerValue];
}


/**
 Map passed callbacks to commandHandle.
 @param listenerCallback Callback that will be called after listening started or on error.
 Will be called exactly once with result of start listen operation.
 @param connectionCallback  Callback that will be called after establishing of incoming connection.
 Can be called multiply times: once for each incoming connection.
 @param messageCallback Callback that will be called on receiving of an incoming message. Can be called multiply times: once for each incoming message.
 @return commandHandle
 */
- (indy_handle_t)createCommandHandleForListenerCallback:(void *)listenerCallback
                                 withConnectionCallback:(void *)connectionCallback
                                     andMessageCallback:(void *)messageCallback
{
    NSValue *listenerCbVal = [NSValue valueWithPointer:listenerCallback];
    NSValue *connectionCbVal = [NSValue valueWithPointer:connectionCallback];
    NSValue *messageCbVal = [NSValue valueWithPointer:messageCallback];
    
    NSNumber *handle = nil;
    
    @synchronized(self.globalLock)
    {
        handle = [NSNumber numberWithInt:self.commandHandleCounter];
        self.commandHandleCounter++;
        NSMutableDictionary *dict = [ NSMutableDictionary dictionaryWithObjectsAndKeys: listenerCbVal, commandCallbackKey,
                                     connectionCbVal, connectionCallbackKey,
                                     messageCbVal, messageCallbackKey,    nil];
        
        [self.commandCompletions setObject:dict forKey:handle];
    }
    return (indy_handle_t)[handle integerValue];
}

- (indy_handle_t)createCommandHandleFor:(void *)callback
                    withMessageCallback:(void *)messageCallback
{
    NSValue *cmdVal = [NSValue valueWithPointer:callback];
    NSValue *mesVal = [NSValue valueWithPointer:messageCallback];
    
    NSNumber *handle = nil;
    
    @synchronized(self.globalLock)
    {
        handle = [NSNumber numberWithInt:self.commandHandleCounter];
        self.commandHandleCounter++;
        NSMutableDictionary *dict = [ NSMutableDictionary dictionaryWithObjectsAndKeys: cmdVal, commandCallbackKey,
                                     mesVal, messageCallbackKey,    nil];
        [self.commandCompletions setObject:dict forKey:handle];
    }
    return (indy_handle_t)[handle integerValue];
}


- (void)deleteCommandHandleFor:(indy_handle_t)handle
{
    NSNumber *key = [NSNumber numberWithInt:handle];
    @synchronized(self.globalLock)
    {
        if ([self.commandCompletions objectForKey:key])
        {
            [self.commandCompletions removeObjectForKey:key];
        }
    }
}

- (void *)commandCompletionFor:(indy_handle_t)handle
{
    NSNumber *key = [NSNumber numberWithInt:handle];
    NSValue *val = nil;
    @synchronized(self.globalLock)
    {
        NSMutableDictionary *dict = (NSMutableDictionary*)[self.commandCompletions objectForKey:key];
        val = [dict objectForKey:@"commandCallback"];
    }
    return val ? [val pointerValue] : NULL;
}

- (NSMutableDictionary*) dictionaryFor:(indy_handle_t)handle
{
    NSNumber *key = [NSNumber numberWithInt:handle];
    NSMutableDictionary *dict = nil;
    @synchronized(self.globalLock)
    {
        dict = (NSMutableDictionary*)[self.commandCompletions objectForKey:key];
    }
    return dict;
}

+ (IndyCallbacks *)sharedInstance
{
    static IndyCallbacks *instance = nil;
    static dispatch_once_t dispatch_once_block;
    
    dispatch_once(&dispatch_once_block, ^ {
        instance = [IndyCallbacks new];
    });
    
    return instance;
}

@end


void IndyWrapperCommon2PCallback(indy_handle_t xcommand_handle,
                                 indy_error_t err)
{
    void * block = [[IndyCallbacks sharedInstance] commandCompletionFor: xcommand_handle];
    [[IndyCallbacks sharedInstance] deleteCommandHandleFor: xcommand_handle];
    
    void (^completion)(NSError*) = (__bridge void (^)(NSError*))block;
    
    if (completion)
    {
        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           NSError *error = [ NSError errorFromIndyError: err ];
                           completion(error);
                       });
    }
}

void IndyWrapperCommon3PHCallback(indy_handle_t xcommand_handle,
                                  indy_error_t err,
                                  indy_handle_t pool_handle)
{
    void * block = [[IndyCallbacks sharedInstance] commandCompletionFor: xcommand_handle];
    [[IndyCallbacks sharedInstance] deleteCommandHandleFor: xcommand_handle];
    
    void (^completion)(NSError*, IndyHandle) = (__bridge void (^)(NSError*, IndyHandle))block;
    
    if (completion)
    {
        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           NSError *error = [ NSError errorFromIndyError: err ];
                           completion(error, (IndyHandle) pool_handle);
                       });
    }
}

void IndyWrapperCommon3PSCallback(indy_handle_t xcommand_handle,
                                  indy_error_t err,
                                  const char* arg1)
{
    void * block = [[IndyCallbacks sharedInstance] commandCompletionFor: xcommand_handle];
    [[IndyCallbacks sharedInstance] deleteCommandHandleFor: xcommand_handle];
    
    void (^completion)(NSError*, NSString *) = (__bridge void (^)(NSError*, NSString *arg1 ))block;
    NSString* sarg1 = [ NSString stringWithUTF8String: arg1];
    
    if (completion)
    {
        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           NSError *error = [ NSError errorFromIndyError: err ];
                           completion(error, sarg1);
                       });
    }
}

void IndyWrapperCommon3PBCallback(indy_handle_t xcommand_handle,
                                  indy_error_t err,
                                  indy_bool_t arg1)
{
    void * block = [[IndyCallbacks sharedInstance] commandCompletionFor: xcommand_handle];
    [[IndyCallbacks sharedInstance] deleteCommandHandleFor: xcommand_handle];
    
    void (^completion)(NSError*, BOOL ) = (__bridge void (^)(NSError*, BOOL arg1 ))block;
    
    if (completion)
    {
        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           NSError *error = [ NSError errorFromIndyError: err ];
                           completion(error, (BOOL) arg1);
                       });
    }
}

void IndyWrapperCommon4PCallback(indy_handle_t xcommand_handle,
                                 indy_error_t err,
                                 const char* arg1,
                                 const char *arg2)
{
    void * block = [[IndyCallbacks sharedInstance] commandCompletionFor: xcommand_handle];
    [[IndyCallbacks sharedInstance] deleteCommandHandleFor: xcommand_handle];
    
    void (^completion)(NSError*, NSString* arg1, NSString *arg2) = (__bridge void (^)(NSError*, NSString* arg1, NSString *arg2))block;
    
    NSString* sarg1 = [ NSString stringWithUTF8String: arg1];
    NSString* sarg2 = [ NSString stringWithUTF8String: arg2];
    
    if (completion)
    {
        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           NSError *error = [ NSError errorFromIndyError: err ];
                           completion(error, sarg1, sarg2);
                       });
    }
}

void IndyWrapperCommon5PCallback(indy_handle_t xcommand_handle,
                                 indy_error_t err,
                                 const char* arg1,
                                 const char *arg2,
                                 const char *arg3)
{
    void * block = [[IndyCallbacks sharedInstance] commandCompletionFor: xcommand_handle];
    [[IndyCallbacks sharedInstance] deleteCommandHandleFor: xcommand_handle];
    
    void (^completion)(NSError*, NSString* arg1, NSString *arg2, NSString *arg3) = (__bridge void (^)(NSError*, NSString* arg1, NSString *arg2, NSString *arg3))block;
    
    NSString* sarg1 = [ NSString stringWithUTF8String: arg1];
    NSString* sarg2 = [ NSString stringWithUTF8String: arg2];
    NSString* sarg3 = [ NSString stringWithUTF8String: arg3];
    
    if (completion)
    {
        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           NSError *error = [ NSError errorFromIndyError: err ];
                           completion(error, sarg1, sarg2, sarg3);
                       });
    }
}

void IndyWrapperCommon5PSCallback(indy_handle_t xcommand_handle,
                                  indy_error_t err,
                                  indy_handle_t connection_handle,
                                  const char* arg1,
                                  const char *arg2)
{
    void * block = [[IndyCallbacks sharedInstance] commandCompletionFor: xcommand_handle];
    [[IndyCallbacks sharedInstance] deleteCommandHandleFor: xcommand_handle];
    
    void (^completion)(NSError*, IndyHandle, NSString* arg1, NSString *arg2) = (__bridge void (^)(NSError*, IndyHandle, NSString* arg1, NSString *arg2))block;
    
    NSString* sarg1 = [NSString stringWithUTF8String: arg1];
    NSString* sarg2 = [NSString stringWithUTF8String: arg2];
    
    if (completion)
    {
        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           NSError *error = [ NSError errorFromIndyError: err ];
                           completion(error, (IndyHandle) connection_handle, sarg1, sarg2);
                       });
    }
}

void IndyWrapperCommonAgentOutgoingConnectionCallback(indy_handle_t xcommand_handle,
                                                      indy_error_t  err,
                                                      indy_handle_t connection_handle)
{
    NSMutableDictionary *dict = [[IndyCallbacks sharedInstance] dictionaryFor: xcommand_handle];
    if(dict && [dict objectForKey: commandCallbackKey])
    {
        void * commandBlock = [[dict objectForKey: commandCallbackKey] pointerValue];
        void * messageBlock = [[dict objectForKey: messageCallbackKey] pointerValue];
        [[IndyCallbacks sharedInstance] deleteCommandHandleFor: xcommand_handle];
        
        void (^completion)(NSError*, IndyHandle) = (__bridge void (^)(NSError*, IndyHandle))commandBlock;
        
        if(err == Success)
        {
            [[IndyCallbacks sharedInstance] rememberConnectHandle: connection_handle withCallback: messageBlock];
        }
        
        if (completion)
        {
            dispatch_async(dispatch_get_main_queue(), ^
                           {
                               NSError *error = [NSError errorFromIndyError: err ];
                               completion(error, (IndyHandle) connection_handle);
                           });
        }
    }
}

void IndyWrapperCommonAgentMessageCallback(indy_handle_t xconnection_handle,
                                           indy_error_t  err,
                                           const char *    message)
{
    NSString *messageArg = [NSString stringWithUTF8String: message];
    dispatch_async(dispatch_get_main_queue(), ^
                   {
                       void* block = [[IndyCallbacks sharedInstance] connectCompletionFor: xconnection_handle];
                       if(block)
                       {
                           void (^completion)(IndyHandle, NSError*, NSString*) = (__bridge void (^)(IndyHandle, NSError*, NSString*))block;
                           if(completion)
                           {
                               NSError *error = [NSError errorFromIndyError: err ];
                               completion(xconnection_handle, error, messageArg);
                           }
                       }
                   });
}

void IndyWrapperCloseConnectionCallback(indy_handle_t xcommand_handle,
                                        indy_error_t err)
{
    void * block = [[IndyCallbacks sharedInstance] commandCompletionFor: xcommand_handle];
    [[IndyCallbacks sharedInstance] deleteCommandHandleFor: xcommand_handle];
    void (^completion)(NSError*) = (__bridge void (^)(NSError*))block;
    
    NSMutableDictionary *dict = [[IndyCallbacks sharedInstance] dictionaryFor: xcommand_handle];
    
    if(dict && [dict objectForKey: connectionHandleKey])
    {
        NSNumber *connectionHandle = [dict objectForKey: connectionHandleKey];
        [[IndyCallbacks sharedInstance] forgetConnectHandle: [connectionHandle intValue]];
        [[IndyCallbacks sharedInstance] removeListenerForConnection: [connectionHandle intValue]];
    }
    
    if (completion)
    {
        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           NSError *error = [ NSError errorFromIndyError: err ];
                           completion(error);
                       });
    }
}

void IndyWrapperCommonAgentListenerCallback(indy_handle_t xcommand_handle,
                                            indy_error_t  err,
                                            indy_handle_t listener_handle)
{
    void * block = [[IndyCallbacks sharedInstance] commandCompletionFor: xcommand_handle];
    // Dictionary of callbacks for this commandHandle
    NSMutableDictionary *dict = [[IndyCallbacks sharedInstance] dictionaryFor: xcommand_handle];
    
    // reset connections dictionary
    dict[connectionsKey] = [NSMutableDictionary new];
    // delete completions for this command handle
    [[IndyCallbacks sharedInstance] deleteCommandHandleFor: xcommand_handle];
    [[IndyCallbacks sharedInstance] rememberListenHandle:listener_handle withDictionary:dict];
    
    void (^completion)(NSError* error, IndyHandle) = (__bridge void (^)(NSError*,IndyHandle))block;
    
    if(completion)
    {
        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           NSError *error = [ NSError errorFromIndyError: err ];
                           completion(error, (IndyHandle) listener_handle);
                       });
    }
}

void IndyWrapperCommonAgentListenerConnectionCallback(indy_handle_t xlistener_handle,
                                                      indy_error_t  err,
                                                      indy_handle_t connection_handle,
                                                      const char *    sender_did,
                                                      const char *    receiver_did)
{
    NSString* sarg1 = [NSString stringWithUTF8String: sender_did];
    NSString* sarg2 = [NSString stringWithUTF8String: receiver_did];
    
    dispatch_async(dispatch_get_main_queue(), ^
                   {
                       [[IndyCallbacks sharedInstance] addListener:xlistener_handle forConnection:connection_handle];
                       [[IndyCallbacks sharedInstance] addConnection:connection_handle forListener:xlistener_handle];
                       
                       NSMutableDictionary *dict = [[IndyCallbacks sharedInstance] listenCompletionsFor: xlistener_handle];
                       
                       if(dict && [dict objectForKey: connectionCallbackKey])
                       {
                           NSValue *val = [dict objectForKey: connectionCallbackKey];
                           if(val)
                           {
                               void* block = [val pointerValue];
                               void (^completion)(IndyHandle,NSError*,IndyHandle,NSString*,NSString*) = (__bridge void (^)(IndyHandle,NSError*,IndyHandle,NSString*,NSString*))block;
                               if(completion)
                               {
                                   NSError *error = [ NSError errorFromIndyError: err ];
                                   
                                   completion((IndyHandle)xlistener_handle, error, (IndyHandle) connection_handle, sarg1, sarg2);
                               }
                           }
                       }
                       
                   });
}

void IndyWrapperCommonAgentListenerMessageCallback(indy_handle_t xconnection_handle,
                                                   indy_error_t  err,
                                                   const char *    message)
{
    NSString* sarg = [NSString stringWithUTF8String: message];
    
    dispatch_async(dispatch_get_main_queue(), ^
                   {
                       NSNumber* listenerHandle = [[IndyCallbacks sharedInstance] listenerForConnection: xconnection_handle];
                       NSMutableDictionary *dict = [[IndyCallbacks sharedInstance] listenCompletionsFor: [listenerHandle intValue]];
                       
                       if(listenerHandle && dict && [dict objectForKey: messageCallbackKey] )
                       {
                           NSValue *val = [dict objectForKey: messageCallbackKey];
                           
                           if(val)
                           {
                               void* block = [val pointerValue];
                               void (^completion)(IndyHandle,NSError*,NSString*) = (__bridge void (^)(IndyHandle,NSError*,NSString*))block;
                               if(completion)
                               {
                                   NSError *error = [ NSError errorFromIndyError: err ];
                                   completion((IndyHandle)xconnection_handle, error, sarg);
                               }
                               
                           }
                       }
                   });
}
