/*
 * 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 "SDImageCodersManager.h"
#import "SDImageIOCoder.h"
#import "SDImageGIFCoder.h"
#import "SDImageAPNGCoder.h"
#import "SDImageHEICCoder.h"
#import "SDInternalMacros.h"

@interface SDImageCodersManager ()

@property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageCoder>> *imageCoders;

@end

@implementation SDImageCodersManager {
    SD_LOCK_DECLARE(_codersLock);
}

+ (nonnull instancetype)sharedManager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

- (instancetype)init {
    if (self = [super init]) {
        // initialize with default coders
        _imageCoders = [NSMutableArray arrayWithArray:@[[SDImageIOCoder sharedCoder], [SDImageGIFCoder sharedCoder], [SDImageAPNGCoder sharedCoder]]];
        SD_LOCK_INIT(_codersLock);
    }
    return self;
}

- (NSArray<id<SDImageCoder>> *)coders {
    SD_LOCK(_codersLock);
    NSArray<id<SDImageCoder>> *coders = [_imageCoders copy];
    SD_UNLOCK(_codersLock);
    return coders;
}

- (void)setCoders:(NSArray<id<SDImageCoder>> *)coders {
    SD_LOCK(_codersLock);
    [_imageCoders removeAllObjects];
    if (coders.count) {
        [_imageCoders addObjectsFromArray:coders];
    }
    SD_UNLOCK(_codersLock);
}

#pragma mark - Coder IO operations

- (void)addCoder:(nonnull id<SDImageCoder>)coder {
    if (![coder conformsToProtocol:@protocol(SDImageCoder)]) {
        return;
    }
    SD_LOCK(_codersLock);
    [_imageCoders addObject:coder];
    SD_UNLOCK(_codersLock);
}

- (void)removeCoder:(nonnull id<SDImageCoder>)coder {
    if (![coder conformsToProtocol:@protocol(SDImageCoder)]) {
        return;
    }
    SD_LOCK(_codersLock);
    [_imageCoders removeObject:coder];
    SD_UNLOCK(_codersLock);
}

#pragma mark - SDImageCoder
- (BOOL)canDecodeFromData:(NSData *)data {
    NSArray<id<SDImageCoder>> *coders = self.coders;
    for (id<SDImageCoder> coder in coders.reverseObjectEnumerator) {
        if ([coder canDecodeFromData:data]) {
            return YES;
        }
    }
    return NO;
}

- (BOOL)canEncodeToFormat:(SDImageFormat)format {
    NSArray<id<SDImageCoder>> *coders = self.coders;
    for (id<SDImageCoder> coder in coders.reverseObjectEnumerator) {
        if ([coder canEncodeToFormat:format]) {
            return YES;
        }
    }
    return NO;
}

- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
    if (!data) {
        return nil;
    }
    UIImage *image;
    NSArray<id<SDImageCoder>> *coders = self.coders;
    for (id<SDImageCoder> coder in coders.reverseObjectEnumerator) {
        if ([coder canDecodeFromData:data]) {
            image = [coder decodedImageWithData:data options:options];
            break;
        }
    }
    
    return image;
}

- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
    if (!image) {
        return nil;
    }
    NSArray<id<SDImageCoder>> *coders = self.coders;
    for (id<SDImageCoder> coder in coders.reverseObjectEnumerator) {
        if ([coder canEncodeToFormat:format]) {
            return [coder encodedDataWithImage:image format:format options:options];
        }
    }
    return nil;
}

- (NSData *)encodedDataWithFrames:(NSArray<SDImageFrame *> *)frames loopCount:(NSUInteger)loopCount format:(SDImageFormat)format options:(SDImageCoderOptions *)options {
    if (!frames || frames.count < 1) {
        return nil;
    }
    NSArray<id<SDImageCoder>> *coders = self.coders;
    for (id<SDImageCoder> coder in coders.reverseObjectEnumerator) {
        if ([coder canEncodeToFormat:format]) {
            if ([coder respondsToSelector:@selector(encodedDataWithFrames:loopCount:format:options:)]) {
                return [coder encodedDataWithFrames:frames loopCount:loopCount format:format options:options];
            }
        }
    }
    return nil;
}

@end
