"""Provide a crypto object, depending on the available modules.

The object has this interface:

aesEncrypt(DATA, KEY)
  Encrypt the DATA with key KEY.

aesDecrypt(DATA, KEY):
  Decrypt the DATA with key KEY.

has_aes
  True if the encryption provides AES encryption.

getKeyLength()
  Return the maximum size for keys for this crypto object, in bytes.

"""
from beaker.crypto.pbkdf2 import pbkdf2
from beaker.crypto.util import hmac, sha1, hmac_sha1, md5
from beaker import util
from beaker.exceptions import InvalidCryptoBackendError

keyLength = None
DEFAULT_NONCE_BITS = 128

CRYPTO_MODULES = {}


def load_default_module():
    """Load the default crypto module and return it.

    Note: if no crypto module is available, return a dummy module
    which does not encrypt at all.

    The fallback chain is:
    1. cryptography (recommended, modern, actively maintained)
    2. pycryptodome (via pycrypto module, widely used)
    3. noencryption (no encryption, fallback)

    """
    try:
        from beaker.crypto import pyca_cryptography
        return pyca_cryptography
    except ImportError:
        try:
            from beaker.crypto import pycrypto
            return pycrypto
        except ImportError:
            pass
    from beaker.crypto import noencryption
    return noencryption


def register_crypto_module(name, mod):
    """
    Register the given module under the name given.
    """
    CRYPTO_MODULES[name] = mod


def get_crypto_module(name):
    """
    Get the active crypto module for this name
    """
    if name not in CRYPTO_MODULES:
        if name == 'default':
            register_crypto_module('default', load_default_module())
        elif name == 'pycryptodome':
            from beaker.crypto import pycrypto
            register_crypto_module(name, pycrypto)
        elif name == 'pycrypto':
            # Backward compatibility alias for pycryptodome
            from beaker.crypto import pycrypto
            register_crypto_module(name, pycrypto)
        elif name == 'cryptography':
            from beaker.crypto import pyca_cryptography
            register_crypto_module(name, pyca_cryptography)
        else:
            raise InvalidCryptoBackendError(
                "No crypto backend with name '%s' is registered." % name)

    return CRYPTO_MODULES[name]



def generateCryptoKeys(master_key, salt, iterations, keylen):
    # NB: We XOR parts of the keystream into the randomly-generated parts, just
    # in case os.urandom() isn't as random as it should be.  Note that if
    # os.urandom() returns truly random data, this will have no effect on the
    # overall security.
    return pbkdf2(master_key, salt, iterations=iterations, dklen=keylen)


def get_nonce_size(number_of_bits):
    if number_of_bits % 8:
        raise ValueError('Nonce complexity currently supports multiples of 8')

    bytes = number_of_bits // 8
    b64bytes = ((4 * bytes // 3) + 3) & ~3
    return bytes, b64bytes
