import re


class ReadCancelledError(Exception):
    """Occurs when a read operation was cancelled"""
    def __init__(self):
        super().__init__(self, 'The read operation was cancelled.')


class InvalidParameterError(Exception):
    """Occurs when an invalid parameter is given, for example,
    when either A or B are required but none is given"""


class TypeNotFoundError(Exception):
    """Occurs when a type is not found, for example,
    when trying to read a TLObject with an invalid constructor code"""
    def __init__(self, invalid_constructor_id):
        super().__init__(self, 'Could not find a matching Constructor ID for the TLObject '
                               'that was supposed to be read with ID {}. Most likely, a TLObject '
                               'was trying to be read when it should not be read.'
                               .format(hex(invalid_constructor_id)))

        self.invalid_constructor_id = invalid_constructor_id


class InvalidDCError(Exception):
    def __init__(self, new_dc):
        super().__init__(self,  'Your phone number is registered to #{} DC. '
                                'This should have been handled automatically; '
                                'if it has not, please restart the app.'.format(new_dc))

        self.new_dc = new_dc


class InvalidChecksumError(Exception):
    def __init__(self, checksum, valid_checksum):
        super().__init__(self,  'Invalid checksum ({} when {} was expected). This packet should be skipped.'
                                .format(checksum, valid_checksum))

        self.checksum = checksum
        self.valid_checksum = valid_checksum


class RPCError(Exception):

    CodeMessages = {
        303: ('ERROR_SEE_OTHER', 'The request must be repeated, but directed to a different data center.'),

        400: ('BAD_REQUEST', 'The query contains errors. In the event that a request was created using a '
                             'form and contains user generated data, the user should be notified that the '
                             'data must be corrected before the query is repeated.'),

        401: ('UNAUTHORIZED', 'There was an unauthorized attempt to use functionality available only to '
                              'authorized users.'),

        403: ('FORBIDDEN', 'Privacy violation. For example, an attempt to write a message to someone who '
                           'has blacklisted the current user.'),

        404: ('NOT_FOUND', 'An attempt to invoke a non-existent object, such as a method.'),

        420: ('FLOOD', 'The maximum allowed number of attempts to invoke the given method with '
                       'the given input parameters has been exceeded. For example, in an attempt '
                       'to request a large number of text messages (SMS) for the same phone number.'),

        500: ('INTERNAL', 'An internal server error occurred while a request was being processed; '
                          'for example, there was a disruption while accessing a database or file storage.')
    }

    ErrorMessages = {
        # 303 ERROR_SEE_OTHER
        'FILE_MIGRATE_(\d+)': 'The file to be accessed is currently stored in a different data center (#{}).',

        'PHONE_MIGRATE_(\d+)': 'The phone number a user is trying to use for authorization is associated '
                               'with a different data center (#{}).',

        'NETWORK_MIGRATE_(\d+)': 'The source IP address is associated with a different data center (#{}, '
                                 'for registration).',

        'USER_MIGRATE_(\d+)': 'The user whose identity is being used to execute queries is associated with '
                              'a different data center  (#{} for registration).',

        # 400 BAD_REQUEST
        'FIRSTNAME_INVALID': 'The first name is invalid.',

        'LASTNAME_INVALID': 'The last name is invalid.',

        'PHONE_NUMBER_INVALID': 'The phone number is invalid.',

        'PHONE_CODE_HASH_EMPTY': 'The phone code hash is missing.',

        'PHONE_CODE_EMPTY': 'The phone code is missing.',

        'PHONE_CODE_INVALID': 'The phone code entered was invalid.',

        'PHONE_CODE_EXPIRED': 'The confirmation code has expired.',

        'API_ID_INVALID': 'The api_id/api_hash combination is invalid.',

        'PHONE_NUMBER_OCCUPIED': 'The phone number is already in use.',

        'PHONE_NUMBER_UNOCCUPIED': 'The phone number is not yet being used.',

        'USERS_TOO_FEW': 'Not enough users (to create a chat, for example).',

        'USERS_TOO_MUCH': 'The maximum number of users has been exceeded (to create a chat, for example).',

        'TYPE_CONSTRUCTOR_INVALID': 'The type constructor is invalid.',

        'FILE_PART_INVALID': 'The file part number is invalid.',

        'FILE_PARTS_INVALID': 'The number of file parts is invalid.',

        'FILE_PART_(\d+)_MISSING': 'Part {} of the file is missing from storage.',

        'MD5_CHECKSUM_INVALID': 'The MD5 checksums do not match.',

        'PHOTO_INVALID_DIMENSIONS': 'The photo dimensions are invalid.',

        'FIELD_NAME_INVALID': 'The field with the name FIELD_NAME is invalid.',

        'FIELD_NAME_EMPTY': 'The field with the name FIELD_NAME is missing.',

        'MSG_WAIT_FAILED': 'A waiting call returned an error.',

        'CHAT_ADMIN_REQUIRED': 'Chat admin privileges are required to do that in the specified chat '
                               '(for example, to send a message in a channel which is not yours).',

        # 401 UNAUTHORIZED
        'AUTH_KEY_UNREGISTERED': 'The key is not registered in the system.',

        'AUTH_KEY_INVALID': 'The key is invalid.',

        'USER_DEACTIVATED': 'The user has been deleted/deactivated.',

        'SESSION_REVOKED': 'The authorization has been invalidated, because of the user terminating all sessions.',

        'SESSION_EXPIRED': 'The authorization has expired.',

        'ACTIVE_USER_REQUIRED': 'The method is only available to already activated users.',

        'AUTH_KEY_PERM_EMPTY': 'The method is unavailable for temporary authorization key, not bound to permanent.',

        # 420 FLOOD
        'FLOOD_WAIT_(\d+)': 'A wait of {} seconds is required.'
    }

    def __init__(self, code, message):
        self.code = code
        self.code_meaning = RPCError.CodeMessages[code]

        self.message = message
        self.must_resend = code == 303  # ERROR_SEE_OTHER, "The request must be repeated"

        called_super = False
        for key, error_msg in RPCError.ErrorMessages.items():
            match = re.match(key, message)
            if match:
                # Get additional_data, if any
                if match.groups():
                    self.additional_data = int(match.group(1))
                    super().__init__(self, error_msg.format(self.additional_data))
                else:
                    self.additional_data = None
                    super().__init__(self, error_msg)

                called_super = True
                break

        if not called_super:
            super().__init__(self, 'Unknown error message with code {}: {}'.format(code, message))


class BadMessageError(Exception):
    """Occurs when handling a bad_message_notification"""
    ErrorMessages = {
        16: 'msg_id too low (most likely, client time is wrong it would be worthwhile to '
            'synchronize it using msg_id notifications and re-send the original message '
            'with the "correct" msg_id or wrap it in a container with a new msg_id if the '
            'original message had waited too long on the client to be transmitted).',

        17: 'msg_id too high (similar to the previous case, the client time has to be '
            'synchronized, and the message re-sent with the correct msg_id).',

        18: 'Incorrect two lower order msg_id bits (the server expects client message msg_id '
            'to be divisible by 4).',

        19: 'Container msg_id is the same as msg_id of a previously received message '
            '(this must never happen).',

        20: 'Message too old, and it cannot be verified whether the server has received a '
            'message with this msg_id or not.',

        32: 'msg_seqno too low (the server has already received a message with a lower '
            'msg_id but with either a higher or an equal and odd seqno).',

        33: 'msg_seqno too high (similarly, there is a message with a higher msg_id but with '
            'either a lower or an equal and odd seqno).',

        34: 'An even msg_seqno expected (irrelevant message), but odd received.',

        35: 'Odd msg_seqno expected (relevant message), but even received.',

        48: 'Incorrect server salt (in this case, the bad_server_salt response is received with '
            'the correct salt, and the message is to be re-sent with it).',

        64: 'Invalid container.'
    }

    def __init__(self, code):
        super().__init__(self, BadMessageError
                         .ErrorMessages.get(code,'Unknown error code (this should not happen): {}.'.format(code)))

        self.code = code
