/*
 * This file is part of the SDWebImage package.
 * (c) Olivier Poitrey <rs@dailymotion.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */


#import "SDCallbackQueue.h"

@interface SDCallbackQueue ()

@property (nonatomic, strong, nonnull) dispatch_queue_t queue;

@end

static void * SDCallbackQueueKey = &SDCallbackQueueKey;
static void SDReleaseBlock(void *context) {
    CFRelease(context);
}

static void SDSafeExecute(SDCallbackQueue *callbackQueue, dispatch_block_t _Nonnull block, BOOL async) {
    // Extendc gcd queue's life cycle
    dispatch_queue_t queue = callbackQueue.queue;
    // Special handle for main queue label only (custom queue can have the same label)
    const char *label = dispatch_queue_get_label(queue);
    if (label && label == dispatch_queue_get_label(dispatch_get_main_queue())) {
        const char *currentLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
        if (label == currentLabel) {
            block();
            return;
        }
    }
    // Check specific to detect queue equal
    void *specific = dispatch_queue_get_specific(queue, SDCallbackQueueKey);
    if (specific && CFGetTypeID(specific) == CFUUIDGetTypeID()) {
        void *currentSpecific = dispatch_get_specific(SDCallbackQueueKey);
        if (currentSpecific && CFGetTypeID(currentSpecific) == CFUUIDGetTypeID() && CFEqual(specific, currentSpecific)) {
            block();
            return;
        }
    }
    if (async) {
        dispatch_async(queue, block);
    } else {
        dispatch_sync(queue, block);
    }
}

@implementation SDCallbackQueue

- (instancetype)initWithDispatchQueue:(dispatch_queue_t)queue {
    self = [super init];
    if (self) {
        NSCParameterAssert(queue);
        CFUUIDRef UUID = CFUUIDCreate(kCFAllocatorDefault);
        dispatch_queue_set_specific(queue, SDCallbackQueueKey, (void *)UUID, SDReleaseBlock);
        _queue = queue;
    }
    return self;
}

+ (SDCallbackQueue *)mainQueue {
    static dispatch_once_t onceToken;
    static SDCallbackQueue *queue;
    dispatch_once(&onceToken, ^{
        queue = [[SDCallbackQueue alloc] initWithDispatchQueue:dispatch_get_main_queue()];
    });
    return queue;
}

+ (SDCallbackQueue *)currentQueue {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    SDCallbackQueue *queue = [[SDCallbackQueue alloc] initWithDispatchQueue:dispatch_get_current_queue()];
#pragma clang diagnostic pop
    return queue;
}

+ (SDCallbackQueue *)globalQueue {
    SDCallbackQueue *queue = [[SDCallbackQueue alloc] initWithDispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)];
    return queue;
}

- (void)sync:(nonnull dispatch_block_t)block {
    switch (self.policy) {
        case SDCallbackPolicySafeExecute:
            SDSafeExecute(self, block, NO);
            break;
        case SDCallbackPolicyDispatch:
            dispatch_sync(self.queue, block);
            break;
        case SDCallbackPolicyInvoke:
            block();
            break;
        default:
            SDSafeExecute(self, block, NO);
            break;
    }
}

- (void)async:(nonnull dispatch_block_t)block {
    switch (self.policy) {
        case SDCallbackPolicySafeExecute:
            SDSafeExecute(self, block, YES);
            break;
        case SDCallbackPolicyDispatch:
            dispatch_async(self.queue, block);
            break;
        case SDCallbackPolicyInvoke:
            block();
            break;
        default:
            SDSafeExecute(self, block, YES);
            break;
    }
}

@end
