//
//  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//

#import "DebugUIMessagesAssetLoader.h"
#import <AFNetworking/AFHTTPSessionManager.h>
#import <AFNetworking/AFNetworking.h>
#import <PromiseKit/AnyPromise.h>
#import <SessionCoreKit/Randomness.h>
#import <SessionServiceKit/MIMETypeUtil.h>
#import <SessionServiceKit/OWSFileSystem.h>
#import <SessionServiceKit/SessionServiceKit-Swift.h>
#import <SessionServiceKit/TSAttachment.h>

NS_ASSUME_NONNULL_BEGIN

@implementation DebugUIMessagesAssetLoader

- (NSString *)labelEmoji
{
    return [TSAttachment emojiForMimeType:self.mimeType];
}

+ (DebugUIMessagesAssetLoader *)fakeAssetLoaderWithUrl:(NSString *)fileUrl mimeType:(NSString *)mimeType
{
    OWSAssertDebug(fileUrl.length > 0);
    OWSAssertDebug(mimeType.length > 0);

    DebugUIMessagesAssetLoader *instance = [DebugUIMessagesAssetLoader new];
    instance.mimeType = mimeType;
    instance.filename = [NSURL URLWithString:fileUrl].lastPathComponent;
    __weak DebugUIMessagesAssetLoader *weakSelf = instance;
    instance.prepareBlock = ^(ActionSuccessBlock success, ActionFailureBlock failure) {
        [weakSelf ensureURLAssetLoaded:fileUrl success:success failure:failure];
    };
    return instance;
}

- (void)ensureURLAssetLoaded:(NSString *)fileUrl success:(ActionSuccessBlock)success failure:(ActionFailureBlock)failure
{
    OWSAssertDebug(success);
    OWSAssertDebug(failure);
    OWSAssertDebug(self.filename.length > 0);
    OWSAssertDebug(self.mimeType.length > 0);

    if (self.filePath) {
        success();
        return;
    }

    // Use a predictable file path so that we reuse the cache between app launches.
    NSString *temporaryDirectory = OWSTemporaryDirectory();
    NSString *cacheDirectory = [temporaryDirectory stringByAppendingPathComponent:@"cached_random_files"];
    [OWSFileSystem ensureDirectoryExists:cacheDirectory];
    NSString *filePath = [cacheDirectory stringByAppendingPathComponent:self.filename];
    if ([NSFileManager.defaultManager fileExistsAtPath:filePath]) {
        self.filePath = filePath;
        return success();
    }

    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
    sessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
    OWSAssertDebug(sessionManager.responseSerializer);
    [sessionManager GET:fileUrl
        parameters:nil
        progress:nil
        success:^(NSURLSessionDataTask *task, NSData *_Nullable responseObject) {
            if ([responseObject writeToFile:filePath atomically:YES]) {
                self.filePath = filePath;
                OWSAssertDebug([NSFileManager.defaultManager fileExistsAtPath:filePath]);
                success();
            } else {
                OWSFailDebug(@"Error write url response [%@]: %@", fileUrl, filePath);
                failure();
            }
        }
        failure:^(NSURLSessionDataTask *_Nullable task, NSError *requestError) {
            OWSFailDebug(@"Error downloading url[%@]: %@", fileUrl, requestError);
            failure();
        }];
}

#pragma mark -

+ (DebugUIMessagesAssetLoader *)fakePngAssetLoaderWithImageSize:(CGSize)imageSize
                                                backgroundColor:(UIColor *)backgroundColor
                                                      textColor:(UIColor *)textColor
                                                          label:(NSString *)label
{
    OWSAssertDebug(imageSize.width > 0);
    OWSAssertDebug(imageSize.height > 0);
    OWSAssertDebug(backgroundColor);
    OWSAssertDebug(textColor);
    OWSAssertDebug(label.length > 0);

    DebugUIMessagesAssetLoader *instance = [DebugUIMessagesAssetLoader new];
    instance.mimeType = OWSMimeTypeImagePng;
    instance.filename = @"image.png";
    __weak DebugUIMessagesAssetLoader *weakSelf = instance;
    instance.prepareBlock = ^(ActionSuccessBlock success, ActionFailureBlock failure) {
        [weakSelf ensurePngAssetLoaded:imageSize
                       backgroundColor:backgroundColor
                             textColor:textColor
                                 label:label
                               success:success
                               failure:failure];
    };
    return instance;
}

- (void)ensurePngAssetLoaded:(CGSize)imageSize
             backgroundColor:(UIColor *)backgroundColor
                   textColor:(UIColor *)textColor
                       label:(NSString *)label
                     success:(ActionSuccessBlock)success
                     failure:(ActionFailureBlock)failure
{
    OWSAssertDebug(success);
    OWSAssertDebug(failure);
    OWSAssertDebug(self.filename.length > 0);
    OWSAssertDebug(self.mimeType.length > 0);
    OWSAssertDebug(imageSize.width > 0 && imageSize.height > 0);
    OWSAssertDebug(backgroundColor);
    OWSAssertDebug(textColor);
    OWSAssertDebug(label.length > 0);

    if (self.filePath) {
        success();
        return;
    }

    @autoreleasepool {
        NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:@"png"];
        UIImage *image =
            [self createRandomPngWithSize:imageSize backgroundColor:backgroundColor textColor:textColor label:label];
        NSData *pngData = UIImagePNGRepresentation(image);
        [pngData writeToFile:filePath atomically:YES];
        self.filePath = filePath;
        OWSAssertDebug([NSFileManager.defaultManager fileExistsAtPath:filePath]);
        success();
    }
}

- (nullable UIImage *)createRandomPngWithSize:(CGSize)imageSize
                              backgroundColor:(UIColor *)backgroundColor
                                    textColor:(UIColor *)textColor
                                        label:(NSString *)label
{
    OWSAssertDebug(imageSize.width > 0 && imageSize.height > 0);
    OWSAssertDebug(backgroundColor);
    OWSAssertDebug(textColor);
    OWSAssertDebug(label.length > 0);

    @autoreleasepool {
        imageSize.width /= UIScreen.mainScreen.scale;
        imageSize.height /= UIScreen.mainScreen.scale;

        CGRect frame = CGRectZero;
        frame.size = imageSize;
        CGFloat smallDimension = MIN(imageSize.width, imageSize.height);
        UIFont *font = [UIFont boldSystemFontOfSize:smallDimension * 0.5f];
        NSDictionary *textAttributes = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : textColor };

        CGRect textFrame =
            [label boundingRectWithSize:frame.size
                                options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
                             attributes:textAttributes
                                context:nil];

        UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale);
        CGContextRef context = UIGraphicsGetCurrentContext();

        CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
        CGContextFillRect(context, frame);
        [label drawAtPoint:CGPointMake(CGRectGetMidX(frame) - CGRectGetMidX(textFrame),
                               CGRectGetMidY(frame) - CGRectGetMidY(textFrame))
            withAttributes:textAttributes];

        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        return image;
    }
}

#pragma mark -

+ (DebugUIMessagesAssetLoader *)fakeRandomAssetLoaderWithLength:(NSUInteger)dataLength mimeType:(NSString *)mimeType
{
    OWSAssertDebug(dataLength > 0);
    OWSAssertDebug(mimeType.length > 0);

    DebugUIMessagesAssetLoader *instance = [DebugUIMessagesAssetLoader new];
    instance.mimeType = mimeType;
    NSString *fileExtension = [MIMETypeUtil fileExtensionForMIMEType:mimeType];
    OWSAssertDebug(fileExtension.length > 0);
    instance.filename = [@"attachment" stringByAppendingPathExtension:fileExtension];
    __weak DebugUIMessagesAssetLoader *weakSelf = instance;
    instance.prepareBlock = ^(ActionSuccessBlock success, ActionFailureBlock failure) {
        [weakSelf ensureRandomAssetLoaded:dataLength success:success failure:failure];
    };
    return instance;
}

- (void)ensureRandomAssetLoaded:(NSUInteger)dataLength
                        success:(ActionSuccessBlock)success
                        failure:(ActionFailureBlock)failure
{
    OWSAssertDebug(dataLength > 0);
    OWSAssertDebug(dataLength < INT_MAX);
    OWSAssertDebug(success);
    OWSAssertDebug(failure);
    OWSAssertDebug(self.filename.length > 0);
    OWSAssertDebug(self.mimeType.length > 0);

    if (self.filePath) {
        success();
        return;
    }

    @autoreleasepool {
        NSString *fileExtension = [MIMETypeUtil fileExtensionForMIMEType:self.mimeType];
        OWSAssertDebug(fileExtension.length > 0);
        NSData *data = [Randomness generateRandomBytes:(int)dataLength];
        OWSAssertDebug(data);
        NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:fileExtension];
        BOOL didWrite = [data writeToFile:filePath atomically:YES];
        OWSAssertDebug(didWrite);
        self.filePath = filePath;
        OWSAssertDebug([NSFileManager.defaultManager fileExistsAtPath:filePath]);
    }

    success();
}

#pragma mark -

+ (DebugUIMessagesAssetLoader *)fakeMissingAssetLoaderWithMimeType:(NSString *)mimeType
{
    OWSAssertDebug(mimeType.length > 0);

    DebugUIMessagesAssetLoader *instance = [DebugUIMessagesAssetLoader new];
    instance.mimeType = mimeType;
    NSString *fileExtension = [MIMETypeUtil fileExtensionForMIMEType:mimeType];
    OWSAssertDebug(fileExtension.length > 0);
    instance.filename = [@"attachment" stringByAppendingPathExtension:fileExtension];
    __weak DebugUIMessagesAssetLoader *weakSelf = instance;
    instance.prepareBlock = ^(ActionSuccessBlock success, ActionFailureBlock failure) {
        [weakSelf ensureMissingAssetLoaded:success failure:failure];
    };
    return instance;
}

- (void)ensureMissingAssetLoaded:(ActionSuccessBlock)success failure:(ActionFailureBlock)failure
{
    OWSAssertDebug(success);
    OWSAssertDebug(failure);
    OWSAssertDebug(self.filename.length > 0);
    OWSAssertDebug(self.mimeType.length > 0);

    if (self.filePath) {
        success();
        return;
    }

    NSString *fileExtension = [MIMETypeUtil fileExtensionForMIMEType:self.mimeType];
    OWSAssertDebug(fileExtension.length > 0);
    NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:fileExtension];
    BOOL didCreate = [NSFileManager.defaultManager createFileAtPath:filePath contents:nil attributes:nil];
    OWSAssertDebug(didCreate);
    self.filePath = filePath;
    OWSAssertDebug([NSFileManager.defaultManager fileExistsAtPath:filePath]);

    success();
}

#pragma mark -

+ (DebugUIMessagesAssetLoader *)fakeOversizeTextAssetLoader
{
    DebugUIMessagesAssetLoader *instance = [DebugUIMessagesAssetLoader new];
    instance.mimeType = OWSMimeTypeOversizeTextMessage;
    instance.filename = @"attachment.txt";
    __weak DebugUIMessagesAssetLoader *weakSelf = instance;
    instance.prepareBlock = ^(ActionSuccessBlock success, ActionFailureBlock failure) {
        [weakSelf ensureOversizeTextAssetLoaded:success failure:failure];
    };
    return instance;
}

- (void)ensureOversizeTextAssetLoaded:(ActionSuccessBlock)success failure:(ActionFailureBlock)failure
{
    OWSAssertDebug(success);
    OWSAssertDebug(failure);
    OWSAssertDebug(self.filename.length > 0);
    OWSAssertDebug(self.mimeType.length > 0);

    if (self.filePath) {
        success();
        return;
    }

    NSMutableString *message = [NSMutableString new];
    for (NSUInteger i = 0; i < 32; i++) {
        [message appendString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rutrum, nulla "
                              @"vitae pretium hendrerit, tellus turpis pharetra libero, vitae sodales tortor ante vel "
                              @"sem. Fusce sed nisl a lorem gravida tincidunt. Suspendisse efficitur non quam ac "
                              @"sodales. Aenean ut velit maximus, posuere sem a, accumsan nunc. Donec ullamcorper "
                              @"turpis lorem. Quisque dignissim purus eu placerat ultricies. Proin at urna eget mi "
                              @"semper congue. Aenean non elementum ex. Praesent pharetra quam at sem vestibulum, "
                              @"vestibulum ornare dolor elementum. Vestibulum massa tortor, scelerisque sit amet "
                              @"pulvinar a, rhoncus vitae nisl. Sed mi nunc, tempus at varius in, malesuada vitae "
                              @"dui. Vivamus efficitur pulvinar erat vitae congue. Proin vehicula turpis non felis "
                              @"congue facilisis. Nullam aliquet dapibus ligula ac mollis. Etiam sit amet posuere "
                              @"lorem, in rhoncus nisi.\n\n"];
    }

    NSString *fileExtension = @"txt";
    NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:fileExtension];
    NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
    OWSAssertDebug(data);
    BOOL didWrite = [data writeToFile:filePath atomically:YES];
    OWSAssertDebug(didWrite);
    self.filePath = filePath;
    OWSAssertDebug([NSFileManager.defaultManager fileExistsAtPath:filePath]);

    success();
}

#pragma mark -

+ (DebugUIMessagesAssetLoader *)fakeOversizeTextAssetLoaderWithText:(NSString *)text
{
    DebugUIMessagesAssetLoader *instance = [DebugUIMessagesAssetLoader new];
    instance.mimeType = OWSMimeTypeOversizeTextMessage;
    instance.filename = @"attachment.txt";
    __weak DebugUIMessagesAssetLoader *weakSelf = instance;
    instance.prepareBlock = ^(ActionSuccessBlock success, ActionFailureBlock failure) {
        [weakSelf ensureOversizeTextAssetLoadedWithText:text success:success failure:failure];
    };
    return instance;
}

- (void)ensureOversizeTextAssetLoadedWithText:(NSString *)text
                                      success:(ActionSuccessBlock)success
                                      failure:(ActionFailureBlock)failure
{
    OWSAssertDebug(success);
    OWSAssertDebug(failure);
    OWSAssertDebug(self.filename.length > 0);
    OWSAssertDebug(self.mimeType.length > 0);

    if (self.filePath) {
        success();
        return;
    }

    NSString *fileExtension = @"txt";
    NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:fileExtension];
    NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding];
    OWSAssertDebug(data);
    BOOL didWrite = [data writeToFile:filePath atomically:YES];
    OWSAssertDebug(didWrite);
    self.filePath = filePath;
    OWSAssertDebug([NSFileManager.defaultManager fileExistsAtPath:filePath]);

    success();
}

#pragma mark -

+ (instancetype)jpegInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader
            fakeAssetLoaderWithUrl:@"https://s3.amazonaws.com/ows-data/example_attachment_media/random-jpg.JPG"
                          mimeType:OWSMimeTypeImageJpeg];
    });
    return instance;
}

+ (instancetype)gifInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader
            fakeAssetLoaderWithUrl:@"https://s3.amazonaws.com/ows-data/example_attachment_media/random-gif.gif"
                          mimeType:OWSMimeTypeImageGif];
    });
    return instance;
}

+ (instancetype)largeGifInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance =
            [DebugUIMessagesAssetLoader fakeAssetLoaderWithUrl:@"https://i.giphy.com/media/LTw0F3GAdaao8/source.gif"
                                                      mimeType:OWSMimeTypeImageGif];
    });
    return instance;
}

+ (instancetype)mp3Instance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader
            fakeAssetLoaderWithUrl:@"https://s3.amazonaws.com/ows-data/example_attachment_media/random-mp3.mp3"
                          mimeType:@"audio/mp3"];
    });
    return instance;
}

+ (instancetype)mp4Instance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader
            fakeAssetLoaderWithUrl:@"https://s3.amazonaws.com/ows-data/example_attachment_media/random-mp4.mp4"
                          mimeType:@"video/mp4"];
    });
    return instance;
}

+ (instancetype)compactPortraitPngInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(60, 100)
                                                               backgroundColor:[UIColor blueColor]
                                                                     textColor:[UIColor whiteColor]
                                                                         label:@"P"];
    });
    return instance;
}

+ (instancetype)compactLandscapePngInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(100, 60)
                                                               backgroundColor:[UIColor greenColor]
                                                                     textColor:[UIColor whiteColor]
                                                                         label:@"L"];
    });
    return instance;
}

+ (instancetype)tallPortraitPngInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(10, 100)
                                                               backgroundColor:[UIColor yellowColor]
                                                                     textColor:[UIColor whiteColor]
                                                                         label:@"P"];
    });
    return instance;
}

+ (instancetype)wideLandscapePngInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(100, 10)
                                                               backgroundColor:[UIColor purpleColor]
                                                                     textColor:[UIColor whiteColor]
                                                                         label:@"L"];
    });
    return instance;
}

+ (instancetype)largePngInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(4000, 4000)
                                                               backgroundColor:[UIColor brownColor]
                                                                     textColor:[UIColor whiteColor]
                                                                         label:@"B"];
    });
    return instance;
}

+ (instancetype)tinyPngInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(2, 2)
                                                               backgroundColor:[UIColor cyanColor]
                                                                     textColor:[UIColor whiteColor]
                                                                         label:@"T"];
    });
    return instance;
}

+ (instancetype)pngInstanceWithSize:(CGSize)size
                    backgroundColor:(UIColor *)backgroundColor
                          textColor:(UIColor *)textColor
                              label:(NSString *)label
{
    return [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:size
                                                       backgroundColor:backgroundColor
                                                             textColor:textColor
                                                                 label:label];
}

+ (instancetype)tinyPdfInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader fakeRandomAssetLoaderWithLength:256 mimeType:@"application/pdf"];
    });
    return instance;
}

+ (instancetype)largePdfInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance =
            [DebugUIMessagesAssetLoader fakeRandomAssetLoaderWithLength:4 * 1024 * 1024 mimeType:@"application/pdf"];
    });
    return instance;
}

+ (instancetype)missingPngInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader fakeMissingAssetLoaderWithMimeType:OWSMimeTypeImagePng];
    });
    return instance;
}

+ (instancetype)missingPdfInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader fakeMissingAssetLoaderWithMimeType:@"application/pdf"];
    });
    return instance;
}

+ (instancetype)oversizeTextInstance
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader fakeOversizeTextAssetLoader];
    });
    return instance;
}

+ (instancetype)oversizeTextInstanceWithText:(NSString *)text
{
    static DebugUIMessagesAssetLoader *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [DebugUIMessagesAssetLoader fakeOversizeTextAssetLoaderWithText:text];
    });
    return instance;
}

#pragma mark -

+ (void)prepareAssetLoaders:(NSArray<DebugUIMessagesAssetLoader *> *)assetLoaders
                    success:(dispatch_block_t)success
                    failure:(dispatch_block_t)failure
{

    NSMutableArray<AnyPromise *> *promises = [NSMutableArray array];
    NSMutableArray<NSError *> *errors = [NSMutableArray array];

    for (DebugUIMessagesAssetLoader *assetLoader in assetLoaders) {
        // Use chained promises to make the code more readable.
        AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
            assetLoader.prepareBlock(
                ^{
                    // The value doesn't matter, we just need any non-NSError value.
                    resolve(@(1));
                },
                ^{
                    NSError *error =
                        [NSError errorWithDomain:@"DebugUI"
                                            code:0
                                        userInfo:@{ NSLocalizedDescriptionKey : @"Could not prepare fake assets." }];
                    @synchronized(errors) {
                        [errors addObject:error];
                    }
                    resolve(error);
                });
        }];
        [promises addObject:promise];
    }

    // We could use PMKJoin() or PMKWhen().
    [PMKJoin(promises)
            .then(^(id value) {
                success();
            })
            .catch(^(id error) {
                OWSLogError(@"Could not prepare fake asset loaders: %@.", error);
                failure();
            }) retainUntilComplete];
}

@end

NS_ASSUME_NONNULL_END
