package simulator;

class Tunny {
    static final int[] CHI_PERIOD = {41, 31, 29, 26, 23};
    static final int[] PSI_PERIOD = {43, 47, 51, 53, 59};
    static final int[] MU_PERIOD = {61, 37};

    static final int PSI_NUM = 5;
    static final int CHI_NUM = 5;
    static final int MU_NUM = 2;

    static final int CHI1 = 0;
    static final int CHI2 = CHI1 + 1;
    static final int CHI3 = CHI2 + 1;
    static final int CHI4 = CHI3 + 1;
    static final int CHI5 = CHI4 + 1;
    static final int PSI1 = 0;
    static final int PSI2 = PSI1 + 1;
    static final int PSI3 = PSI2 + 1;
    static final int PSI4 = PSI3 + 1;
    static final int PSI5 = PSI4 + 1;
    static final int MU1 = 0;
    static final int MU2 = MU1 + 1;

    private static final int CHI1_PERIOD = CHI_PERIOD[CHI1];
    private static final int CHI2_PERIOD = CHI_PERIOD[CHI2];
    private static final int CHI3_PERIOD = CHI_PERIOD[CHI3];
    private static final int CHI4_PERIOD = CHI_PERIOD[CHI4];
    private static final int CHI5_PERIOD = CHI_PERIOD[CHI5];
    private static final int PSI1_PERIOD = PSI_PERIOD[PSI1];
    private static final int PSI2_PERIOD = PSI_PERIOD[PSI2];
    private static final int PSI3_PERIOD = PSI_PERIOD[PSI3];
    private static final int PSI4_PERIOD = PSI_PERIOD[PSI4];
    private static final int PSI5_PERIOD = PSI_PERIOD[PSI5];
    private static final int MU1_PERIOD = MU_PERIOD[MU1];
    private static final int MU2_PERIOD = MU_PERIOD[MU2];

    private static final byte BIT5 = 0x10;
    private static final byte BIT4 = 0x08;
    private static final byte BIT3 = 0x04;
    private static final byte BIT2 = 0x02;
    private static final byte BIT1 = 0x01;

    Limitation limitation;
    Patterns patterns;
    Positions startPositions;

    private Tunny(Limitation limitation, Patterns patterns, Positions startPositions) {
        if (limitation != null) {
            this.limitation = limitation;
        } else {
            this.limitation = Limitation.NO_LIMITATION;
        }
        if (patterns != null) {
            this.patterns = new Patterns(patterns);
        } else {
            this.patterns = new Patterns();
        }
        if (startPositions != null) {
            this.startPositions = new Positions(startPositions);
        } else {
            this.startPositions = new Positions();

        }
    }
    Tunny() {
        this(null, null, null);
    }
    Tunny(Limitation limitation) {
        this(limitation, null, null);
    }

    void randomize(int d) {
        patterns.randomize(d);
        startPositions.randomize();
    }
    void randomize() {
        patterns.randomize();
        startPositions.randomize();
    }

    void encipher(byte[] outputStream, int len, byte[] inputStream, boolean encipher) {

        boolean chi2OneBack = false;
        boolean psi1OneBack = false;
        boolean p5OneBack = false;
        boolean p5TwoBack= false;


        int mu37Moves = 0;
        int psiMoves = 0;
        for (int p = 0; p < len; p++) {
            byte symbol = 0;

            boolean chiBit1 = patterns.chi[CHI1][(p + startPositions.chi[CHI1]) % CHI1_PERIOD];
            boolean psiBit1 = patterns.psi[PSI1][(psiMoves + startPositions.psi[PSI1]) % PSI1_PERIOD];
            symbol += (psiBit1 != chiBit1) ? BIT1 : 0x00;

            boolean chiBit2 = patterns.chi[CHI2][(p + startPositions.chi[CHI2]) % CHI2_PERIOD];
            boolean psiBit2 = patterns.psi[PSI2][(psiMoves + startPositions.psi[PSI2]) % PSI2_PERIOD];
            symbol += (psiBit2 != chiBit2) ? BIT2 : 0x00;

            boolean chiBit3 = patterns.chi[CHI3][(p + startPositions.chi[CHI3]) % CHI3_PERIOD];
            boolean psiBit3 = patterns.psi[PSI3][(psiMoves + startPositions.psi[PSI3]) % PSI3_PERIOD];
            symbol += (psiBit3 != chiBit3) ? BIT3 : 0x00;

            boolean chiBit4 = patterns.chi[CHI4][(p + startPositions.chi[CHI4]) % CHI4_PERIOD];
            boolean psiBit4 = patterns.psi[PSI4][(psiMoves + startPositions.psi[PSI4]) % PSI4_PERIOD];
            symbol += (psiBit4 != chiBit4) ? BIT4 : 0x00;

            boolean chiBit5 = patterns.chi[CHI5][(p + startPositions.chi[CHI5]) % CHI5_PERIOD];
            boolean psiBit5 = patterns.psi[PSI5][(psiMoves + startPositions.psi[PSI5]) % PSI5_PERIOD];
            symbol += (psiBit5 != chiBit5) ? BIT5 : 0x00;

            outputStream[p] = (byte) (symbol ^ inputStream[p]);

            boolean stateMu37 = patterns.mu[MU2][(mu37Moves + startPositions.mu[MU2]) % MU2_PERIOD];

            boolean shouldMovePsi = stateMu37;
            switch (limitation) {
                case CHI2_1BACK:
                    if (!chi2OneBack) {
                        shouldMovePsi = true;
                    }
                    break;
                case PSI1_1BACK:
                    if (!(chi2OneBack ^ psi1OneBack)) {
                        shouldMovePsi = true;
                    }
                    break;
                case P5_2BACK:
                    if (!(chi2OneBack ^ p5TwoBack)) {
                        shouldMovePsi = true;
                    }
                    break;
                case PSI1_1BACK_P5_2BACK:
                    if (!(chi2OneBack ^ psi1OneBack ^ p5TwoBack)) {
                        shouldMovePsi = true;
                    }
                    break;
                case NO_LIMITATION:
                default:
                    break;
            }

            if (shouldMovePsi) {
                psiMoves++;
            }
            boolean stateMu61 = patterns.mu[MU1][(p + startPositions.mu[MU1]) % MU1_PERIOD];
            if (stateMu61) {
                mu37Moves++;
            }
            chi2OneBack = chiBit2;
            psi1OneBack = psiBit1;
            p5TwoBack = p5OneBack;
            byte plainSymbol = encipher ? inputStream[p] : outputStream[p];
            p5OneBack = (plainSymbol & BIT5) != 0x0;

        }
    }

    void dechi(byte[] outputStream, int len, byte[] inputStream) {

        for (int p = 0; p < len; p++) {
            byte symbol = 0;

            boolean chiBit1 = patterns.chi[CHI1][(p + startPositions.chi[CHI1]) % CHI1_PERIOD];
            symbol += (chiBit1) ? BIT1 : 0x00;

            boolean chiBit2 = patterns.chi[CHI2][(p + startPositions.chi[CHI2]) % CHI2_PERIOD];
            symbol += (chiBit2) ? BIT2 : 0x00;

            boolean chiBit3 = patterns.chi[CHI3][(p + startPositions.chi[CHI3]) % CHI3_PERIOD];
            symbol += (chiBit3) ? BIT3 : 0x00;

            boolean chiBit4 = patterns.chi[CHI4][(p + startPositions.chi[CHI4]) % CHI4_PERIOD];
            symbol += (chiBit4) ? BIT4 : 0x00;

            boolean chiBit5 = patterns.chi[CHI5][(p + startPositions.chi[CHI5]) % CHI5_PERIOD];
            symbol += (chiBit5) ? BIT5 : 0x00;

            outputStream[p] = (byte) (symbol ^ inputStream[p]);
        }
    }

    @Override
    public String toString(){
        return patterns.toString() + startPositions.toString() + "\n" + limitation;
    }

    void parse(String s) {
        String[] lines = s.split("[\r\n]+");
        if (lines.length != 14) {
            throw new RuntimeException("Invalid key - expecting 14 lines");
        }
        patterns.parse(s);
        startPositions.parse(lines[12]);
        limitation = Limitation.valueOf(lines[13]);
    }
    void print(boolean details) {
        patterns.print(details);
        System.out.println(startPositions.toString());
        System.out.println(limitation);
    }
}
