import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class P59_XorDecryption {
    // Project Euler - Problem 59 - XOR decryption
    // Each character on a computer is assigned a unique code and the preferred standard is ASCII (American Standard
    // Code for Information Interchange). For example, uppercase A = 65, asterisk (*) = 42, and lowercase k = 107.
    // A modern encryption method is to take a text file, convert the bytes to ASCII, then XOR each byte with a given
    // value, taken from a secret key. The advantage with the XOR function is that using the same encryption key on the
    // cipher text, restores the plain text; for example, 65 XOR 42 = 107, then 107 XOR 42 = 65.
    // For unbreakable encryption, the key is the same length as the plain text message, and the key is made up of
    // random bytes. The user would keep the encrypted message and the encryption key in different locations, and
    // without both "halves", it is impossible to decrypt the message.
    // Unfortunately, this method is impractical for most users, so the modified method is to use a password as a key.
    // If the password is shorter than the message, which is likely, the key is repeated cyclically throughout the
    // message. The balance for this method is using a sufficiently long password key for security, but short enough to
    // be memorable. Your task has been made easy, as the encryption key consists of three lower case characters.
    // Using cipher.txt (right click and 'Save Link/Target As...'), a file containing the encrypted ASCII codes, and the
    // knowledge that the plain text must contain common English words, decrypt the message and find the sum of the
    // ASCII values in the original text.

    static final String CYPHER_FILE = "file/p059_cipher.txt";
    static final String WORDS_FILE = "file/english-words-1000.txt";
    private static int keyCode = 0;
    private static Set words;

    public static void main(String[] args) throws IOException {
        String message = "\nThe encryption key is %s.\n";
        String key = P59_XorDecryption.findKey(CYPHER_FILE);
        System.out.printf(message, key);
    }

    private static String findKey(String cypherFile) throws IOException {
        loadEnglishWords();
        String encryptedMessage = getEncryptedMessage(cypherFile);
        String clearText;
        while (true) {
            String key = getKey();
            clearText = getDecryptedMessage(encryptedMessage, key);
            if (hasEnglishWords(clearText)) {
                System.out.println("clearText = " + clearText);
                return key;
            }
        }
    }

    private static String getEncryptedMessage(String cypherFile) throws IOException {
        List lines = new ResourceFile(cypherFile).getLines();
        String commaSeparatedCodes = lines.get(0).toString();
        String[] codeStringArray = commaSeparatedCodes.split(",");
        String encryptedMessage = "";
        for (String code : codeStringArray) encryptedMessage += (char) Integer.parseInt(code);
        return encryptedMessage;
    }

    private static String getDecryptedMessage(String encryptedMessage, String key) {
        String clearText = "";
        int position = 0;
        for (char code : encryptedMessage.toCharArray()) {
            char keyChar = key.charAt(position % 3);
            clearText += (char) (code ^ keyChar);
            position++;
        }
        return clearText;
    }

    private static void loadEnglishWords() throws IOException {
        List lines = new ResourceFile(WORDS_FILE).getLines();
        words = new HashSet<String>();
        for (Object line : lines) {
            String word = line.toString();
            words.add(word);
        }
    }

    static String getKey() {
        int positionValue = keyCode / 26 / 26;
        String key = Character.toString((char) ('a' + positionValue));
        positionValue = keyCode / 26 % 26;
        key += Character.toString((char) ('a' + positionValue));
        positionValue = keyCode % 26;
        key += Character.toString((char) ('a' + positionValue));
        keyCode++;
        return key;
    }

    private static boolean hasEnglishWords(String text) {
        String[] tokens = text.split(" ");
        int countTokens = 0;
        int englishWords = 0;
        for (String token : tokens) {
            countTokens++;
            if (words.contains(token)) englishWords++;
        }
        return 1.0 * englishWords / countTokens > 0.5;
    }
}

@@ -2089,28 +2089,60 @@
 The 
-encryption key
+sum of the ASCII codes in the original message
  is %25
-s
+d
 .%5Cn%22
@@ -2151,26 +2151,23 @@
         
-String key
+int sum
  = P59_X
@@ -2179,23 +2179,45 @@
 ryption.
-findKey
+decryptAndFindSumOfAsciiCodes
 (CYPHER_
@@ -2254,27 +2254,27 @@
 tf(message, 
-key
+sum
 );%0A    %7D%0A%0A  
@@ -2294,22 +2294,41 @@
 tic 
-String findKey
+int decryptAndFindSumOfAsciiCodes
 (Str
@@ -2677,107 +2677,489 @@
     
-System.out.println(%22clearText = %22 + clearText);%0A                return key;%0A            %7D%0A        %7D
+int sumOfAsciiCodes = getSumOfAsciiCodes(clearText);%0A                System.out.println(%22key = %22 + key);%0A                System.out.println(%22clearText = %22 + clearText);%0A                System.out.println(%22sumOfAsciiCodes = %22 + sumOfAsciiCodes);%0A                return sumOfAsciiCodes;%0A            %7D%0A        %7D%0A    %7D%0A%0A    private static int getSumOfAsciiCodes(String clearText) %7B%0A        int sum = 0;%0A        for (char code : clearText.toCharArray()) sum += code;%0A        return sum;
 %0A   

add summation of ASCII codes in decrypted, original message