import random
import collections

INT_MASK = 0xFFFFFFFF       

class IsaacRandom(random.Random):
    

    def seed(self, seed=None):
        
        def mix():
            init_state[0] ^= ((init_state[1]<<11)&INT_MASK); init_state[3] += init_state[0]; init_state[3] &= INT_MASK; init_state[1] += init_state[2]; init_state[1] &= INT_MASK
            init_state[1] ^=  (init_state[2]>>2)           ; init_state[4] += init_state[1]; init_state[4] &= INT_MASK; init_state[2] += init_state[3]; init_state[2] &= INT_MASK
            init_state[2] ^= ((init_state[3]<<8 )&INT_MASK); init_state[5] += init_state[2]; init_state[5] &= INT_MASK; init_state[3] += init_state[4]; init_state[3] &= INT_MASK
            init_state[3] ^=  (init_state[4]>>16)          ; init_state[6] += init_state[3]; init_state[6] &= INT_MASK; init_state[4] += init_state[5]; init_state[4] &= INT_MASK
            init_state[4] ^= ((init_state[5]<<10)&INT_MASK); init_state[7] += init_state[4]; init_state[7] &= INT_MASK; init_state[5] += init_state[6]; init_state[5] &= INT_MASK
            init_state[5] ^=  (init_state[6]>>4 )          ; init_state[0] += init_state[5]; init_state[0] &= INT_MASK; init_state[6] += init_state[7]; init_state[6] &= INT_MASK
            init_state[6] ^= ((init_state[7]<<8 )&INT_MASK); init_state[1] += init_state[6]; init_state[1] &= INT_MASK; init_state[7] += init_state[0]; init_state[7] &= INT_MASK
            init_state[7] ^=  (init_state[0]>>9 )          ; init_state[2] += init_state[7]; init_state[2] &= INT_MASK; init_state[0] += init_state[1]; init_state[0] &= INT_MASK

        super().seed(0) 
        if seed is not None:
            if isinstance(seed, str):
                seed = [ord(x) for x in seed]
            elif isinstance(seed, collections.Iterable):
                seed = [x & INT_MASK for x in seed]
            elif isinstance(seed, int):
                val = abs(seed)
                seed = []
                while val:
                    seed.append(val & INT_MASK)
                    val >>= 32
            else:
                raise TypeError('Seed must be string, integer or iterable of integer')

            
            if len(seed)>256:
                del seed[256:]
            elif len(seed)<256:
                seed.extend([0]*(256-len(seed)))

        self.aa = self.bb = self.cc = 0
        self.mm = []
        init_state = [0x9e3779b9]*8

        for _ in range(4):
            mix()

        for i in range(0, 256, 8):
            if seed is not None:
                for j in range(8):
                    init_state[j] += seed[i+j]
                    init_state[j] &= INT_MASK
            mix()
            self.mm += init_state

        if seed is not None:
            for i in range(0, 256, 8):
                for j in range(8):
                    init_state[j] += self.mm[i+j]
                    init_state[j] &= INT_MASK
                mix()
                for j in range(8):
                    self.mm[i+j] = init_state[j]

        self.rand_count = 256
        self.rand_result = [0]*256

    def getstate(self):
        return super().getstate(), self.aa, self.bb, self.cc, self.mm, self.rand_count, self.rand_result

    def setstate(self, state):
        super().setstate(state[0])
        _, self.aa, self.bb, self.cc, self.mm, self.rand_count, self.rand_result = state 

    def _generate(self):
        
        
        self.cc = (self.cc + 1) & INT_MASK
        self.bb = (self.bb + self.cc) & INT_MASK

        for i in range(256):
            x = self.mm[i]
            mod = i & 3
            if mod==0:
                self.aa ^= ((self.aa << 13) & INT_MASK)
            elif mod==1:
                self.aa ^= (self.aa >> 6)
            elif mod==2:
                self.aa ^= ((self.aa << 2) & INT_MASK)
            else: 
                self.aa ^= (self.aa >> 16)
            self.aa = (self.mm[i^128] + self.aa) & INT_MASK
            y = self.mm[i] = (self.mm[(x>>2) & 0xFF] + self.aa + self.bb) & INT_MASK
            self.rand_result[i] = self.bb = (self.mm[(y>>10) & 0xFF] + x) & INT_MASK

        self.rand_count = 0

    def next_int(self):
        
        if self.rand_count == 256:
            self._generate()
        result = self.rand_result[self.rand_count]
        self.rand_count += 1
        return result

    def getrandbits(self, k):
        
        result = 0
        ints_needed = (k+31)
        ints_used = 0
        while ints_used < ints_needed:
            if self.rand_count == 256:
                self._generate()
            ints_to_take = min(256-self.rand_count, ints_needed)
            for val in self.rand_result[self.rand_count: self.rand_count+ints_to_take]:
                result = (result << 32) | val
            self.rand_count += ints_to_take
            ints_used += ints_to_take
        result &= ((1<<k)-1)    
        return result

    def random(self):
        
        
        return self.getrandbits(53) * (2**-53)

    def rand_char(self):
        
        return self.next_int()% 95 + 32

    def vernam(self, msg):
        
        return bytes((self.rand_char() & 0xFF) ^ x for x in msg)

    
    ENCIPHER = 'encipher'
    DECIPHER = 'decipher'

    @staticmethod
    def _caesar(ciphermode, ch, shift, modulo, start):
        if ciphermode == IsaacRandom.DECIPHER:
            shift = -shift
        n = ((ch-start)+shift)% modulo
        if n<0:
            n += modulo
        return start+n

    def caesar(self, ciphermode, msg, modulo, start):
        
        return bytes(self._caesar(ciphermode, ch, self.rand_char(), modulo, start) for ch in msg)

if __name__=='__main__':
    import binascii

    def hexify(b):
        return binascii.hexlify(b).decode('ascii').upper()

    MOD = 95
    START = 32

    msg = 'a Top Secret secret'
    key = 'this is my secret key'
    isaac_random = IsaacRandom(key)
    vernam_encoded = isaac_random.vernam(msg.encode('ascii'))
    caesar_encoded = isaac_random.caesar(IsaacRandom.ENCIPHER, msg.encode('ascii'), MOD, START)
    isaac_random.seed(key)
    vernam_decoded = isaac_random.vernam(vernam_encoded).decode('ascii')
    caesar_decoded = isaac_random.caesar(IsaacRandom.DECIPHER, caesar_encoded, MOD, START).decode('ascii')

    print('Message:', msg)
    print('Key   :', key)
    print('XOR   :', hexify(vernam_encoded))
    print('XOR dcr:', vernam_decoded)
    print('MOD   :', hexify(caesar_encoded))
    print('MOD dcr:', caesar_decoded)