//
//  ZYJQNUploader.h
//  ZYJQiniuSDK
//
//  Created by bailong on 14-9-28.
//  Copyright (c) 2014年 Qiniu. All rights reserved.
//

#import <Foundation/Foundation.h>

#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import <MobileCoreServices/MobileCoreServices.h>
#import <UIKit/UIKit.h>

#if !TARGET_OS_MACCATALYST
#import <AssetsLibrary/AssetsLibrary.h>
#import "ZYJQNALAssetFile.h"
#endif

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
#import "ZYJQNPHAssetFile.h"
#import <Photos/Photos.h>
#endif

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
#import "ZYJQNPHAssetResource.h"
#endif

#else
#import <CoreServices/CoreServices.h>
#endif

#import "ZYJQNAsyncRun.h"
#import "ZYJQNConfiguration.h"
#import "ZYJQNCrc32.h"
#import "ZYJQNFile.h"
#import "ZYJQNFormUpload.h"
#import "ZYJQNResponseInfo.h"
#import "ZYJQNResumeUpload.h"
#import "ZYJQNSessionManager.h"
#import "ZYJQNUpToken.h"
#import "ZYJQNUploadManager.h"
#import "ZYJQNUploadOption+Private.h"
#import "ZYJQNConcurrentResumeUpload.h"
#import "ZYJQNUploadInfoCollector.h"

#import "ZYJQNDnsPrefetcher.h"
#import "ZYJQNZone.h"

@interface ZYJQNUploadManager ()
@property (nonatomic) ZYJQNSessionManager *sessionManager;
@property (nonatomic) ZYJQNConfiguration *config;
@end

@implementation ZYJQNUploadManager

- (instancetype)init {
    return [self initWithConfiguration:nil];
}

- (instancetype)initWithRecorder:(id<ZYJQNRecorderDelegate>)recorder {
    return [self initWithRecorder:recorder recorderKeyGenerator:nil];
}

- (instancetype)initWithRecorder:(id<ZYJQNRecorderDelegate>)recorder
            recorderKeyGenerator:(ZYJQNRecorderKeyGenerator)recorderKeyGenerator {
    ZYJQNConfiguration *config = [ZYJQNConfiguration build:^(ZYJQNConfigurationBuilder *builder) {
        builder.recorder = recorder;
        builder.recorderKeyGen = recorderKeyGenerator;
    }];
    return [self initWithConfiguration:config];
}

- (instancetype)initWithConfiguration:(ZYJQNConfiguration *)config {
    if (self = [super init]) {
        if (config == nil) {
            config = [ZYJQNConfiguration build:^(ZYJQNConfigurationBuilder *builder){
            }];
        }
        _config = config;
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
        _sessionManager = [[ZYJQNSessionManager alloc] initWithProxy:config.proxy timeout:config.timeoutInterval urlConverter:config.converter];
#endif
        
        [[ZYJQNTransactionManager shared] addDnsLocalLoadTransaction];
    }
    return self;
}

+ (instancetype)sharedInstanceWithConfiguration:(ZYJQNConfiguration *)config {
    static ZYJQNUploadManager *sharedInstance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] initWithConfiguration:config];
    });

    return sharedInstance;
}

+ (BOOL)checkAndNotifyError:(NSString *)key
                      token:(NSString *)token
                      input:(NSObject *)input
                 identifier:(NSString *)identifier
                   complete:(ZYJQNUpCompletionHandler)completionHandler {
    NSString *desc = nil;
    if (completionHandler == nil) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:@"no completionHandler"
                                     userInfo:nil];
        return YES;
    }
    if (input == nil) {
        desc = @"no input data";
    } else if (token == nil || [token isEqual:[NSNull null]] || [token isEqualToString:@""]) {
        desc = @"no token";
    }
    if (desc != nil) {
        ZYJQNAsyncRunInMain(^{
            ZYJQNResponseInfo *info = [Collector completeWithInvalidArgument:desc identifier:identifier];
            completionHandler(info, key, nil);
        });
        return YES;
    }
    return NO;
}

- (void)putData:(NSData *)data
            key:(NSString *)key
          token:(NSString *)token
       complete:(ZYJQNUpCompletionHandler)completionHandler
         option:(ZYJQNUploadOption *)option {
    
    NSString *identifier = [[NSUUID UUID] UUIDString];
    [Collector registerWithIdentifier:identifier token:token];
    [self putData:data fileName:nil key:key token:token identifier:identifier complete:completionHandler option:option];
}

- (void)putData:(NSData *)data
       fileName:(NSString *)fileName
            key:(NSString *)key
          token:(NSString *)token
     identifier:(NSString *)identifier
       complete:(ZYJQNUpCompletionHandler)completionHandler
         option:(ZYJQNUploadOption *)option {

    if ([ZYJQNUploadManager checkAndNotifyError:key token:token input:data identifier:identifier complete:completionHandler]) {
        return;
    }
    
    [[ZYJQNTransactionManager shared] addDnsCheckAndPrefetchTransaction:self.config.zone
                                                               token:token];

    ZYJQNUpToken *t = [ZYJQNUpToken parse:token];
    if (t == nil) {
        ZYJQNAsyncRunInMain(^{
            ZYJQNResponseInfo *info = [Collector completeWithInvalidToken:@"invalid token" identifier:identifier];
            completionHandler(info, key, nil);
        });
        return;
    } else {
        [Collector update:ZYJCK_bucket value:t.bucket identifier:identifier];
        [Collector update:ZYJCK_key value:key identifier:identifier];
    }
    
    [_config.zone preQuery:t on:^(int code, ZYJQNHttpResponseInfo *httpResponseInfo) {
        [Collector addRequestWithType:ZYJQNRequestType_ucQuery httpResponseInfo:httpResponseInfo fileOffset:ZYJQN_IntNotSet targetRegionId:nil currentRegionId:nil identifier:identifier];
        if (code != 0) {
            ZYJQNAsyncRunInMain(^{
                ZYJQNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:identifier];
                completionHandler(info, key, nil);
            });
            return;
        }
        if ([data length] == 0) {
            ZYJQNAsyncRunInMain(^{
                ZYJQNResponseInfo *info = [Collector completeWithZeroData:nil identifier:identifier];
                completionHandler(info, key, nil);
            });
            return;
        }
        ZYJQNUpCompletionHandler complete = ^(ZYJQNResponseInfo *info, NSString *key, NSDictionary *resp) {
            ZYJQNAsyncRunInMain(^{
                completionHandler(info, key, resp);
            });
        };
        ZYJQNFormUpload *up = [[ZYJQNFormUpload alloc]
                     initWithData:data
                          withKey:key
                        withFileName:fileName
                        withToken:t
                            withIdentifier:(NSString *)identifier
            withCompletionHandler:complete
                       withOption:option
                            withSessionManager:self.sessionManager
                withConfiguration:self.config];
        ZYJQNAsyncRun(^{
            [up put];
        });
    }];
}

- (void)putFileInternal:(id<ZYJQNFileDelegate>)file
                    key:(NSString *)key
                  token:(NSString *)token
             identifier:(NSString *)identifier
               complete:(ZYJQNUpCompletionHandler)completionHandler
                 option:(ZYJQNUploadOption *)option {
    
    @autoreleasepool {
        ZYJQNUpToken *t = [ZYJQNUpToken parse:token];
        if (t == nil) {
            ZYJQNAsyncRunInMain(^{
                ZYJQNResponseInfo *info = [Collector completeWithInvalidToken:@"invalid token" identifier:identifier];
                completionHandler(info, key, nil);
            });
            return;
        } else {
            [Collector update:ZYJCK_bucket value:t.bucket identifier:identifier];
            [Collector update:ZYJCK_key value:key identifier:identifier];
        }

        [[ZYJQNTransactionManager shared] addDnsCheckAndPrefetchTransaction:self.config.zone
                                                                   token:token];
        
        [_config.zone preQuery:t on:^(int code, ZYJQNHttpResponseInfo *httpResponseInfo) {
            [Collector addRequestWithType:ZYJQNRequestType_ucQuery httpResponseInfo:httpResponseInfo fileOffset:ZYJQN_IntNotSet targetRegionId:nil currentRegionId:nil identifier:identifier];
            if (code != 0) {
                ZYJQNAsyncRunInMain(^{
                    ZYJQNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:identifier];
                    completionHandler(info, key, nil);
                });
                return;
            }
            ZYJQNUpCompletionHandler complete = ^(ZYJQNResponseInfo *info, NSString *key, NSDictionary *resp) {
                [file close];
                ZYJQNAsyncRunInMain(^{
                    completionHandler(info, key, resp);
                });
            };

            if ([file size] <= self.config.putThreshold) {
                NSError *error;
                NSData *data = [file readAllWithError:&error];
                if (error) {
                    ZYJQNAsyncRunInMain(^{
                        ZYJQNResponseInfo *info = [Collector completeWithLocalIOError:error identifier:identifier];
                        completionHandler(info, key, nil);
                    });
                    return;
                }
                NSString *fileName = [[file path] lastPathComponent];
                [self putData:data fileName:fileName key:key token:token identifier:identifier complete:completionHandler option:option];
                return;
            }

            NSString *recorderKey = key;
            if (self.config.recorder != nil && self.config.recorderKeyGen != nil) {
                recorderKey = self.config.recorderKeyGen(key, [file path]);
            }
            
            NSLog(@"recorder %@", self.config.recorder);
            
            if (self.config.useConcurrentResumeUpload) {
                ZYJQNConcurrentResumeUpload *up = [[ZYJQNConcurrentResumeUpload alloc]
                                                initWithFile:file
                                                withKey:key
                                                withToken:t
                                                withIdentifier:identifier
                                                withRecorder:self.config.recorder
                                                withRecorderKey:recorderKey
                                                withSessionManager:self.sessionManager
                                                withCompletionHandler:completionHandler
                                                withOption:option
                                                withConfiguration:self.config];
                ZYJQNAsyncRun(^{
                    [up run];
                });
            } else {
                ZYJQNResumeUpload *up = [[ZYJQNResumeUpload alloc]
                                      initWithFile:file
                                      withKey:key
                                      withToken:t
                                      withIdentifier:identifier
                                      withCompletionHandler:complete
                                      withOption:option
                                      withRecorder:self.config.recorder
                                      withRecorderKey:recorderKey
                                      withSessionManager:self.sessionManager
                                      withConfiguration:self.config];
                ZYJQNAsyncRun(^{
                    [up run];
                });
            }
        }];
    }
}

- (void)putFile:(NSString *)filePath
            key:(NSString *)key
          token:(NSString *)token
       complete:(ZYJQNUpCompletionHandler)completionHandler
         option:(ZYJQNUploadOption *)option {
    
    NSString *identifier = [[NSUUID UUID] UUIDString];
    [Collector registerWithIdentifier:identifier token:token];
    if ([ZYJQNUploadManager checkAndNotifyError:key token:token input:filePath identifier:identifier complete:completionHandler]) {
        return;
    }

    @autoreleasepool {
        NSError *error = nil;
        __block ZYJQNFile *file = [[ZYJQNFile alloc] init:filePath error:&error];
        if (error) {
            ZYJQNAsyncRunInMain(^{
                ZYJQNResponseInfo *info = [Collector completeWithFileError:error identifier:identifier];
                completionHandler(info, key, nil);
            });
            return;
        }
        [self putFileInternal:file key:key token:token identifier:identifier complete:completionHandler option:option];
    }
}

#if !TARGET_OS_MACCATALYST
- (void)putALAsset:(ALAsset *)asset
               key:(NSString *)key
             token:(NSString *)token
          complete:(ZYJQNUpCompletionHandler)completionHandler
            option:(ZYJQNUploadOption *)option {
#if __IPHONE_OS_VERSION_MIN_REQUIRED
    NSString *identifier = [[NSUUID UUID] UUIDString];
    [Collector registerWithIdentifier:identifier token:token];
    
    if ([ZYJQNUploadManager checkAndNotifyError:key token:token input:asset identifier:identifier complete:completionHandler]) {
        return;
    }

    @autoreleasepool {
        NSError *error = nil;
        __block ZYJQNALAssetFile *file = [[ZYJQNALAssetFile alloc] init:asset error:&error];
        if (error) {
            ZYJQNAsyncRunInMain(^{
                ZYJQNResponseInfo *info = [Collector completeWithFileError:error identifier:identifier];
                completionHandler(info, key, nil);
            });
            return;
        }
        [self putFileInternal:file key:key token:token identifier:identifier complete:completionHandler option:option];
    }
#endif
}
#endif

- (void)putPHAsset:(PHAsset *)asset
               key:(NSString *)key
             token:(NSString *)token
          complete:(ZYJQNUpCompletionHandler)completionHandler
            option:(ZYJQNUploadOption *)option {
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90100)
    NSString *identifier = [[NSUUID UUID] UUIDString];
    [Collector registerWithIdentifier:identifier token:token];
    
    if ([ZYJQNUploadManager checkAndNotifyError:key token:token input:asset identifier:identifier complete:completionHandler]) {
        return;
    }

    @autoreleasepool {
        NSError *error = nil;
        __block ZYJQNPHAssetFile *file = [[ZYJQNPHAssetFile alloc] init:asset error:&error];
        if (error) {
            ZYJQNAsyncRunInMain(^{
                ZYJQNResponseInfo *info = [Collector completeWithFileError:error identifier:identifier];
                completionHandler(info, key, nil);
            });
            return;
        }
        [self putFileInternal:file key:key token:token identifier:identifier complete:completionHandler option:option];
    }
#endif
}

- (void)putPHAssetResource:(PHAssetResource *)assetResource
                       key:(NSString *)key
                     token:(NSString *)token
                  complete:(ZYJQNUpCompletionHandler)completionHandler
                    option:(ZYJQNUploadOption *)option {
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000)
    NSString *identifier = [[NSUUID UUID] UUIDString];
    [Collector registerWithIdentifier:identifier token:token];
    
    if ([ZYJQNUploadManager checkAndNotifyError:key token:token input:assetResource identifier:identifier complete:completionHandler]) {
        return;
    }
    @autoreleasepool {
        NSError *error = nil;
        __block ZYJQNPHAssetResource *file = [[ZYJQNPHAssetResource alloc] init:assetResource error:&error];
        if (error) {
            ZYJQNAsyncRunInMain(^{
                ZYJQNResponseInfo *info = [Collector completeWithFileError:error identifier:identifier];
                completionHandler(info, key, nil);
            });
            return;
        }
        [self putFileInternal:file key:key token:token identifier:identifier complete:completionHandler option:option];
    }
#endif
}

@end
