#! usr/bin/env python3

"""
I intend this to be a crytanalysis suite that can analyze an arbitrary
ciphertext and provide details on what the encryption method might be,
what the key might look like, and/or a potential cipher text solution.
"""

ORIGINAL_CIPHERTEXT = "\
NAGQNXIIZAGBGIIYXQOMQUGQUZAXTNGMYXQGTTASNISQO\
AMFGZAGEZVOOGUZAGIGMTAMQUTZYMXQGUMCMYZDECMLWS\
RVQYVIEASVQUTXLMQQSZTZMYZZAGDMOMXQSQMPVMYYESR\
WQSNIGUOGZAGEAMZGZSAVQZXLMQAMVIZAGDMQUVYOGZAG\
DQSDSYGQSDSYGLMQXGQUVYGZSBGMYZAGBYVQZSRZAGBSS\
WTZAMZIXGSVZSQZAGUGTWTMRVIIZAYGGTLSYGSRTGFGYM\
IXQTVIZTSRBISZZGUCMOGTMQUTLYMNISRTISFGQIENSYW\
ZAMZZAGEAMFGSRRGYGUDGXMDTXLWMQUZXYGUDSYGZAMQM\
QEZAYMIIVCSQZAGNSSUTZMLWTNSYWXQONGMYXGUIE"

# Letter frequencies taken from Table 1.1 from Understanding Crypto, page 9
ENGLISH_LETTER_FREQUENCY = {
    'A' : .0817,
    'B' : .0150,
    'C' : .0278,
    'D' : .0425,
    'E' : .1270,
    'F' : .0223,
    'G' : .0202,
    'H' : .0609,
    'I' : .0697,
    'J' : .0015,
    'K' : .0077,
    'L' : .0403,
    'M' : .0241,
    'N' : .0675,
    'O' : .0751,
    'P' : .0193,
    'Q' : .0010,
    'R' : .0599,
    'S' : .0633,
    'T' : .0906,
    'U' : .0276,
    'V' : .0098,
    'W' : .0236,
    'X' : .0015,
    'Y' : .0197,
    'Z' : .0007
}

substitution_dict = {
    'A' : '_',
    'B' : '_',
    'C' : '_',
    'D' : '_',
    'E' : '_',
    'F' : '_',
    'G' : '_',
    'H' : '_',
    'I' : '_',
    'J' : '_',
    'K' : '_',
    'L' : '_',
    'M' : '_',
    'N' : '_',
    'O' : '_',
    'P' : '_',
    'Q' : '_',
    'R' : '_',
    'S' : '_',
    'T' : '_',
    'U' : '_',
    'V' : '_',
    'W' : '_',
    'X' : '_',
    'Y' : '_',
    'Z' : '_'
}

def find_letter_distribution(ciphertext, percentage = False):
    """ 
    Returns a dictionary containing letter frequencies of each
    letter included in the string  (Normalized if 'percentage',
    absolute otherwise.)
    """
    distribution = dict()

    for character in ciphertext:
    # Keep a running total in 'distribution' of character count
        if character not in distribution:
            distribution[character] = 1
        else:
            distribution[character] += 1
    
    # Normalize the data if percentage is requested
    if percentage:
        for char in distribution:
            distribution[char] /= len(ciphertext)

    return distribution
    
    # Print the highest frequency letters first
#    for char in sorted(distribution, key=distribution.get, reverse=True):
#        print(char,'has a frequency of',distribution[char])
    

def translate_from_dictionary(ciphertext, translate):
    """
    Translate the ciphertext using the 
    'translate' dictionary as a substitution table.
    """
    plaintext = ''
    for character in ciphertext:
        plaintext += translate[character]

    return plaintext

def count_digraphs(ciphertext):
    """
    Count and print the most frequent 2-letter 
    combinations in the ciphertext.
    """
    # Zip the ciphertext with an offset to get 2char elements
    two_letter_set = [x+y for x,y in zip(*[ciphertext[i:] 
                       for i in range(2)])]

    digraph_frequency = dict()

    for digraph in two_letter_set:
        if digraph not in digraph_frequency:
            digraph_frequency[digraph] = 1
        else:
            digraph_frequency[digraph] += 1

    print('2-letter sequences:')
    for digraph in sorted(digraph_frequency, 
                          key=digraph_frequency.get, 