//
// Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// A copy of the License is located at
//
// http://aws.amazon.com/apache2.0
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
//

#import "AWSURLRequestRetryHandler.h"
#import "AWSURLResponseSerialization.h"
#import "AWSService.h"

@interface AWSURLRequestRetryHandler ()

@property (atomic, assign) BOOL isClockSkewRetried;

@end

@implementation AWSURLRequestRetryHandler

- (instancetype)initWithMaximumRetryCount:(uint32_t)maxRetryCount {
    if (self = [super init]) {
        _maxRetryCount = maxRetryCount;
    }

    return self;
}

- (BOOL)isClockSkewError:(NSError *)error {
    if ([error.domain isEqualToString:AWSServiceErrorDomain]) {
        switch (error.code) {
            case AWSServiceErrorRequestTimeTooSkewed:
            case AWSServiceErrorInvalidSignatureException:
            case AWSServiceErrorRequestExpired:
            case AWSServiceErrorAuthFailure:
            case AWSServiceErrorSignatureDoesNotMatch:
                return YES;
            default:
                break;
        }
    }

    return NO;
}

- (AWSNetworkingRetryType)shouldRetry:(uint32_t)currentRetryCount
                      originalRequest:(AWSNetworkingRequest *)originalRequest
                             response:(NSHTTPURLResponse *)response
                                 data:(NSData *)data
                                error:(NSError *)error {
    if (currentRetryCount >= self.maxRetryCount) {
        return AWSNetworkingRetryTypeShouldNotRetry;
    }

    // Clock skew exceptions.
    if (!self.isClockSkewRetried && [self isClockSkewError:error]) {
        self.isClockSkewRetried = YES;
        return AWSNetworkingRetryTypeShouldCorrectClockSkewAndRetry;
    }

    if ([error.domain isEqualToString:NSURLErrorDomain]) {
        switch (error.code) {
            case NSURLErrorCancelled:
            case NSURLErrorBadURL:
            case NSURLErrorNotConnectedToInternet:

            case NSURLErrorSecureConnectionFailed:
            case NSURLErrorServerCertificateHasBadDate:
            case NSURLErrorServerCertificateUntrusted:
            case NSURLErrorServerCertificateHasUnknownRoot:
            case NSURLErrorServerCertificateNotYetValid:
            case NSURLErrorClientCertificateRejected:
            case NSURLErrorClientCertificateRequired:
            case NSURLErrorCannotLoadFromNetwork:
                return AWSNetworkingRetryTypeShouldNotRetry;

            default:
                return AWSNetworkingRetryTypeShouldRetry;
        }
    }

    // Invalid temporary credentials exceptions.
    if ([error.domain isEqualToString:AWSServiceErrorDomain]) {
        switch (error.code) {
            case AWSServiceErrorIncompleteSignature:
            case AWSServiceErrorInvalidClientTokenId:
            case AWSServiceErrorMissingAuthenticationToken:
            case AWSServiceErrorAccessDenied:
            case AWSServiceErrorUnrecognizedClientException:
            case AWSServiceErrorAuthFailure:
            case AWSServiceErrorAccessDeniedException:
            case AWSServiceErrorExpiredToken:
            case AWSServiceErrorInvalidAccessKeyId:
            case AWSServiceErrorInvalidToken:
            case AWSServiceErrorTokenRefreshRequired:
            case AWSServiceErrorAccessFailure:
            case AWSServiceErrorAuthMissingFailure:
                return AWSNetworkingRetryTypeShouldRefreshCredentialsAndRetry;

            default:
                break;
        }
    }

    // Throttling exceptions.
    if ([error.domain isEqualToString:AWSServiceErrorDomain]) {
        switch (error.code) {
            case AWSServiceErrorThrottling:
            case AWSServiceErrorThrottlingException:
                return AWSNetworkingRetryTypeShouldRetry;

            default:
                break;
        }
    }

    switch (response.statusCode) {
        case 500:
        case 503:
            return AWSNetworkingRetryTypeShouldRetry;
            break;

        default:
            break;
    }

    return AWSNetworkingRetryTypeShouldNotRetry;
}

- (NSTimeInterval)timeIntervalForRetry:(uint32_t)currentRetryCount
                              response:(NSHTTPURLResponse *)response
                                  data:(NSData *)data
                                 error:(NSError *)error {
    return pow(2, currentRetryCount) * 100 / 1000;
}

@end
