/*
 * This file is part of the ZYJSDWebImage 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 "ZYJSDWebImageCompat.h"
#import "ZYJSDWebImageOperation.h"
#import "ZYJSDImageCacheDefine.h"
#import "ZYJSDImageLoader.h"
#import "ZYJSDImageZYJTransformer.h"
#import "ZYJSDWebImageCacheKeyFilter.h"
#import "ZYJSDWebImageCacheSerializer.h"
#import "ZYJSDWebImageOptionsProcessor.h"

typedef void(^ZYJSDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, ZYJSDImageCacheType cacheType, NSURL * _Nullable imageURL);

typedef void(^ZYJSDInternalCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, ZYJSDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL);

/**
 A combined operation representing the cache and loader operation. You can use it to cancel the load process.
 */
@interface ZYJSDWebImageCombinedOperation : NSObject <ZYJSDWebImageOperation>

/**
 Cancel the current operation, including cache and loader process
 */
- (void)cancel;

/**
 The cache operation from the image cache query
 */
@property (strong, nonatomic, nullable, readonly) id<ZYJSDWebImageOperation> cacheOperation;

/**
 The loader operation from the image loader (such as download operation)
 */
@property (strong, nonatomic, nullable, readonly) id<ZYJSDWebImageOperation> loaderOperation;

@end


@class ZYJSDWebImageManager;

/**
 The manager delegate protocol.
 */
@protocol ZYJSDWebImageManagerDelegate <NSObject>

@optional

/**
 * Controls which image should be downloaded when the image is not found in the cache.
 *
 * @param imageManager The current `ZYJSDWebImageManager`
 * @param imageURL     The url of the image to be downloaded
 *
 * @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied.
 */
- (BOOL)imageManager:(nonnull ZYJSDWebImageManager *)imageManager shouldDownloadImageForURL:(nonnull NSURL *)imageURL;

/**
 * Controls the complicated logic to mark as failed URLs when download error occur.
 * If the delegate implement this method, we will not use the built-in way to mark URL as failed based on error code;
 @param imageManager The current `ZYJSDWebImageManager`
 @param imageURL The url of the image
 @param error The download error for the url
 @return Whether to block this url or not. Return YES to mark this URL as failed.
 */
- (BOOL)imageManager:(nonnull ZYJSDWebImageManager *)imageManager shouldBlockFailedURL:(nonnull NSURL *)imageURL withError:(nonnull NSError *)error;

@end

/**
 * The ZYJSDWebImageManager is the class behind the UIImageView+ZYJWebCache category and likes.
 * It ties the asynchronous downloader (ZYJSDWebImageDownloader) with the image cache store (ZYJSDImageCache).
 * You can use this class directly to benefit from web image downloading with caching in another context than
 * a UIView.
 *
 * Here is a simple example of how to use ZYJSDWebImageManager:
 *
 * @code

ZYJSDWebImageManager *manager = [ZYJSDWebImageManager sharedManager];
[manager loadImageWithURL:imageURL
                  options:0
                 progress:nil
                completed:^(UIImage *image, NSData *data, NSError *error, ZYJSDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                    if (image) {
                        // do something with image
                    }
                }];

 * @endcode
 */
@interface ZYJSDWebImageManager : NSObject

/**
 * The delegate for manager. Defaults to nil.
 */
@property (weak, nonatomic, nullable) id <ZYJSDWebImageManagerDelegate> delegate;

/**
 * The image cache used by manager to query image cache.
 */
@property (strong, nonatomic, readonly, nonnull) id<ZYJSDImageCache> imageCache;

/**
 * The image loader used by manager to load image.
 */
@property (strong, nonatomic, readonly, nonnull) id<ZYJSDImageLoader> imageLoader;

/**
 The image ZYJTransformer for manager. It's used for image ZYJTransform after the image load finished and store the ZYJTransformed image to cache, see `ZYJSDImageZYJTransformer`.
 Defaults to nil, which means no ZYJTransform is applied.
 @note This will affect all the load requests for this manager if you provide. However, you can pass `ZYJSDWebImageContextImageZYJTransformer` in context arg to explicitly use that ZYJTransformer instead.
 */
@property (strong, nonatomic, nullable) id<ZYJSDImageZYJTransformer> ZYJTransformer;

/**
 * The cache filter is used to convert an URL into a cache key each time ZYJSDWebImageManager need cache key to use image cache.
 *
 * The following example sets a filter in the application delegate that will remove any query-string from the
 * URL before to use it as a cache key:
 *
 * @code
 ZYJSDWebImageManager.sharedManager.cacheKeyFilter =[ZYJSDWebImageCacheKeyFilter cacheKeyFilterWithBlock:^NSString * _Nullable(NSURL * _Nonnull url) {
    url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
    return [url absoluteString];
 }];
 * @endcode
 */
@property (nonatomic, strong, nullable) id<ZYJSDWebImageCacheKeyFilter> cacheKeyFilter;

/**
 * The cache serializer is used to convert the decoded image, the source downloaded data, to the actual data used for storing to the disk cache. If you return nil, means to generate the data from the image instance, see `ZYJSDImageCache`.
 * For example, if you are using WebP images and facing the slow decoding time issue when later retrieving from disk cache again. You can try to encode the decoded image to JPEG/PNG format to disk cache instead of source downloaded data.
 * @note The `image` arg is nonnull, but when you also provide an image ZYJTransformer and the image is ZYJTransformed, the `data` arg may be nil, take attention to this case.
 * @note This method is called from a global queue in order to not to block the main thread.
 * @code
 ZYJSDWebImageManager.sharedManager.cacheSerializer = [ZYJSDWebImageCacheSerializer cacheSerializerWithBlock:^NSData * _Nullable(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL) {
    ZYJSDImageFormat format = [NSData ZYJSD_imageFormatForImageData:data];
    switch (format) {
        case ZYJSDImageFormatWebP:
            return image.images ? data : nil;
        default:
            return data;
    }
}];
 * @endcode
 * The default value is nil. Means we just store the source downloaded data to disk cache.
 */
@property (nonatomic, strong, nullable) id<ZYJSDWebImageCacheSerializer> cacheSerializer;

/**
 The options processor is used, to have a global control for all the image request options and context option for current manager.
 @note If you use `ZYJTransformer`, `cacheKeyFilter` or `cacheSerializer` property of manager, the input context option already apply those properties before passed. This options processor is a better replacement for those property in common usage.
 For example, you can control the global options, based on the URL or original context option like the below code.
 
 @code
 ZYJSDWebImageManager.sharedManager.optionsProcessor = [ZYJSDWebImageOptionsProcessor optionsProcessorWithBlock:^ZYJSDWebImageOptionsResult * _Nullable(NSURL * _Nullable url, ZYJSDWebImageOptions options, ZYJSDWebImageContext * _Nullable context) {
     // Only do animation on `ZYJSDAnimatedImageView`
     if (!context[ZYJSDWebImageContextAnimatedImageClass]) {
        options |= ZYJSDWebImageDecodeFirstFrameOnly;
     }
     // Do not force decode for png url
     if ([url.lastPathComponent isEqualToString:@"png"]) {
        options |= ZYJSDWebImageAvoidDecodeImage;
     }
     // Always use screen scale factor
     ZYJSDWebImageMutableContext *mutableContext = [NSDictionary dictionaryWithDictionary:context];
     mutableContext[ZYJSDWebImageContextImageScaleFactor] = @(UIScreen.mainScreen.scale);
     context = [mutableContext copy];
 
     return [[ZYJSDWebImageOptionsResult alloc] initWithOptions:options context:context];
 }];
 @endcode
 */
@property (nonatomic, strong, nullable) id<ZYJSDWebImageOptionsProcessor> optionsProcessor;

/**
 * Check one or more operations running
 */
@property (nonatomic, assign, readonly, getter=isRunning) BOOL running;

/**
 The default image cache when the manager which is created with no arguments. Such as shared manager or init.
 Defaults to nil. Means using `ZYJSDImageCache.sharedImageCache`
 */
@property (nonatomic, class, nullable) id<ZYJSDImageCache> defaultImageCache;

/**
 The default image loader for manager which is created with no arguments. Such as shared manager or init.
 Defaults to nil. Means using `ZYJSDWebImageDownloader.sharedDownloader`
 */
@property (nonatomic, class, nullable) id<ZYJSDImageLoader> defaultImageLoader;

/**
 * Returns global shared manager instance.
 */
@property (nonatomic, class, readonly, nonnull) ZYJSDWebImageManager *sharedManager;

/**
 * Allows to specify instance of cache and image loader used with image manager.
 * @return new instance of `ZYJSDWebImageManager` with specified cache and loader.
 */
- (nonnull instancetype)initWithCache:(nonnull id<ZYJSDImageCache>)cache loader:(nonnull id<ZYJSDImageLoader>)loader NS_DESIGNATED_INITIALIZER;

/**
 * Downloads the image at the given URL if not present in cache or return the cached version otherwise.
 *
 * @param url            The URL to the image
 * @param options        A mask to specify options to use for this request
 * @param progressBlock  A block called while image is downloading
 *                       @note the progress block is executed on a background queue
 * @param completedBlock A block called when operation has been completed.
 *
 *   This parameter is required.
 * 
 *   This block has no return value and takes the requested UIImage as first parameter and the NSData representation as second parameter.
 *   In case of error the image parameter is nil and the third parameter may contain an NSError.
 *
 *   The forth parameter is an `ZYJSDImageCacheType` enum indicating if the image was retrieved from the local cache
 *   or from the memory cache or from the network.
 *
 *   The fifth parameter is set to NO when the ZYJSDWebImageProgressiveLoad option is used and the image is
 *   downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the
 *   block is called a last time with the full image and the last parameter set to YES.
 *
 *   The last parameter is the original image URL
 *
 * @return Returns an instance of ZYJSDWebImageCombinedOperation, which you can cancel the loading process.
 */
- (nullable ZYJSDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                                   options:(ZYJSDWebImageOptions)options
                                                  progress:(nullable ZYJSDImageLoaderProgressBlock)progressBlock
                                                 completed:(nonnull ZYJSDInternalCompletionBlock)completedBlock;

/**
 * Downloads the image at the given URL if not present in cache or return the cached version otherwise.
 *
 * @param url            The URL to the image
 * @param options        A mask to specify options to use for this request
 * @param context        A context contains different options to perform specify changes or processes, see `ZYJSDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
 * @param progressBlock  A block called while image is downloading
 *                       @note the progress block is executed on a background queue
 * @param completedBlock A block called when operation has been completed.
 *
 * @return Returns an instance of ZYJSDWebImageCombinedOperation, which you can cancel the loading process.
 */
- (nullable ZYJSDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                                   options:(ZYJSDWebImageOptions)options
                                                   context:(nullable ZYJSDWebImageContext *)context
                                                  progress:(nullable ZYJSDImageLoaderProgressBlock)progressBlock
                                                 completed:(nonnull ZYJSDInternalCompletionBlock)completedBlock;

/**
 * Cancel all current operations
 */
- (void)cancelAll;

/**
 * Remove the specify URL from failed black list.
 * @param url The failed URL.
 */
- (void)removeFailedURL:(nonnull NSURL *)url;

/**
 * Remove all the URL from failed black list.
 */
- (void)removeAllFailedURLs;

/**
 * Return the cache key for a given URL, does not considerate ZYJTransformer or thumbnail.
 * @note This method does not have context option, only use the url and manager level cacheKeyFilter to generate the cache key.
 */
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;

/**
 * Return the cache key for a given URL and context option.
 * @note The context option like `.thumbnailPixelSize` and `.imageZYJTransformer` will effect the generated cache key, using this if you have those context associated.
*/
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url context:(nullable ZYJSDWebImageContext *)context;

@end
