"""S3 error codes adapted into more natural Python ones.

Adapted from: https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
"""

import errno
import functools


# Fallback values since some systems might not have these.
ENAMETOOLONG = getattr(errno, 'ENAMETOOLONG', errno.EINVAL)
ENOTEMPTY = getattr(errno, 'ENOTEMPTY', errno.EINVAL)
EMSGSIZE = getattr(errno, 'EMSGSIZE', errno.EINVAL)
EREMOTEIO = getattr(errno, 'EREMOTEIO', errno.EIO)
EREMCHG = getattr(errno, 'EREMCHG', errno.ENOENT)


ERROR_CODE_TO_EXCEPTION = {
    'AccessDenied': PermissionError,
    'AccountProblem': PermissionError,
    'AllAccessDisabled': PermissionError,
    'AmbiguousGrantByEmailAddress': functools.partial(IOError, errno.EINVAL),
    'AuthorizationHeaderMalformed': functools.partial(IOError, errno.EINVAL),
    'BadDigest': functools.partial(IOError, errno.EINVAL),
    'BucketAlreadyExists': FileExistsError,
    'BucketAlreadyOwnedByYou': FileExistsError,
    'BucketNotEmpty': functools.partial(IOError, ENOTEMPTY),
    'CredentialsNotSupported': functools.partial(IOError, errno.EINVAL),
    'CrossLocationLoggingProhibited': PermissionError,
    'EntityTooSmall': functools.partial(IOError, errno.EINVAL),
    'EntityTooLarge': functools.partial(IOError, EMSGSIZE),
    'ExpiredToken': PermissionError,
    'IllegalLocationConstraintException': PermissionError,
    'IllegalVersioningConfigurationException': functools.partial(IOError, errno.EINVAL),
    'IncompleteBody': functools.partial(IOError, errno.EINVAL),
    'IncorrectNumberOfFilesInPostRequest': functools.partial(IOError, errno.EINVAL),
    'InlineDataTooLarge': functools.partial(IOError, EMSGSIZE),
    'InternalError': functools.partial(IOError, EREMOTEIO),
    'InvalidAccessKeyId': PermissionError,
    'InvalidAddressingHeader': functools.partial(IOError, errno.EINVAL),
    'InvalidArgument': functools.partial(IOError, errno.EINVAL),
    'InvalidBucketName': functools.partial(IOError, errno.EINVAL),
    'InvalidBucketState': functools.partial(IOError, errno.EPERM),
    'InvalidDigest': functools.partial(IOError, errno.EINVAL),
    'InvalidEncryptionAlgorithmError': functools.partial(IOError, errno.EINVAL),
    'InvalidLocationConstraint': functools.partial(IOError, errno.EINVAL),
    'InvalidObjectState': PermissionError,
    'InvalidPart': functools.partial(IOError, errno.EINVAL),
    'InvalidPartOrder': functools.partial(IOError, errno.EINVAL),
    'InvalidPayer': PermissionError,
    'InvalidPolicyDocument': functools.partial(IOError, errno.EINVAL),
    'InvalidRange': functools.partial(IOError, errno.EINVAL),
    'InvalidRequest': functools.partial(IOError, errno.EINVAL),
    'InvalidSecurity': PermissionError,
    'InvalidSOAPRequest': functools.partial(IOError, errno.EINVAL),
    'InvalidStorageClass': functools.partial(IOError, errno.EINVAL),
    'InvalidTargetBucketForLogging': functools.partial(IOError, errno.EINVAL),
    'InvalidToken': functools.partial(IOError, errno.EINVAL),
    'InvalidURI': functools.partial(IOError, errno.EINVAL),
    'KeyTooLongError': functools.partial(IOError, ENAMETOOLONG),
    'MalformedACLError': functools.partial(IOError, errno.EINVAL),
    'MalformedPOSTRequest': functools.partial(IOError, errno.EINVAL),
    'MalformedXML': functools.partial(IOError, errno.EINVAL),
    'MaxMessageLengthExceeded': functools.partial(IOError, EMSGSIZE),
    'MaxPostPreDataLengthExceededError': functools.partial(IOError, EMSGSIZE),
    'MetadataTooLarge': functools.partial(IOError, EMSGSIZE),
    'MethodNotAllowed': functools.partial(IOError, errno.EPERM),
    'MissingAttachment': functools.partial(IOError, errno.EINVAL),
    'MissingContentLength': functools.partial(IOError, errno.EINVAL),
    'MissingRequestBodyError': functools.partial(IOError, errno.EINVAL),
    'MissingSecurityElement': functools.partial(IOError, errno.EINVAL),
    'MissingSecurityHeader': functools.partial(IOError, errno.EINVAL),
    'NoLoggingStatusForKey': functools.partial(IOError, errno.EINVAL),
    'NoSuchBucket': FileNotFoundError,
    'NoSuchBucketPolicy': FileNotFoundError,
    'NoSuchKey': FileNotFoundError,
    'NoSuchLifecycleConfiguration': FileNotFoundError,
    'NoSuchUpload': FileNotFoundError,
    'NoSuchVersion': FileNotFoundError,
    'NotImplemented': functools.partial(IOError, errno.ENOSYS),
    'NotSignedUp': PermissionError,
    'OperationAborted': functools.partial(IOError, errno.EBUSY),
    'PermanentRedirect': functools.partial(IOError, EREMCHG),
    'PreconditionFailed': functools.partial(IOError, errno.EINVAL),
    'Redirect': functools.partial(IOError, EREMCHG),
    'RestoreAlreadyInProgress': functools.partial(IOError, errno.EBUSY),
    'RequestIsNotMultiPartContent': functools.partial(IOError, errno.EINVAL),
    'RequestTimeout': TimeoutError,
    'RequestTimeTooSkewed': PermissionError,
    'RequestTorrentOfBucketError': functools.partial(IOError, errno.EPERM),
    'SignatureDoesNotMatch': PermissionError,
    'ServiceUnavailable': functools.partial(IOError, errno.EBUSY),
    'SlowDown': functools.partial(IOError, errno.EBUSY),
    'TemporaryRedirect': functools.partial(IOError, EREMCHG),
    'TokenRefreshRequired': functools.partial(IOError, errno.EINVAL),
    'TooManyBuckets': functools.partial(IOError, errno.EINVAL),
    'UnexpectedContent': functools.partial(IOError, errno.EINVAL),
    'UnresolvableGrantByEmailAddress': functools.partial(IOError, errno.EINVAL),
    'UserKeyMustBeSpecified': functools.partial(IOError, errno.EINVAL),
    '301': functools.partial(IOError, EREMCHG),     # PermanentRedirect
    '307': functools.partial(IOError, EREMCHG),     # Redirect
    '400': functools.partial(IOError, errno.EINVAL),
    '403': PermissionError,
    '404': FileNotFoundError,
    '405': functools.partial(IOError, errno.EPERM),
    '409': functools.partial(IOError, errno.EBUSY),
    '412': functools.partial(IOError, errno.EINVAL),    # PreconditionFailed
    '416': functools.partial(IOError, errno.EINVAL),    # InvalidRange
    '500': functools.partial(IOError, EREMOTEIO),       # InternalError
    '501': functools.partial(IOError, errno.ENOSYS),    # NotImplemented
    '503': functools.partial(IOError, errno.EBUSY),     # SlowDown
}


def translate_boto_error(error, message=None, *args, **kwargs):
    """Convert a ClientError exception into a Python one.

    Parameters
    ----------

    error : botocore.exceptions.ClientError
        The exception returned by the boto API.
    message : str
        An error message to use for the returned exception. If not given, the
        error message returned by the server is used instead.
    *args, **kwargs :
        Additional arguments to pass to the exception constructor, after the
        error message. Useful for passing the filename arguments to ``IOError``.

    Returns
    -------

    An instantiated exception ready to be thrown. If the error code isn't
    recognized, an IOError with the original error message is returned.
    """
    code = error.response['Error'].get('Code')
    constructor = ERROR_CODE_TO_EXCEPTION.get(code)
    if not constructor:
        # No match found, wrap this in an IOError with the appropriate message.
        return IOError(errno.EIO, message or str(error), *args)

    if not message:
        message = error.response['Error'].get('Message', str(error))

    return constructor(message, *args, **kwargs)
