""" enigma.py
"""

import operator
from string import ascii_uppercase as UPPER
from copy import deepcopy
from permutations import Permutation,cycleform,permform

def char2int(key):
    try:
        c = key.upper()
        return UPPER.index(c)
    except:
        return key

class Rotor(Permutation):

    def __init__(self,perm,ringsetting='A',key='A',notches=()):

        Permutation.__init__(self,perm)
        self.notches = notches
        self.setRing(ringsetting)
        self.setKey(key)

    def setRing(self,A):
        r = char2int(A)
        cycles = [[(x+r) % 26 for x in cycle] for cycle in self.C]
        perm = permform(cycles)
        self.P = perm
        self.C = cycleform(perm)

    def setKey(self,A):
        k = char2int(A)
        cycles = [[(x-k) % 26 for x in cycle] for cycle in self.C]
        perm = permform(cycles)
        self.P = perm
        self.C = cycleform(perm)
        self.key = UPPER[k]

    def step(self):
        cycles = [[(x-1) % 26 for x in cycle] for cycle in self.C]
        perm = permform(cycles)
        self.P = perm
        self.C = cycleform(perm)

        k = char2int(self.key)
        k = (k+1) % 26
        self.key = UPPER[k]

    def trigger(self):
        return self.key in self.notches

def rotor(strng):
    return Permutation(tuple(strng.lower()))

# The rotors
I     = Rotor("EKMFLGDQVZNTOWYHXUSPAIBRCJ",notches=("Q"))
II    = Rotor("AJDKSIRUXBLHWTMCQGZNPYFVOE",notches=("E"))
III   = Rotor("BDFHJLCPRTXVZNYEIWGAKMUSQO",notches=("V"))
IV    = Rotor("ESOVPZJAYQUIRHXLNFTGKDCMWB",notches=("J"))
V     = Rotor("VZBRGITYUPSDNHLXAWMJQOFECK",notches=("Z"))
VI    = Rotor("JPGVOUMFYQBENHZRDKASXLICTW",notches=("Z","M"))
VII   = Rotor("NZJHGRCXMYSWBOUFAIVLPEKQDT",notches=("Z","M"))
VIII  = Rotor("FKQHTLXOCBJSPDZRAMEWNIUYGV",notches=("Z","M"))

# Used only in Model M-4 with thinB and thinC reflectors
beta  = Rotor("LEYJVCNIXWPBQMDRTAKZGFUHOS")
gamma = Rotor("FSOKANUERHMBTIYCWLQPZXVGJD")

# The reflectors
B     = Rotor("YRUHQSLDPXNGOKMIEBFZCWVJAT")
C     = Rotor("FVPJIAOYEDRZXWGCTKUQSBNMHL")
# The reflectors used in Model M-4 with beta and gamma rotors.
thinB = Rotor("ENKQAUYWJICOPBLMDXZVFTHRGS")
thinC = Rotor("RDOBJNTKVEHMLFCWZAXGYIPSUQ")

# The cyclic permutation and its inverse
rho   = Permutation("BCDEFGHIJKLMNOPQRSTUVWXYZA")

identity =  Permutation("ABCDEFGHIJKLMNOPQRSTUVWXYZ")

# The 'QWERTZU' in military and civilian types. We
# have the 'QWERTZU' act on the left so we use the
# inverse of the input permutation.
military = Permutation("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
civilian = Permutation("QWERTZUIOASDFGHJKPYXCVBNML")

# This class still needs to be checked to see if it
# performs correctly.
class Enigma(object):

    def __init__(self,
            rotorList = (I,II,III),
            reflector = B,
            ringSettings = (),
            stecker = identity,
            qwertzu = military):
        """ The rotor (wheel) list should be given from slowest to fastest:
            U, W_L, W_M, W_R. The ring settings default to 'AA...', the
            stecker and 'qwertzu' both default to the identity. The key
            settings are made after the machine is initialized.
        """
        self.qwertzu = qwertzu
        self.rotors = [deepcopy(R) for R in rotorList]
        self.rotors.insert(0,deepcopy(reflector))
        self.nR = len(self.rotors)

        self.__setRings(ringSettings)
        self.setStecker(stecker)

    def __repr__(self):
        return repr(self.permutation)

    def __str__(self):
        return str(self.permutation)

    def __call__(self,text):
        return "".join(map(self.encipher,text))

    def setStecker(self,stecker):
        try:
            self.SQ = stecker * self.qwertzu
        except:
            self.SQ = Permutation(stecker) * self.qwertzu

        self.setPermutation()

    def __setRings(self,ringSettings):
        # Convert ring setting letters to numbers.
        ringSettings = map(char2int,ringSettings)

        # All ring settings default to 'A'.
        nS = len(ringSettings)
        ringSettings = (0,)*(self.nR - nS) + tuple(ringSettings)
        for k,R in enumerate(self.rotors):
            R.setRing(ringSettings[k])

    def setKeys(self,keySettings):
        # Convert key setting letters to numbers.
        keySettings = map(char2int,keySettings)

        # All key settings default to 'A'.
        nK = len(keySettings)
        keySettings = (0,)*(self.nR - nK) + tuple(keySettings)
        for k,R in enumerate(self.rotors):
            R.setKey(keySettings[k])

        self.setPermutation()

    def setPermutation(self):
        self.permutation = (self.SQ >> reduce(operator.lshift,self.rotors))

    def step(self):
        if self.rotors[-2].trigger():
            motion = (1,1,1)
        elif self.rotors[-1].trigger():
            motion = (0,1,1)
        else:
            motion = (0,0,1)

        for i in range(3):
            if motion[i]:
                self.rotors[i-3].step()

        self.setPermutation()

    def encipher(self,c):
        self.step()
        return self.permutation.encipher(c)

