# Author: Trevor Perrin
# See the LICENSE file for legal information regarding use of this file.

"""Pure-Python AES implementation."""

import sys
from .aes import AES
from .rijndael import Rijndael
from .cryptomath import bytesToNumber, numberToByteArray

__all__ = ["new", "Python_AES"]


def new(key, mode, IV):
    # IV argument name is a part of the interface
    # pylint: disable=invalid-name
    if mode == 2:
        return Python_AES(key, mode, IV)
    elif mode == 6:
        return Python_AES_CTR(key, mode, IV)
    else:
        raise NotImplementedError()


class Python_AES(AES):
    def __init__(self, key, mode, IV):
        # IV argument/field names are a part of the interface
        # pylint: disable=invalid-name
        key, IV = bytearray(key), bytearray(IV)
        super(Python_AES, self).__init__(key, mode, IV, "python")
        self.rijndael = Rijndael(key, 16)
        self.IV = IV

    def encrypt(self, plaintext):
        super(Python_AES, self).encrypt(plaintext)

        plaintextBytes = bytearray(plaintext)
        chainBytes = self.IV[:]

        # CBC Mode: For each block...
        for x in range(len(plaintextBytes) // 16):
            # XOR with the chaining block
            blockBytes = plaintextBytes[x * 16 : (x * 16) + 16]
            for y in range(16):
                blockBytes[y] ^= chainBytes[y]

            # Encrypt it
            encryptedBytes = self.rijndael.encrypt(blockBytes)

            # Overwrite the input with the output
            for y in range(16):
                plaintextBytes[(x * 16) + y] = encryptedBytes[y]

            # Set the next chaining block
            chainBytes = encryptedBytes

        self.IV = chainBytes[:]
        return plaintextBytes

    def decrypt(self, ciphertext):
        super(Python_AES, self).decrypt(ciphertext)

        ciphertextBytes = ciphertext[:]
        chainBytes = self.IV[:]

        # CBC Mode: For each block...
        for x in range(len(ciphertextBytes) // 16):
            # Decrypt it
            blockBytes = ciphertextBytes[x * 16 : (x * 16) + 16]
            decryptedBytes = self.rijndael.decrypt(blockBytes)

            # XOR with the chaining block and overwrite the input with output
            for y in range(16):
                decryptedBytes[y] ^= chainBytes[y]
                ciphertextBytes[(x * 16) + y] = decryptedBytes[y]

            # Set the next chaining block
            chainBytes = blockBytes

        self.IV = chainBytes[:]
        return ciphertextBytes


class Python_AES_CTR(AES):
    def __init__(self, key, mode, IV):
        super(Python_AES_CTR, self).__init__(key, mode, IV, "python")
        self.rijndael = Rijndael(key, 16)
        self.IV = IV
        self._counter_bytes = 16 - len(self.IV)
        self._counter = self.IV + bytearray(b"\x00" * self._counter_bytes)

    @property
    def counter(self):
        return self._counter

    @counter.setter
    def counter(self, ctr):
        self._counter = ctr

    def _counter_update(self):
        counter_int = bytesToNumber(self._counter) + 1
        self._counter = numberToByteArray(counter_int, 16)
        if self._counter_bytes > 0 and self._counter[-self._counter_bytes :] == bytearray(
            b"\xff" * self._counter_bytes
        ):
            raise OverflowError("CTR counter overflowed")

    def encrypt(self, plaintext):
        mask = bytearray()
        while len(mask) < len(plaintext):
            mask += self.rijndael.encrypt(self._counter)
            self._counter_update()
        if sys.version_info < (3, 0):
            inp_bytes = bytearray(ord(i) ^ j for i, j in zip(plaintext, mask))
        else:
            inp_bytes = bytearray(i ^ j for i, j in zip(plaintext, mask))
        return inp_bytes

    def decrypt(self, ciphertext):
        return self.encrypt(ciphertext)
