import os
import re
import time

# Identifies current file location
currentfilepath = os.path.abspath(__file__)
currentfslocation = os.path.dirname(currentfilepath)

# Tables to translate letters to numbers and vice versa
idtables = {
    "a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6, "g": 7, "h": 8, "i": 9, "j": 10, "k": 11, "l": 12, "m": 13, "n": 14, "o": 15, "p": 16, "q": 17, "r": 18, "s": 19, "t": 20, "u": 21, "v": 22, "w": 23, "x": 24, "y": 25, "z": 26
    , "A": 1, "B": 2, "C": 3, "D": 4, "E": 5, "F": 6, "G": 7, "H": 8, "I": 9, "J": 10, "K": 11, "L": 12, "M": 13, "N": 14, "O": 15, "P": 16, "Q": 17, "R": 18, "S": 19, "T": 20, "U": 21, "V": 22, "W": 23, "X": 24, "Y": 25, "Z": 26
}
revidtables = {
    1: "a", 2: "b", 3: "c", 4: "d", 5: "e", 6: "f", 7: "g", 8: "h", 9: "i", 10: "j", 11: "k", 12: "l", 13: "m", 14: "n", 15: "o", 16: "p", 17: "q", 18: "r", 19: "s", 20: "t", 21: "u", 22: "v", 23: "w", 24: "x", 25: "y", 26: "z"
}

# The actual translater (Encoder to numbers)
def toidconverter(toencode):
    idlist = []
    for letter in toencode:
        if not letter.isalpha():
            ids = letter
        if letter.isalpha():
            ids = idtables.get(letter)
        idlist.append(ids)
    return idlist

# The actual translater (Decoder to text)
def fromidconverter(todecode):
    letterlist = []
    for value in todecode:
        if not isinstance(value, int):
            ids = str(value)
        if isinstance(value, int):
            ids = revidtables.get(value)
        letterlist.append(ids)
    return letterlist



# Encrypts messages
def encrypter(message, key):
    # Filter the key to strictly contain integers (valid letters) to prevent math crashes
    key = [k for k in key if isinstance(k, int)]
    if not key: # Failsafe if the key is empty or has no valid letters
        return "".join(fromidconverter(message))

    pullcounter = 0
    elist = []
    for singleid in message:
        if isinstance(singleid, int):
            pullindex = pullcounter % len(key)
            currentlocation = key[pullindex]

            elist.append(((singleid + currentlocation - 2) % 26) + 1)

            pullcounter += 1
        else:
            elist.append(singleid)
    return "".join(fromidconverter(elist))



# Decrypts messages
def decrypter(emessage, ekey):
    # Filter the key to strictly contain integers (valid letters) to prevent math crashes
    ekey = [k for k in ekey if isinstance(k, int)]
    if not ekey: # Failsafe if the key is empty or has no valid letters
        return "".join(fromidconverter(emessage))

    pullcounter = 0
    elist = []
    for singleid in emessage:
        if isinstance(singleid, int):
            pullindex = pullcounter % len(ekey)
            currentlocation = ekey[pullindex]

            elist.append(((singleid - currentlocation) % 26) + 1)

            pullcounter += 1
        else:
            elist.append(str(singleid))
    return "".join(fromidconverter(elist))



# Cracks the code for messages
def cracker(message, keyfile):
        checkset = set() # Set for checking each code
        keylist = [] # List of keys (Gets populated by the below code)
        
        # MODIFIED: Appending .lower() to ensure all keys are standardized
        if keyfile == "bruteforce": 
            with open(f"{currentfslocation}/words.txt", "r", encoding="utf-8", errors="replace") as file:
                for line in file:
                    if not line.isascii():
                        continue
                    keylist.append(line.strip().lower())
        else:
            with open(f"{currentfslocation}/{keyfile}", "r", encoding="utf-8", errors="replace") as keylist2: 
                for line in keylist2:
                    if not line.isascii():
                        continue
                    keylist.append(line.strip().lower())

        # MODIFIED: Remove duplicates from the keylist while preserving order
        keylist = list(dict.fromkeys(keylist))

        # MODIFIED: Appending .lower() to ensure validation words are standard
        with open(f"{currentfslocation}/words.txt", "r", encoding="utf-8", errors="replace") as datalist: 
            for data in datalist:
                if not data.isascii():
                    continue
                checkset.add(str(data.strip()).lower())

        first3words = ' '.join(re.findall(r'\w+', message)[:3]) # Gets the first 3 words of the message for cracking


        # The key cracking manager, supports less than 3 words with lower accurcy
        for key in keylist:
            current = decrypter(toidconverter(first3words),toidconverter(key))
            current = current.split(" ")
            try:
                if current[0] in checkset and current[1] in checkset and current[2] in checkset:
                    print(f"Your Key Is (High Accurcy): {key}")
                    print(f"Full Decoded Phrase: {decrypter(toidconverter(message), toidconverter(key))}")
            except IndexError:
                try:
                    if current[0] in checkset and current[1] in checkset:
                        print(f"Your Key Is (Lower Accurcy): {key}")
                        print(f"Full Decoded Phrase: {decrypter(toidconverter(message), toidconverter(key))}")
                except IndexError:
                    if current[0] in checkset:
                        print(f"Your Key Is (Multiple Potential Keys): {key}")
                        print(f"Full Decoded Phrase: {decrypter(toidconverter(message), toidconverter(key))}")



# User interactions
running = True
while running:
    print("TBCipher toolkit v1.0-release\nWhat would you like to do:")
    print("[1] Encrypt a message with key  [2] Decrypt a message with key  [3] Crack a key  [0] Exit")
    task = input("> ")

    if task == "0":
        running = False

    if task == "1":
        print("Enter Your Message:")
        message = toidconverter(input("> "))
        print("Enter Your Key:")
        key = toidconverter(input("> "))
        print(encrypter(message,key))
        input("Press ENTER to continue...")

    if task == "2":
        print("Enter Your Encrypted Message: ")
        emessage = toidconverter(input("> "))
        print("Enter Your Key")
        ekey = toidconverter(input("> "))
        print(decrypter(emessage,ekey))
        input("Press ENTER to continue...")

    if task == "3":
        print("Enter Your Encrypted Message:")
        cmessage = input("> ")
        print('Enter Keylist Filename (Make sure it is in the directory where this script is) Or Enter "bruteforce" To Use Checklist as Keylist:')
        ckeyfile = input("> ")
        cracker(cmessage, ckeyfile)
        input("Press ENTER to continue...")