"""This module contains the Vigenere class."""

from operator import add, sub

from CharacterCipher import CharacterCipher
from KeyedCipher import KeyedCipher
from Tools import LEN_ALPHABET, apply_caseless, offset, from_offset


class Vigenere(CharacterCipher, KeyedCipher):

    """This is a Rot-like cipher that uses a private key.

    Basically, each letter is rotated by an amount that is a function of both
    its position in the file as well as the private key.

    The following attributes are used:

    key - This is the private key.
    keyDescription - This is a description of what constitutes a valid key.
    defaultKey - This is the default key.

    The following private variables are used:

    _keyIndex - This is a value in the range 0..len(key) that gets incremented 
    module len(key) for every ASCII character as that character is encrypted 
    or decrypted.
    
    """

    keyDescription = "one or more lowercase letters"
    defaultKey = "a"

    def __init__(self):
        """Set key to defaultKey."""
        self.key = self.defaultKey

    def encrypt(self, s):
        """Extend the base class's encrypt method to set _keyIndex to 0."""
        self._keyIndex = 0
        return CharacterCipher.encrypt(self, s)

    def decrypt(self, s):
        """Extend the base class's decrypt method to set _keyIndex to 0."""
        self._keyIndex = 0
        return CharacterCipher.decrypt(self, s)

    def encryptCharacter(self, c):
        """Encrypt and return c."""
        return self._cryptCharacter(c, add)

    def decryptCharacter(self, c):
        """Decrypt and return c."""
        return self._cryptCharacter(c, sub)

    def _cryptCharacter(self, c, f):
        """Take care of non-letters as well as the _keyIndex."""
        if not c.isalpha():
            return c
        ret = apply_caseless(self._cryptCharacterBackend, c, f)
        self._keyIndex = (self._keyIndex + 1) % len(self.key)
        return ret

    def _cryptCharacterBackend(self, c, f):
        """Actually do the encryption or decryption."""
        inputPart = offset(c)
        keyPart = offset(self.key[self._keyIndex])
        desiredOffset = f(inputPart, keyPart) % LEN_ALPHABET
        return from_offset(desiredOffset)

    def isValidKey(self, key):
        """Is key a a valid key?  See keyDescription."""
        return key.isalpha() and key.islower()


# Do some testing.
if __name__ == '__main__':
    cipher = Vigenere()
    assert cipher.isValidKey("abz")
    assert not cipher.isValidKey("ABC")
    assert not cipher.isValidKey(" ")
    cipher.key = "bad"
    assert cipher.key == "bad"
    assert cipher.encrypt("bad") == "cag"
    assert cipher.decrypt("cag") == "bad"
    cipher.key = "xyz"
    assert cipher.encrypt("bca") == "yaz"
    assert cipher.decrypt("yaz") == "bca"
