import  re

class SteganographyEncodeDecode():

    def __init__(self, key, key2=3, window_size=1, start_marker='#', end_marker='#'):
        self.key1 = key
        self.key2 = key2
        self.window_size = window_size
        # self.pattern_regex = r""+start_marker+"[0-9]{1}[a-zA-Z0-9-.:]{1,}"+end_marker
        self.pattern_regex = r""+start_marker+"[0-9]{1,},[a-zA-Z0-9-.:]{1,}"+end_marker
        self.pattern = re.compile(self.pattern_regex)

    # function to perform xor operations
    def xor(self,a,b):
        y = int(a,2) ^ int(b,2)
        return '{0:b}'.format(y).rjust(8,'0')

    def has_pattern(self, input):
        matchedStrings = self.pattern.findall(input)
        if len(matchedStrings) > 0:
            return True, matchedStrings
        return False, []

    def encode(self, secretMessage, coverDNA):
        # steganography encoding starts
        asciiArray = []
        binaryArray = []
        # get ascii of the reverse of the string
        message = secretMessage[::-1] # reverse secret message
        for ch in message:
            asciiArray.append(ord(ch))

        # convert string and key into binary
        for num in asciiArray:
            binaryArray.append("{0:b}".format(num).rjust(8,'0'))
        binaryKey = "{0:b}".format(self.key1).rjust(8,'0')

        # perform xor operations
        def xor(a,b):
            if len(a) != len(b) and len(a)!= 8:
                print('Either size mismatch for XOR, or the valie is not 8 bit')
                exit()
            y = int(a,2) ^ int(b,2)
            return '{0:b}'.format(y).rjust(8,'0')

        XORs = ""
        xorOutput = binaryKey
        for value in binaryArray:
            xorOutput = xor(value, xorOutput)
            XORs += xorOutput

        # using dictionary convert cover dna to binary
        dnaDictionary = {'A':'00', 'C':'01', 'G':'10', 'T':'11'}
        binaryOfCoverDNA = ""
        for base in coverDNA:
            binaryOfCoverDNA += dnaDictionary[base]

        # splitting binary of cover dna using key2
        binaryOfCoverDNASplit = [binaryOfCoverDNA[i:i+self.key2] for i in range(0, len(binaryOfCoverDNA), self.key2)]

        # inserting xors in splitting binary of cover dna
        for i in range(0,len(XORs)):
            binaryOfCoverDNASplit[i] = str(XORs[i])+binaryOfCoverDNASplit[i]

        # final step to produce fake dna
        reverseDnaDictionary = dict((v,k) for k,v in dnaDictionary.items())
        binaryFakeDNA = ''.join(binaryOfCoverDNASplit)
        fakeDNA = ""
        for i in range(0, len(binaryFakeDNA), 2):
            fakeDNA += reverseDnaDictionary[binaryFakeDNA[i:i+2]]

        return fakeDNA

    def decode(self, fakeDNA):
        # convert fake dna to binary using dictionary
        dnaDictionary = {'A':'00', 'C':'01', 'G':'10', 'T':'11'}
        binaryOfFakeDNA = ""
        for base in fakeDNA:
            binaryOfFakeDNA += dnaDictionary[base]
        # devide using key2+1 length
        splitBinaryOfFakeDNA = [binaryOfFakeDNA[i:i+self.key2+1] for i in range(0, len(binaryOfFakeDNA), self.key2+1)]
        # remove most significant bit from each chunk
        mostSignificantBits = ""
        for bits in splitBinaryOfFakeDNA:
            mostSignificantBits += bits[0]
        # split it by 8 bit (one ascii)
        messageBinary = [mostSignificantBits[i:i+8] for i in range(0, len(mostSignificantBits), 8)]
        # perform xor and join
        binaryKey = "{0:b}".format(self.key1).rjust(8,'0')
        XORs = ""
        for value in messageBinary:
            XORs += self.xor(value, binaryKey)
            binaryKey = value
        # convert binary to ascii and reveal message
        bytes = [XORs[i:i+8] for i in range(0, len(XORs), 8)]
        decodeMessage = ""
        for byte in bytes[::-1]:
            decodeMessage += chr(int(byte, 2))
        return decodeMessage

    def findingPayload(self, input_DNA_sequence):
        # moving the payload by window size
        payload_parts = []
        for i in range(0, len(input_DNA_sequence), self.window_size):
            fakeDNA = input_DNA_sequence[i:]
            decodeMessage = self.decode(fakeDNA)
            gotPattern, values = self.has_pattern(decodeMessage)
            if gotPattern:
                payload_parts.extend(values)
        return payload_parts
