package simulator;

enum ChallengeType {
    DEPTH,                      // Answer: last 10 symbols in British notation
    SETTING,                    // Answer according to subtype
    KEY_BREAKING,               // Answer according to subtype
    BREAKING}                   // Answer according to subtype

enum ChallengeSubType {         // Relevant only to SETTING, KEY_BREAKING, BREAKING
    FIND_CHI12,                 // Answer: Settings: two positions; Breaking: two patterns
    FIND_CHI,                   // Answer: Settings: five positions; Breaking: five patterns
    FIND_PSI_KNOWN_CHI_MU,      // Answer: 6-digit code
    FIND_MU_KNOWN_CHI_PSI,      // Answer: 6-digit code
    FIND_MU_PSI_KNOWN_CHI,      // Answer: 6-digit code
    FIND_ALL}                   // Answer: 6-digit code

public class Simulation {

    private static int MAX_LINE_WIDTH = 200;


    public static void depthChallenge(int challengeNumber, int level, ChallengeType challengeType, int len, int depth, Limitation limitation) {
        Tunny t = new Tunny(limitation);
        t.randomize();
        byte[][] plaintexts = new byte[depth][];
        byte[][] ciphertexts = new byte[depth][];
        for (int i = 0; i < depth; i++) {
            plaintexts[i] = Reference.randomPlaintextSegment(len);
            ciphertexts[i] = new byte[plaintexts[i].length];
            t.encipher(ciphertexts[i], len, plaintexts[i], true);
        }

        System.out.printf("\n\n\"LEVEL %d DEPTH\" CHALLENGE %d: Depth of %d (length %d), using Limitation: %s\n", level, challengeNumber, depth, len, limitation);
        for (int i = 0; i < depth; i++) {
            //System.out.printf("Ciphertext #%d: \n%s\n", i, Baudot.toBritishNotation(ciphertexts[i]));
            saveCiphertext(challengeNumber, level, ciphertexts, i);
        }
        System.out.println("Enter the last 10 symbols of the first plaintext (in British notation).");

        System.out.printf("\n\nSOLUTION FOR LEVEL %d CHALLENGE #%d: Depth of %d (length %d), using Limitation: %s\n", level, challengeNumber, depth, len, limitation);
        for (int i = 0; i < depth; i++) {
            //printPlaintext(plaintexts, i);
            saveFullPlaintext(challengeNumber, level, plaintexts, i, true);
            //printCiphertext(ciphertexts, i);
        }
        //printKey(t);
        saveKey(challengeNumber, level, t, true);
        printSolutionLast10Symbols(plaintexts[0]);
    }

    

    public static void challenge(int challengeNumber, int level, ChallengeType challengeType, ChallengeSubType challengeSubType, int len, Limitation limitation, int d) {
        Tunny t = new Tunny(limitation);
        t.randomize(d);
        byte[] plaintext = Reference.randomPlaintextSegment(len);
        byte[] ciphertext = new byte[plaintext.length];
        int code = -1;
        if (challengeSubType != ChallengeSubType.FIND_CHI12 && challengeSubType != ChallengeSubType.FIND_CHI) {
            code = Utils.random(100_000, 999_999);
            byte[] codeSegment = numericCodeSegment(code);
            replace(plaintext, codeSegment, plaintext.length - codeSegment.length);
        }
        t.encipher(ciphertext, len, plaintext, true);
        //Colossus.runDoubleDelta12(t, ciphertext, len);
        System.out.printf("\n\nLEVEL %d CHALLENGE #%d: ", level, challengeNumber);
        switch (challengeType) {
            case SETTING:
                switch (challengeSubType) {
                    case FIND_CHI12:
                        System.out.println(" \"SETTING\" CHALLENGE - the patterns for CHI1 and CHI2 wheels are known.");
                        System.out.println("You need to find the starting positions for CHI1 and CHI2.");
                        printChi12Patterns(t);
                        printLimitation(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        System.out.println("Enter the solution in the form of two numbers separated with a space. Note that positions are numbered from 0.");
                        System.out.println("For example, if the CHI1 and CHI2 starting positions are 37 and 0, respectively, enter \"37 0\".");
                        break;
                    case FIND_CHI:
                        System.out.println(" \"SETTING\" CHALLENGE - the patterns for the CHI wheels are known.");
                        System.out.println("You need to find the starting positions for the CHI wheels.");
                        printChiWheelPatterns(t);
                        printLimitation(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        System.out.println("Enter the solution in the form of five numbers separated with a space. Note that positions are numbered from 0.");
                        System.out.println("For example, if the CHI wheels starting positions are 37, 0, 11, 16, 21, respectively, enter \"37 0 11 16 21\".");
                        break;
                    case FIND_PSI_KNOWN_CHI_MU:
                        System.out.println(" \"SETTING\" CHALLENGE - all the wheel patterns are known, as well as the starting positions for the CHI and MU wheels.");
                        System.out.println("The starting positions for the PSI wheels are unknown.");
                        printChiWheelPatterns(t);
                        printPsiWheelPatterns(t);
                        printMuWheelPatterns(t);
                        printChiWheelPositions(t);
                        printMuWheelPositions(t);
                        printLimitation(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        System.out.println("You need to recover the PSI wheel starting positions so that you can decrypt the ciphertext.");
                        printTheSolutionIsACode();
                        break;
                    case FIND_MU_KNOWN_CHI_PSI:
                        System.out.println(" \"SETTING\" CHALLENGE - all the wheel patterns are known, as well as the starting positions for the CHI and PSI wheels.");
                        System.out.println("The starting positions for the MU wheels are unknown.");
                        printChiWheelPatterns(t);
                        printPsiWheelPatterns(t);
                        printMuWheelPatterns(t);
                        printChiWheelPositions(t);
                        printPsiWheelPositions(t);
                        printLimitation(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        System.out.println("You need to recover the MU wheel starting positions so that you can decrypt the ciphertext.");
                        printTheSolutionIsACode();
                        break;
                    case FIND_MU_PSI_KNOWN_CHI:
                        System.out.println(" \"SETTING\" CHALLENGE - all the wheel patterns are known, as well as the starting positions for the CHI wheels.");
                        System.out.println("The starting positions for the MU wheels and the PSI wheels are unknown.");
                        printChiWheelPatterns(t);
                        printPsiWheelPatterns(t);
                        printMuWheelPatterns(t);
                        printChiWheelPositions(t);
                        printLimitation(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        System.out.println("You need to recover the MU wheel and PSI wheel starting positions so that you can decrypt the ciphertext.");
                        printTheSolutionIsACode();
                        break;
                    case FIND_ALL:
                        System.out.println(" \"SETTING\" CHALLENGE - all the wheel patterns are known, but the wheel starting positions are unknown.");
                        printChiWheelPatterns(t);
                        printPsiWheelPatterns(t);
                        printMuWheelPatterns(t);
                        printLimitation(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        System.out.println("You need to recover the wheel starting positions so that you can decrypt the ciphertext.");
                        printTheSolutionIsACode();
                        break;
                }
                break;
            case KEY_BREAKING:
            case BREAKING:
                if (challengeType == ChallengeType.KEY_BREAKING) {
                    System.out.println(" \"KEY-BREAKING\" CHALLENGE - the plaintext is known. You need to recover wheel patterns from known-plaintext and ciphertext");
                } else {
                    System.out.println(" \"BREAKING\" CHALLENGE - the plaintext is unknown. You need to recover wheel patterns from ciphertext.");
                }
                switch (challengeSubType) {
                    case FIND_CHI12:
                        System.out.println("You need to find the patterns for the CHI1 and CHI2 wheels");
                        printLimitation(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        if (challengeType == ChallengeType.KEY_BREAKING) {
                            //printPlaintext(plaintext);
                            saveFullPlaintext(challengeNumber, level, plaintext, false);
                        }
                        System.out.println("Enter the solution in the form of two pattern strings separated with a space.");
                        System.out.println("You should assume that the starting position for both the CHI1 and CHI2 wheels is 0.");
                        break;
                    case FIND_CHI:
                        System.out.println("You need to find the patterns for the five CHI wheels");
                        printLimitation(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        if (challengeType == ChallengeType.KEY_BREAKING) {
                            //printPlaintext(plaintext);
                            saveFullPlaintext(challengeNumber, level, plaintext, false);
                        }
                        System.out.println("Enter the solution in the form of five pattern strings separated with spaces.");
                        System.out.println("You should assume that the starting position for all the CHI wheels is 0.");
                        break;
                    case FIND_PSI_KNOWN_CHI_MU:
                        System.out.println("The patterns and the starting positions for the CHI and MU wheels are known.");
                        System.out.println("The patterns for the PSI wheels are unknown.");
                        printLimitation(t);
                        printChiWheelPatterns(t);
                        printMuWheelPatterns(t);
                        printChiWheelPositions(t);
                        printMuWheelPositions(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        if (challengeType == ChallengeType.KEY_BREAKING) {
                            //printPlaintextRemoveCode(plaintext);
                            savePlaintextRemoveCode(challengeNumber, level, plaintext, false);
                        }
                        System.out.println("You need to recover the PSI wheel patterns so that you can decrypt the ciphertext.");
                        System.out.println("You may assume that the starting position of all PSI wheels is 0.");
                        printTheSolutionIsACode();
                        break;
                    case FIND_MU_KNOWN_CHI_PSI:
                        System.out.println("The patterns and the starting positions for the CHI and PSI wheels are known.");
                        System.out.println("The patterns for the MU wheels are unknown.");
                        printLimitation(t);
                        printChiWheelPatterns(t);
                        printPsiWheelPatterns(t);
                        printChiWheelPositions(t);
                        printPsiWheelPositions(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        if (challengeType == ChallengeType.KEY_BREAKING) {
                            //printPlaintextRemoveCode(plaintext);
                            savePlaintextRemoveCode(challengeNumber, level, plaintext, false);
                        }
                        System.out.println("You need to recover the MU wheel patterns so that you can decrypt the ciphertext.");
                        System.out.println("You may assume that the starting position of all MU wheels is 0.");
                        printTheSolutionIsACode();
                        break;
                    case FIND_MU_PSI_KNOWN_CHI:
                        System.out.println("The patterns and the starting positions for the CHI wheels are known.");
                        System.out.println("The patterns for the MU and PSI wheels are unknown.");
                        printLimitation(t);
                        printChiWheelPatterns(t);
                        printChiWheelPositions(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        if (challengeType == ChallengeType.KEY_BREAKING) {
                            //printPlaintextRemoveCode(plaintext);
                            savePlaintextRemoveCode(challengeNumber, level, plaintext, false);
                        }
                        System.out.println("You need to recover the MU wheel and PSI wheel patterns so that you can decrypt the ciphertext.");
                        System.out.println("You may assume that the starting position of all MU wheels and PSI wheels is 0.");
                        printTheSolutionIsACode();
                        break;
                    case FIND_ALL:
                        System.out.println("The patterns and the starting positions of all the wheels are unknown.");
                        printLimitation(t);
                        //printCiphertext(ciphertext);
                        saveCiphertext(challengeNumber, level, ciphertext);
                        if (challengeType == ChallengeType.KEY_BREAKING) {
                            //printPlaintextRemoveCode(plaintext);
                            savePlaintextRemoveCode(challengeNumber, level, plaintext, false);
                        }
                        System.out.println("You need to recover all the wheel patterns so that you can decrypt the ciphertext.");
                        System.out.println("You may assume that the starting position of all the wheels is 0.");
                        printTheSolutionIsACode();
                        break;
                }
                break;
        }


        System.out.printf("\n\nSOLUTION FOR LEVEL %d CHALLENGE #%d: \n", level, challengeNumber);
        //printKey(t);
        saveKey(challengeNumber, level, t, true);
        //printPlaintext(plaintext);
        saveFullPlaintext(challengeNumber, level, plaintext, true);
        //printCiphertext(ciphertext);
        switch (challengeType) {
            case SETTING:
                switch (challengeSubType) {
                    case FIND_CHI12:
                        printSolutionChi12WheelPositions(t);
                        break;
                    case FIND_CHI:
                        printSolutionChiWheelPositions(t);
                        break;
                    case FIND_PSI_KNOWN_CHI_MU:
                    case FIND_MU_KNOWN_CHI_PSI:
                    case FIND_MU_PSI_KNOWN_CHI:
                    case FIND_ALL:
                        printSolutionCode(code);
                        break;
                }
                break;
            case KEY_BREAKING:
            case BREAKING:
                switch (challengeSubType) {
                    case FIND_CHI12:
                        printSolutionChi12WheelPatterns(t);
                        break;
                    case FIND_CHI:
                        printSolutionChiWheelPatterns(t);
                        break;
                    case FIND_PSI_KNOWN_CHI_MU:
                    case FIND_MU_KNOWN_CHI_PSI:
                    case FIND_MU_PSI_KNOWN_CHI:
                    case FIND_ALL:
                        printSolutionCode(code);
                        break;
                }
                break;
        }

    }

    private static void saveKey(int challengeNumber, int level, Tunny t, boolean solution) {
        String fileName = "tunnyLevel" + level + "Challenge" + challengeNumber + "Key" + (solution ? "Solution" : "") + ".txt";
        Utils.writeFile(fileName, t.toString());
    }
    private static void saveCiphertext(int challengeNumber, int level, byte[] ciphertext) {
        String fileName = "tunnyLevel" + level + "Challenge" + challengeNumber + "Ciphertext.txt";
        Utils.writeFile(fileName, Baudot.toBritishNotation(ciphertext));
    }
    private static void saveCiphertext(int challengeNumber, int level, byte[][] ciphertexts, int index) {
        String fileName = "tunnyLevel" + level + "Challenge" + challengeNumber + "Ciphertext"+ (index + 1) +".txt";
        Utils.writeFile(fileName, Baudot.toBritishNotation(ciphertexts[index]));
    }
    private static void saveFullPlaintext(int challengeNumber, int level, byte[] plaintext, boolean solution) {
        String fileName = "tunnyLevel" + level + "Challenge" + challengeNumber + "Plaintext" + (solution ? "Solution" : "") + ".txt";
        Utils.writeFile(fileName, Baudot.toBritishNotation(plaintext));
    }
    private static void saveFullPlaintext(int challengeNumber, int level, byte[][] plaintexts, int index, boolean solution) {
        String fileName = "tunnyLevel" + level + "Challenge" + challengeNumber + "Plaintext" + (index + 1) + "" + (solution ? "Solution" : "") + ".txt";
        Utils.writeFile(fileName, Baudot.toBritishNotation(plaintexts[index]));
    }
    private static void savePlaintextRemoveCode(int challengeNumber, int level, byte[] plaintext, boolean solution) {
        String fileName = "tunnyLevel" + level + "Challenge" + challengeNumber + "Plaintext" + (solution ? "Solution" : "") + ".txt";
        Utils.writeFile(fileName, Baudot.toBritishNotation(plaintext).substring(0, plaintext.length - 12));
    }
    private static byte[] numericCodeSegment(int code) {
        return Baudot.fromReadableNotation(String.format("<<<%06d>>>", code));
    }

    private static void replace(byte[] part1, byte[] part2, int position) {
        System.arraycopy(part2, 0, part1, position, part2.length);
    }

    private static void printTheSolutionIsACode() {
        System.out.println("The solution is a 6-digit code at the end of the plaintext.");
    }

    private static void printSolutionCode(int code) {
        System.out.printf("The solution (6-digit code): %06d\n", code);
    }
    private static void printSolutionLast10Symbols(byte[] plaintext) {
        String firstPlaintextString = Baudot.toBritishNotation(plaintext);
        System.out.printf("The solution (last 10 symbols): %s\n", firstPlaintextString.substring(firstPlaintextString.length() - 10));
    }
    
    private static void printKey(Tunny t) {
        System.out.println("The encryption key (patterns and starting positions for CHI 1-5 / PSI 1-5 / MU 1-2): ");
        t.print(false);
    }

    private static void printFormattedLongText(String header, String s) {
        System.out.printf("%s:\n", header);
        if (s.length() < MAX_LINE_WIDTH) {
            System.out.println(s);
        } else {
            for (int i = 0; i < s.length(); i += MAX_LINE_WIDTH) {
                System.out.println(s.substring(i, Math.min(i + MAX_LINE_WIDTH, s.length())));
            }
        }
    }
    private static void printPlaintext(byte[] plaintext) {
        printFormattedLongText("Plaintext", Baudot.toBritishNotation(plaintext));
    }

    private static void printPlaintextRemoveCode(byte[] plaintext) {
        printFormattedLongText("Plaintext", Baudot.toBritishNotation(plaintext).substring(0, plaintext.length - 12));
    }
    private static void printPlaintext(byte[][] plaintexts, int index) {
        printFormattedLongText("Plaintext " + index , Baudot.toBritishNotation(plaintexts[index]));
    }

    private static void printCiphertext(byte[] ciphertext) {
        printFormattedLongText("Ciphertext", Baudot.toBritishNotation(ciphertext));
    }
    private static void printCiphertext(byte[][] ciphertexts, int index) {
        printFormattedLongText("Ciphertext " + index , Baudot.toBritishNotation(ciphertexts[index]));
    }


    private static void printLimitation(Tunny t) {
        System.out.printf("Limitation:  %s\n", t.limitation);
    }

    private static void printChi12Patterns(Tunny t) {
        System.out.printf("CHI1 pattern: %s\n", Patterns.toString(t.patterns.chi[Tunny.CHI1]));
        System.out.printf("CHI2 pattern: %s\n", Patterns.toString(t.patterns.chi[Tunny.CHI2]));
    }
    private static void printSolutionChiWheelPatterns(Tunny t) {
        System.out.print("The solution (the CHI wheel patterns): ");
        for (int w = 0; w < Tunny.CHI_NUM; w++) {
            System.out.printf("%s ", Patterns.toString(t.patterns.chi[w]));
        }
        System.out.println();
    }
    private static void printSolutionChi12WheelPatterns(Tunny t) {
        System.out.print("The solution (the CHI1 and CHI2 wheel patterns): ");
        for (int w = 0; w < 2; w++) {
            System.out.printf("%s ", Patterns.toString(t.patterns.chi[w]));
        }
        System.out.println();
    }
    private static void printChiWheelPatterns(Tunny t) {
        System.out.printf("CHI1 pattern: %s\n", Patterns.toString(t.patterns.chi[Tunny.CHI1]));
        System.out.printf("CHI2 pattern: %s\n", Patterns.toString(t.patterns.chi[Tunny.CHI2]));
        System.out.printf("CHI3 pattern: %s\n", Patterns.toString(t.patterns.chi[Tunny.CHI3]));
        System.out.printf("CHI4 pattern: %s\n", Patterns.toString(t.patterns.chi[Tunny.CHI4]));
        System.out.printf("CHI5 pattern: %s\n", Patterns.toString(t.patterns.chi[Tunny.CHI5]));
    }

    private static void printPsiWheelPatterns(Tunny t) {
        System.out.printf("PSI1 pattern: %s\n", Patterns.toString(t.patterns.psi[Tunny.PSI1]));
        System.out.printf("PSI2 pattern: %s\n", Patterns.toString(t.patterns.psi[Tunny.PSI2]));
        System.out.printf("PSI3 pattern: %s\n", Patterns.toString(t.patterns.psi[Tunny.PSI3]));
        System.out.printf("PSI4 pattern: %s\n", Patterns.toString(t.patterns.psi[Tunny.PSI4]));
        System.out.printf("PSI5 pattern: %s\n", Patterns.toString(t.patterns.psi[Tunny.PSI5]));
    }
    private static void printMuWheelPatterns(Tunny t) {
        System.out.printf("MU1 pattern: %s\n", Patterns.toString(t.patterns.mu[Tunny.MU1]));
        System.out.printf("MU2 pattern: %s\n", Patterns.toString(t.patterns.mu[Tunny.MU2]));
    }

    private static void printChiWheelPositions(Tunny t) {
        System.out.print("CHI wheels starting positions: ");
        for (int w = 0; w < Tunny.CHI_NUM; w++) {
            System.out.printf("%d ", t.startPositions.chi[w]);
        }
        System.out.println();
    }
    private static void printSolutionChiWheelPositions(Tunny t) {
        System.out.print("The solution (the CHI wheel starting positions): ");
        for (int w = 0; w < Tunny.CHI_NUM; w++) {
            System.out.printf("%d ", t.startPositions.chi[w]);
        }
        System.out.println();
    }
    private static void printSolutionChi12WheelPositions(Tunny t) {
        System.out.print("The solution (the CHI2 and CHI2 wheel starting positions): ");
        for (int w = 0; w < 2; w++) {
            System.out.printf("%d ", t.startPositions.chi[w]);
        }
        System.out.println();
    }
    private static void printPsiWheelPositions(Tunny t) {
        System.out.print("PSI wheels starting positions: ");
        for (int w = 0; w < Tunny.PSI_NUM; w++) {
            System.out.printf("%d ", t.startPositions.psi[w]);
        }
        System.out.println();
    }
    private static void printMuWheelPositions(Tunny t) {
        System.out.print("MU wheels starting positions: ");
        for (int w = 0; w < Tunny.MU_NUM; w++) {
            System.out.printf("%d ", t.startPositions.mu[w]);
        }
        System.out.println();
    }
}
