#import "ARTChannel+Private.h"
#import "ARTChannelOptions+Private.h"
#import "ARTDataEncoder.h"
#import "ARTMessage.h"
#import "ARTChannelOptions.h"
#import "ARTNSArray+ARTFunctional.h"
#import "ARTBaseMessage+Private.h"
#import "ARTDataQuery.h"
#import "ARTRest+Private.h"
#import "ARTDefault.h"
#import "ARTClientOptions+Private.h"
#import "ARTInternalLog.h"
#import "ARTGCD.h"

@implementation ARTChannel {
    dispatch_queue_t _queue;
    ARTChannelOptions *_options;
}

- (instancetype)initWithName:(NSString *)name andOptions:(ARTChannelOptions *)options rest:(ARTRestInternal *)rest logger:(ARTInternalLog *)logger {
    if (self = [super init]) {
        _name = name;
        _logger = logger;
        _queue = rest.queue;
        _options = options;
        _options.frozen = YES;
        NSError *error = nil;
        _dataEncoder = [[ARTDataEncoder alloc] initWithCipherParams:_options.cipher logger:_logger error:&error];
        if (error != nil) {
            ARTLogWarn(_logger, @"creating ARTDataEncoder: %@", error);
            _dataEncoder = [[ARTDataEncoder alloc] initWithCipherParams:nil logger:_logger error:nil];
        }
    }
    return self;
}

- (ARTChannelOptions *)options {
    __block ARTChannelOptions *ret;
    art_dispatch_sync(_queue, ^{
        ret = [self options_nosync];
    });
    return ret;
}

- (ARTChannelOptions *)options_nosync {
    return _options;
}

- (void)setOptions:(ARTChannelOptions *)options {
    art_dispatch_sync(_queue, ^{
        [self setOptions_nosync:options];
    });
}

- (void)setOptions_nosync:(ARTChannelOptions *)options {
    _options = options;
    [self recreateDataEncoderWith:options.cipher];
}

- (void)recreateDataEncoderWith:(ARTCipherParams*)cipher {
    NSError *error = nil;
    _dataEncoder = [[ARTDataEncoder alloc] initWithCipherParams:cipher logger:self.logger error:&error];
    
    if (error != nil) {
        ARTLogWarn(_logger, @"creating ARTDataEncoder: %@", error);
        _dataEncoder = [[ARTDataEncoder alloc] initWithCipherParams:nil logger:self.logger error:nil];
    }
}

- (void)publish:(NSString *)name data:(id)data {
    [self publish:name data:data callback:nil];
}

- (void)publish:(nullable NSString *)name data:(nullable id)data callback:(nullable ARTCallback)callback {
    [self publish:name data:data extras:nil callback:callback];
}

- (void)publish:(NSString *)name data:(id)data extras:(id<ARTJsonCompatible>)extras {
    [self publish:name data:data extras:extras callback:nil];
}

- (void)publish:(nullable NSString *)name data:(nullable id)data extras:(id<ARTJsonCompatible>)extras callback:(nullable ARTCallback)callback {
    [self publish:name message:[[ARTMessage alloc] initWithName:name data:data] extras:extras callback:callback];
}

- (void)publish:(NSString *)name data:(id)data clientId:(NSString *)clientId {
    [self publish:name data:data clientId:clientId callback:nil];
}

- (void)publish:(NSString *)name data:(id)data clientId:(NSString *)clientId extras:(id<ARTJsonCompatible>)extras {
    [self publish:name data:data clientId:clientId extras:extras callback:nil];
}

- (void)publish:(NSString *)name data:(id)data clientId:(NSString *)clientId callback:(ARTCallback)callback {
    [self publish:name data:data clientId:(NSString *)clientId extras:nil callback:callback];
}

- (void)publish:(NSString *)name data:(id)data clientId:(NSString *)clientId extras:(id<ARTJsonCompatible>)extras callback:(ARTCallback)callback {
    [self publish:name message:[[ARTMessage alloc] initWithName:name data:data clientId:clientId] extras:extras callback:callback];
}

- (void)publish:(NSString *)name message:(ARTMessage *)message extras:(id<ARTJsonCompatible>)extras callback:(ARTCallback)callback {
    NSError *error = nil;
    message.extras = extras;
    ARTMessage *messagesWithDataEncoded = [self encodeMessageIfNeeded:message error:&error];
    if (error) {
        if (callback) callback([ARTErrorInfo createFromNSError:error]);
        return;
    }
    
    // Checked after encoding, so that the client can receive callback with encoding errors
    if ([self exceedMaxSize:@[message]]) {
        ARTErrorInfo *sizeError = [ARTErrorInfo createWithCode:ARTErrorMaxMessageLengthExceeded
                                                       message:@"Maximum message length exceeded."];
        if (callback) {
            callback(sizeError);
        }
        return;
    }
    
    [self internalPostMessages:messagesWithDataEncoded callback:callback];
}

- (void)publish:(NSArray<ARTMessage *> *)messages {
    [self publish:messages callback:nil];
}

- (void)publish:(NSArray<ARTMessage *> *)messages callback:(nullable ARTCallback)callback {
    NSError *error = nil;

    NSMutableArray<ARTMessage *> *messagesWithDataEncoded = [NSMutableArray new];
    for (ARTMessage *message in messages) {
        [messagesWithDataEncoded addObject:[self encodeMessageIfNeeded:message error:&error]];
    }
    
    if (error) {
        if (callback) {
            callback([ARTErrorInfo createFromNSError:error]);
        }
        return;
    }
    
    // Checked after encoding, so that the client can receive callback with encoding errors
    if ([self exceedMaxSize:messages]) {
        ARTErrorInfo *sizeError = [ARTErrorInfo createWithCode:ARTErrorMaxMessageLengthExceeded
                                                       message:@"Maximum message length exceeded."];
        if (callback) {
            callback(sizeError);
        }
        return;
    }
    
    [self internalPostMessages:messagesWithDataEncoded callback:callback];
}

- (BOOL)exceedMaxSize:(NSArray<ARTBaseMessage *> *)messages {
    NSInteger size = 0;
    for (ARTMessage *message in messages) {
        size += [message messageSize];
    }
    return size > [ARTDefault maxMessageSize];
}

- (ARTMessage *)encodeMessageIfNeeded:(ARTMessage *)message error:(NSError **)error {
    if (!self.dataEncoder) {
        return message;
    }
    NSError *e = nil;
    message = [message encodeWithEncoder:self.dataEncoder error:&e];
    if (e) {
        ARTLogError(self.logger, @"ARTChannel: error encoding data: %@", e);
    }
    if (error) {
        *error = e;
    }
    return message;
}

- (void)historyWithWrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents completion:(ARTPaginatedMessagesCallback)callback {
    NSAssert(false, @"-[%@ %@] should always be overriden.", self.class, NSStringFromSelector(_cmd));
}

- (void)internalPostMessages:(id)data callback:(ARTCallback)callback {
    NSAssert(false, @"-[%@ %@] should always be overriden.", self.class, NSStringFromSelector(_cmd));
}

@end
