package simulator;

/**
 * Set of utilities to convert text from and to Baudot alphabet.
 * In WII, Bletchely Park had a special notations for the 6 control characters.
 * Another notation, "readable" is available, for a readable decryption (figure symbols displayed as plaintext) which can be matched against the ciphertext (single line, same length).
 */
class Baudot {

    // Parameters to format raw text.
    private final static boolean LETTER_SHIFT_BEFORE_SPACE = true; // 9 --> 89 (if DOUBLE_LETTER_OR_FIGURE_SHIFT = true then 9 -> 889)
    private final static boolean DOUBLE_LETTER_OR_FIGURE_SHIFT = true;  // when false: full stop . --> +N8 ; when true . -> ++N88    (note that usually there is a space after that: ++N889).


    // Figure symbols - they are the same for all notations. ENQ and BEL have no printable equivalend, so using @ and * instead.
    private static final String FIGURES = "$3$-$'87$@4*,!:(5+)2£6019?&$./;$";  // $ = special symbols.
    //          @ = ENQ
    //            * = BEL

    private static final byte BYTE_CR = 2;
    private static final byte BYTE_LF = 8;
    private static final byte BYTE_LTRS = 31;
    private static final byte BYTE_SP = 4;
    private static final byte BYTE_FIGS = 27;
    private static final byte BYTE_NUL = 0;

    // Raw plaintext alphabet - all valid symbols, with additional symbols represented with closest equivalents.
    private static final String RAW_PLAINTEXT_LETTERS = " ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÃÅΆĄÂªªÇČÐĎΛĚÊÈÉĘËĮÎÌÍÏŁŇŃÑØÒÓÔŐÕΘº°ǪΦÞŘŔŠ§ŤÚŰÙÛŮ×ÝŻŽŹ\t";
    private static final String PLAINTEXT_LETTERS_MAP = " ABCDEFGHIJKLMNOPQRSTUVWXYZAAAAAAAAACCDDDEEEEEEIIIIILNNNOOOOOOOOOOPPRRSSTUUUUUXYZZZ ";
    private static final String RAW_PLAINTEXT_FIGURES = "3-'87@4*,!:(5+)2£6019?&./;{}[]<>~%–—_=$#|«»’^“”·̨" + "\"" + "\\";
    private final static String PLAINTEXT_FIGURES_MAP = "3-'87@4*,!:(5+)2£6019?&./;()()()&&----£&/''''''.," + "'"  + "/";

    // British notation, used at Bletchley Park.
    private static final char BRITISH_CR = '3';
    private static final char BRITISH_LF = '4';
    private static final char BRITISH_LTRS = '8';
    private static final char BRITISH_FIGS = '+';
    private static final char BRITISH_SP = '9';
    private static final char BRITISH_NUL = '/';

    private static final String BRITISH_LETTERS = letters(BRITISH_CR, BRITISH_LF, BRITISH_LTRS, BRITISH_FIGS, BRITISH_SP, BRITISH_NUL);

    // Readable notation - each symbol represented by one symbol (no loss of information), properly reproducing figure shift symbols.
    // Useful when matching symbol by symbol against ciphertext in single line (while being able to read figure symbols).
    private static final char READABLE_CR = '#';
    private static final char READABLE_LF = '|';
    private static final char READABLE_LTRS = '>';
    private static final char READABLE_FIGS = '<';
    private static final char READABLE_SP = ' ';
    private static final char READABLE_NUL = '_';
    private static final String READABLE_LETTERS = letters(READABLE_CR, READABLE_LF, READABLE_LTRS, READABLE_FIGS, READABLE_SP, READABLE_NUL);


    // Build the letter mode symbols.
    private static String letters(char cr, char lf, char ltrs, char figs, char sp, char nul) {
        return ("" + nul + "E" + lf + "A" + sp + "SIU" + cr + "DRJNFCKTZLWHYPQOBG" + figs + "MXV" + ltrs);
    }

    // Parse raw plaintext. Replace unsupported characters with closest equivalents in Baudot (some unknown symbols may be discarded).
    // Apply the rules for adding additional/double shift symbols.
    static byte[] fromRawPlaintext(String plaintextString) {

        // Convert special German symbols
        plaintextString = plaintextString
                .replaceAll("ß", "SS")
                .toUpperCase()   // uppercase only after replacing ß
                .replaceAll("Ö", "OE")
                .replaceAll("Œ", "OE")
                .replaceAll("Ü", "UE")
                .replaceAll("Æ", "AE")
                .replaceAll("Ä", "AE");


        StringBuilder s = new StringBuilder();
        boolean figureShift = false;

        for (char c : plaintextString.toCharArray()) {

            if ((int) c > 65000) {
                continue;
            }

            switch (c) {
                case ' ':
                    if (figureShift || LETTER_SHIFT_BEFORE_SPACE) {
                        s.append(READABLE_LTRS);
                        if (DOUBLE_LETTER_OR_FIGURE_SHIFT) {
                            s.append(READABLE_LTRS);
                        }
                    }
                    figureShift = false;
                    s.append(READABLE_SP);
                    break;
                case '\n':
                    if (figureShift) {
                        s.append(READABLE_LTRS);
                        if (DOUBLE_LETTER_OR_FIGURE_SHIFT) {
                            s.append(READABLE_LTRS);
                        }
                        figureShift = false;
                    }
                    s.append(READABLE_LF);
                    break;
                case '\r':
                    if (figureShift) {
                        s.append(READABLE_LTRS);
                        if (DOUBLE_LETTER_OR_FIGURE_SHIFT) {
                            s.append(READABLE_LTRS);
                        }
                        figureShift = false;
                    }
                    s.append(READABLE_CR);
                    break;
                default:
                    int index = RAW_PLAINTEXT_LETTERS.indexOf(c);
                    if (index != -1) {
                        if (figureShift) {
                            s.append(READABLE_LTRS);
                            if (DOUBLE_LETTER_OR_FIGURE_SHIFT) {
                                s.append(READABLE_LTRS);
                            }
                            figureShift = false;
                        }
                        s.append(PLAINTEXT_LETTERS_MAP.charAt(index));
                        continue;
                    }
                    index = RAW_PLAINTEXT_FIGURES.indexOf(c);
                    if (index != -1) {
                        if (!figureShift) {
                            s.append(READABLE_FIGS);
                            if (DOUBLE_LETTER_OR_FIGURE_SHIFT) {
                                s.append(READABLE_FIGS);
                            }
                            figureShift = true;
                        }
                        s.append(PLAINTEXT_FIGURES_MAP.charAt(index));
                        continue;
                    }

                    break;
            }
        }
        return fromReadableNotation(s.toString());
    }
    static String toRawPlaintext(byte[] bytes) {
        StringBuilder s = new StringBuilder();
        boolean figureShift = false;
        for (byte b : bytes) {

            switch (b) {
                case BYTE_FIGS:
                    figureShift = true;
                    break;
                case BYTE_LTRS:
                    figureShift = false;
                    break;
                case BYTE_CR:
                    break;
                case BYTE_LF:
                    s.append('\n');
                    break;
                case BYTE_SP:
                    s.append(' ');
                    break;
                case BYTE_NUL:
                    break;
                default:
                    if (figureShift) {
                        s.append(FIGURES.charAt(b));
                    } else {
                        s.append(READABLE_LETTERS.charAt(b));
                    }
                    break;
            }
        }
        return s.toString();

    }

    // Parse/create readable plaintext.
    static byte[] fromReadableNotation(String readableNotationString) {
        int len = readableNotationString.length();
        byte[] bytes = new byte[len];
        boolean figure = false;
        for (int i = 0; i < len; i++) {
            char c = readableNotationString.charAt(i);
            byte b;
            switch (c) {
                case READABLE_SP:
                    b = BYTE_SP;
                    break;
                case READABLE_NUL:
                    b = BYTE_NUL;
                    break;
                case READABLE_LTRS:
                    figure = false;
                    b = BYTE_LTRS;
                    break;
                case READABLE_FIGS:
                    figure = true;
                    b = BYTE_FIGS;
                    break;
                case READABLE_LF:
                    b = BYTE_LF;
                    break;
                case READABLE_CR:
                    b = BYTE_CR;
                    break;
                default:
                    if (figure) {
                        b = (byte) Baudot.FIGURES.indexOf(c);
                    } else {
                        b = (byte) Baudot.READABLE_LETTERS.indexOf(c);
                    }
                    if (b == -1) {
                        throw new RuntimeException("Invalid text in readable notation: unknown symbol " + readableNotationString.charAt(i) + " at position " + i);
                    }
                    break;
            }

            bytes[i] = b;
        }
        return bytes;
    }
    static String toReadableNotation(byte[] bytes) {
        StringBuilder s = new StringBuilder();
        boolean figureShift = false;
        for (byte b : bytes) {

            switch (b) {
                case BYTE_FIGS:
                    s.append(READABLE_FIGS);
                    figureShift = true;
                    break;
                case BYTE_LTRS:
                    s.append(READABLE_LTRS);
                    figureShift = false;
                    break;
                case BYTE_CR:
                    s.append(READABLE_CR);
                    break;
                case BYTE_LF:
                    s.append(READABLE_LF);
                    break;
                case BYTE_SP:
                    s.append(READABLE_SP);
                    break;
                case BYTE_NUL:
                    s.append(READABLE_NUL);
                    break;
                default:
                    if (figureShift) {
                        s.append(FIGURES.charAt(b));
                    } else {
                        s.append(READABLE_LETTERS.charAt(b));
                    }
                    break;
            }
        }
        return s.toString();

    }

    // Parse/create Baudot sequence with British notation.
    static byte[] fromBritishNotation(String britishNotationString) {

        int len = britishNotationString.length();
        byte[] bytes = new byte[len];
        for (int i = 0; i < len; i++) {
            int index = Baudot.BRITISH_LETTERS.indexOf(britishNotationString.charAt(i));
            if (index  == -1) {
                throw new RuntimeException("Invalid text in British notation: unknown symbol " + britishNotationString.charAt(i) + " at position " + i);
            }
            bytes[i] = (byte) index;
        }
        return bytes;
    }
    static String toBritishNotation(byte[] bytes) {
        StringBuilder s = new StringBuilder();
        for (byte c : bytes) {
            s.append(BRITISH_LETTERS.charAt(c));
        }
        return s.toString();
    }

    public static byte[] readRawPlaintextFile(String fileName) {
        String str = Utils.readFile(fileName);
        str = str.replaceAll("[ ]+", " ")
                .replaceAll("(\\. )+", ". ")
                .replaceAll("(\\r\\n)+", "\\\r\\\n");
        return fromRawPlaintext(str);
    }

    public static byte[] readBritishNotationFile(String fileName) {
        String str = Utils.readFile(fileName);
        str = str.replaceAll("[^A-Z3489+/]+", "");
        return fromBritishNotation(str);
    }

    public static void main(String[] args) {
        String rawPlaintext = "Diese Wahrscheinlichkeit lag um so näher, als sich in der am 21.\r\n" +
                "Dezember 1916 in Berlin überreichten Friedensnote der amerikanischen\n" +
                "Regierung der Passus fand, daß die Interessen der Vereinigten Staaten\n" +
                "durch den Krieg 'ernstlich in Mitleidenschaft gezogen seien'; und daß\n" +
                "das Interesse der Union an einer baldigen Beendigung des Krieges sich\n" +
                "daraus ergebe, daß »sie offenkundig genötigt wäre, Bestimmungen über\n" +
                "den bestmöglichen Schutz ihrer Interessen zu treffen, falls der Krieg\n" +
                "fortdauern sollte«." +
                "\n" +
                "In  this  is  {likewise}  included  a  sum  of\n" +
                "9,721_l (+/- 1,000_l)._  received  from  theatres,  fairs,  and  \"races\".\n" +
                "Police  Force,  is  put  down  at  [41,714_l._],  and  <the>  expenditure  at\n" +
                "41,315_l._,  the  gross  pay,  irrespective  (of  other  charges  to  the  force),\n" +
                "amounting  to  29,800_l._\n" +
                "\n" +
                "";

        byte[] bytes = fromRawPlaintext(rawPlaintext);
        System.out.println(toReadableNotation(bytes));
        System.out.println(toBritishNotation(bytes));
        System.out.println(toReadableNotation(fromBritishNotation(toBritishNotation(bytes))));

        System.out.println(rawPlaintext);
        System.out.println(toRawPlaintext(bytes));

    }

}
