//
//  ZYJQNHttpClient.m
//  AppTest
//
//  Created by yangsen on 2020/4/7.
//  Copyright © 2020 com.qiniu. All rights reserved.
//

#import "ZYJQNCFHttpClient.h"
#import "NSURLRequest+ZYJQNRequest.h"

@interface ZYJQNCFHttpClient()<NSStreamDelegate>

@property(nonatomic, strong)NSMutableURLRequest *request;
@property(nonatomic, assign)BOOL isReadResponseHeader;
@property(nonatomic, assign)BOOL isInputStreamEvaluated;
@property(nonatomic, strong)NSInputStream *inputStream;
@property(nonatomic, strong)NSRunLoop *inputStreamRunLoop;

// 模拟上传进度
@property(nonatomic, strong)NSTimer *progressTimer; // 进度定时器
@property(nonatomic, assign)int64_t totalBytesSent; // 已上传大小
@property(nonatomic, assign)int64_t bytesSent; // 模拟每次上传大小
@property(nonatomic, assign)int64_t maxBytesSent; // 模拟上传最大值
@property(nonatomic, assign)int64_t totalBytesExpectedToSend; // 总大小

@end
@implementation ZYJQNCFHttpClient

+ (instancetype)client:(NSURLRequest *)request{
    if (!request) {
        return nil;
    }
    
    ZYJQNCFHttpClient *client = [[ZYJQNCFHttpClient alloc] init];
    [client setup:request];
    return client;
}

- (void)setup:(NSURLRequest *)request{
    
    @autoreleasepool {
        self.request = [request mutableCopy];
        NSInputStream *inputStream = [self createInputStream:self.request];
        
        NSString *host = [self.request ZYJQN_domain];
        if ([self.request ZYJQN_isHttps]) {
           [self setInputStreamSNI:inputStream sni:host];
        }
        
        [self setupProgress];
        
        self.inputStream = inputStream;
        
    }
}

- (void)startLoading{

    [self openInputStream];
    [self startProgress];
}

- (void)stopLoading{
    
    [self closeInputStream];
    [self endProgress:YES];
}

//MARK: -- rquest -> stream
- (NSInputStream *)createInputStream:(NSURLRequest *)urlRequest{
    
    CFStringRef urlString = (__bridge CFStringRef) [urlRequest.URL absoluteString];
    CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault,
                                         urlString,
                                         NULL);
    CFStringRef httpMethod = (__bridge CFStringRef) urlRequest.HTTPMethod;
    CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault,
                                                          httpMethod,
                                                          url,
                                                          kCFHTTPVersion1_1);
    CFRelease(url);
    
    
    NSDictionary *headFieldInfo = self.request.ZYJQN_allHTTPHeaderFields;
    for (NSString *headerField in headFieldInfo) {
        CFStringRef headerFieldP = (__bridge CFStringRef)headerField;
        CFStringRef headerFieldValueP = (__bridge CFStringRef)(headFieldInfo[headerField]);
        CFHTTPMessageSetHeaderFieldValue(request, headerFieldP, headerFieldValueP);
    }
    

    NSData *httpBody = [self.request ZYJQN_getHttpBody];
    if (httpBody) {
        CFDataRef bodyData = (__bridge CFDataRef) httpBody;
        CFHTTPMessageSetBody(request, bodyData);
    }
    
    CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
    NSInputStream *inputStream = (__bridge_transfer NSInputStream *) readStream;
    
    CFRelease(request);
    
    return inputStream;
}

- (void)setInputStreamSNI:(NSInputStream *)inputStream sni:(NSString *)sni{
    if (!sni || sni.length == 0) {
        return;
    }
    NSMutableDictionary *settings = [NSMutableDictionary dictionary];
    [settings setObject:NSStreamSocketSecurityLevelNegotiatedSSL
                 forKey:NSStreamSocketSecurityLevelKey];
    [settings setObject:sni
                 forKey:(NSString *)kCFStreamSSLPeerName];
    [inputStream setProperty:settings forKey:(NSString *)CFBridgingRelease(kCFStreamPropertySSLSettings)];
}


//MARK: -- stream action
- (void)openInputStream{
    if (!self.inputStreamRunLoop) {
        self.inputStreamRunLoop = [NSRunLoop currentRunLoop];
    }
    [self.inputStream scheduleInRunLoop:self.inputStreamRunLoop
                                forMode:NSRunLoopCommonModes];
    
    self.inputStream.delegate = self;
    [self.inputStream open];
}

- (void)closeInputStream {
    [self.inputStream removeFromRunLoop:self.inputStreamRunLoop forMode:NSRunLoopCommonModes];
    [self.inputStream setDelegate:nil];
    [self.inputStream close];
    self.inputStream = nil;
}

- (BOOL)shouldEvaluateInputStreamServerTrust{
    if (![self.request ZYJQN_isHttps] || self.isInputStreamEvaluated) {
        return NO;
    } else {
        return YES;
    }
}

- (void)evaluateInputStreamServerTrust{
    if (self.isInputStreamEvaluated) {
        return;
    }
    
    SecTrustRef trust = (__bridge SecTrustRef) [self.inputStream propertyForKey:(__bridge NSString *) kCFStreamPropertySSLPeerTrust];
    NSString *host = [self.request allHTTPHeaderFields][@"host"];
    if ([self delegate_evaluateServerTrust:trust forDomain:host]) {
        self.isInputStreamEvaluated = YES;
    } else {
        [self delegate_onError:[NSError errorWithDomain:@"CFNetwork SSLHandshake failed"
                                                   code:-9807
                                               userInfo:nil]];
    }
}

- (void)inputStreamGetAndNotifyHttpResponse{

    CFReadStreamRef readStream = (__bridge CFReadStreamRef)self.inputStream;
    CFHTTPMessageRef httpMessage = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
    
    CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(httpMessage);
    NSDictionary *headInfo = (__bridge_transfer NSDictionary *)headerFields;
    
    CFStringRef httpVersion = CFHTTPMessageCopyVersion(httpMessage);
    NSString *httpVersionInfo = (__bridge_transfer NSString *)httpVersion;
    
    CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(httpMessage);
    
    if (![self isHttpRedirectStatusCode:statusCode]) {
        NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL statusCode:statusCode HTTPVersion:httpVersionInfo headerFields:headInfo];
        [self delegate_onReceiveResponse:response];
    }
    
    CFRelease(httpMessage);
}

- (void)inputStreamGetAndNotifyHttpData{
    
    UInt8 buffer[16 * 1024];
    UInt8 *buf = NULL;
    NSUInteger length = 0;
    
    if (![self.inputStream getBuffer:&buf length:&length]) {
        NSInteger amount = [self.inputStream read:buffer maxLength:sizeof(buffer)];
        buf = buffer;
        length = amount;
    }
    
    NSData *data = [[NSData alloc] initWithBytes:buf length:length];
    [self delegate_didLoadData:data];
}

- (void)inputStreamDidLoadHttpResponse{
    
    [self delegate_didFinish];
}

- (BOOL)isInputStreamHttpResponseHeaderComplete{
    CFReadStreamRef readStream = (__bridge CFReadStreamRef)self.inputStream;
    CFHTTPMessageRef responseMessage = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
    BOOL isComplete = CFHTTPMessageIsHeaderComplete(responseMessage);
    CFRelease(responseMessage);
    return isComplete;
}

- (BOOL)shouldInputStreamRedirect{
    CFReadStreamRef readStream = (__bridge CFReadStreamRef)self.inputStream;
    CFHTTPMessageRef responseMessage = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
    CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(responseMessage);
    CFRelease(responseMessage);
    return [self isHttpRedirectStatusCode:statusCode];
}

- (BOOL)isHttpRedirectStatusCode:(NSInteger)code{
    if (code >= 300 && code < 400) {
        return YES;
    } else {
        return NO;
    }
}

- (void)inputStreamRedirect{
    CFReadStreamRef readStream = (__bridge CFReadStreamRef)self.inputStream;
    CFHTTPMessageRef responseMessage = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
    
    CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(responseMessage);
    NSDictionary *headInfo = (__bridge_transfer NSDictionary *)headerFields;
    
    NSString *urlString = headInfo[@"Location"];
    if (!urlString) {
        urlString = headInfo[@"location"];
    }
    if (!urlString) {
        return;
    }
    
    CFStringRef httpVersion = CFHTTPMessageCopyVersion(responseMessage);
    NSString *httpVersionString = (__bridge_transfer NSString *)httpVersion;
    
    CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(responseMessage);
    
    NSURL *url = [NSURL URLWithString:urlString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"GET";
    NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL
                                                          statusCode:statusCode
                                                         HTTPVersion:httpVersionString
                                                        headerFields:headInfo];
    
    [self delegate_redirectedToRequest:request redirectResponse:response];
    
    CFRelease(responseMessage);
}

//MARK: -- NSStreamDelegate
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
    @autoreleasepool {
        switch (eventCode) {
            case NSStreamEventHasBytesAvailable:{
                
                if (![self isInputStreamHttpResponseHeaderComplete]) {
                    break;
                }
                
                if ([self shouldEvaluateInputStreamServerTrust]) {
                    [self evaluateInputStreamServerTrust];
                }
                
                if (self.isReadResponseHeader == NO) {
                    self.isReadResponseHeader = YES;
                    [self inputStreamGetAndNotifyHttpResponse];
                }
                
                [self inputStreamGetAndNotifyHttpData];
            }
                break;
            case NSStreamEventHasSpaceAvailable:
                break;
            case NSStreamEventErrorOccurred:{
                [self endProgress: YES];
                [self closeInputStream];
                [self delegate_onError:[self.inputStream streamError]];
            }
                break;
            case NSStreamEventEndEncountered:{
                if ([self shouldInputStreamRedirect]) {
                    [self inputStreamRedirect];
                } else {
                    [self endProgress: NO];
                    [self inputStreamDidLoadHttpResponse];
                }
            }
                break;
            default:
                break;
        }
    }
}

//MARK: -- progress and timer action
- (void)setupProgress{
    self.bytesSent = 256 * 1024;
    self.totalBytesExpectedToSend = [self.request.ZYJQN_getHttpBody length];
    self.maxBytesSent = self.totalBytesExpectedToSend * 0.5;
}

- (void)startProgress{
    [self createTimer];
}

- (void)endProgress:(BOOL)hasError{
    
    [self invalidateTimer];
    
    if (!hasError) {
        [self delegate_didSendBodyData:self.totalBytesExpectedToSend - self.totalBytesSent
                        totalBytesSent:self.totalBytesExpectedToSend
              totalBytesExpectedToSend:self.totalBytesExpectedToSend];
    }
}

- (void)createTimer{
    
    if (_progressTimer) {
        [self invalidateTimer];
    }
    
    __weak typeof(self) weakSelf = self;
    NSTimer *timer = [NSTimer timerWithTimeInterval:0.5
                                             target:weakSelf
                                           selector:@selector(timerAction)
                                           userInfo:nil
                                            repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer
                                 forMode:NSDefaultRunLoopMode];
    
    [self timerAction];
    _progressTimer = timer;
}

- (void)invalidateTimer{
    [self.progressTimer invalidate];
    self.progressTimer = nil;
}

- (void)timerAction{

    self.totalBytesSent += self.bytesSent;
    if (self.totalBytesSent < self.maxBytesSent) {
        [self delegate_didSendBodyData:self.bytesSent
                        totalBytesSent:self.totalBytesSent
              totalBytesExpectedToSend:self.totalBytesExpectedToSend];
    }
}


//MARK: -- delegate action
- (void)delegate_redirectedToRequest:(NSURLRequest *)request
                    redirectResponse:(NSURLResponse *)redirectResponse{
    if ([self.delegate respondsToSelector:@selector(redirectedToRequest:redirectResponse:)]) {
        [self.delegate redirectedToRequest:request redirectResponse:redirectResponse];
    }
}

- (BOOL)delegate_evaluateServerTrust:(SecTrustRef)serverTrust
                           forDomain:(NSString *)domain{
    if ([self.delegate respondsToSelector:@selector(evaluateServerTrust:forDomain:)]) {
        return [self.delegate evaluateServerTrust:serverTrust forDomain:domain];
    } else {
        return NO;
    }
}

- (void)delegate_onError:(NSError *)error{
    if ([self.delegate respondsToSelector:@selector(onError:)]) {
        [self.delegate onError:error];
    }
}

- (void)delegate_didSendBodyData:(int64_t)bytesSent
                  totalBytesSent:(int64_t)totalBytesSent
        totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
    if ([self.delegate respondsToSelector:@selector(didSendBodyData:
                                                    totalBytesSent:
                                                    totalBytesExpectedToSend:)]) {
        [self.delegate didSendBodyData:bytesSent
                        totalBytesSent:totalBytesSent
              totalBytesExpectedToSend:totalBytesExpectedToSend];
    }
}
- (void)delegate_onReceiveResponse:(NSURLResponse *)response{
    if ([self.delegate respondsToSelector:@selector(onReceiveResponse:)]) {
        [self.delegate onReceiveResponse:response];
    }
}

- (void)delegate_didLoadData:(NSData *)data{
    if ([self.delegate respondsToSelector:@selector(didLoadData:)]) {
        [self.delegate didLoadData:data];
    }
}

- (void)delegate_didFinish{
    if ([self.delegate respondsToSelector:@selector(didFinish)]) {
        [self.delegate didFinish];
    }
}

@end
