package simulator;

class Patterns {
    public boolean[][] chi = new boolean[Tunny.CHI_NUM][];
    public boolean[][] mu = new boolean[Tunny.MU_NUM][];
    public boolean[][] psi = new boolean[Tunny.PSI_NUM][];

    Patterns() {
        for (int w = 0; w < Tunny.CHI_NUM; w++) {
            this.chi[w] = new boolean[Tunny.CHI_PERIOD[w]];
        }
        for (int w = 0; w < Tunny.PSI_NUM; w++) {
            this.psi[w] = new boolean[Tunny.PSI_PERIOD[w]];
        }
        for (int w = 0; w < 2; w++) {
            this.mu[w] = new boolean[Tunny.MU_PERIOD[w]];
        }
    }
    private Patterns(boolean[][] chi, boolean[][] psi, boolean[][] mu) {
        this();
        if (chi != null) {

            for (int w = 0; w < Tunny.CHI_NUM; w++) {
                if (chi[w].length != Tunny.CHI_PERIOD[w]) {
                    throw new RuntimeException("Invalid Chi patterns");
                }
                System.arraycopy(chi[w], 0, this.chi[w], 0, Tunny.CHI_PERIOD[w]);
            }
        }
        if (psi != null) {
            for (int w = 0; w < Tunny.PSI_NUM; w++) {
                if (psi[w].length != Tunny.PSI_PERIOD[w]) {
                    throw new RuntimeException("Invalid Psi patterns");
                }
                System.arraycopy(psi[w], 0, this.psi[w], 0, Tunny.PSI_PERIOD[w]);
            }
        }
        if (mu != null) {
            for (int w = 0; w < Tunny.MU_NUM; w++) {
                if (mu[w].length != Tunny.MU_PERIOD[w]) {
                    throw new RuntimeException("Invalid Mu patterns");
                }
                System.arraycopy(mu[w], 0, this.mu[w], 0, Tunny.MU_PERIOD[w]);
            }
        }
    }
    Patterns(Patterns patterns) {
        this(patterns.chi, patterns.psi, patterns.mu);
    }

    public void randomize() {
        randomizeMu37();
        randomizeMu61();
        randomizePsi();
        randomizeChi();
    }
    public void randomize(int d) {
        randomizeMu37(d);
        randomizeMu61();
        randomizePsi();
        randomizeChi();
    }
    public void parse(String s) {
        String[] lines = s.split("[\r\n]+");
        if (lines.length <= 12) {
            throw new RuntimeException("Not enough patterns " + lines.length + " (expecting 12)");
        }
        for (int w = Tunny.CHI1; w <= Tunny.CHI5; w++) {
            Patterns.parse(lines[w], chi[w]);
        }
        for (int w = Tunny.PSI1; w <= Tunny.PSI5; w++) {
            Patterns.parse(lines[5 + w], psi[w]);
        }
        for (int w = Tunny.MU1; w <= Tunny.MU2; w++) {
            Patterns.parse(lines[10 + w], mu[w]);
        }
    }
    @Override
    public String toString(){
        StringBuilder s = new StringBuilder();
        for (int w = Tunny.CHI1 ; w <= Tunny.CHI5; w++) {
            s.append(Patterns.toString(chi[w]));
            s.append("\n");
        }
        for (int w = Tunny.PSI1 ; w <= Tunny.PSI5; w++) {
            s.append(Patterns.toString(psi[w]));
            s.append("\n");
        }
        for (int w = Tunny.MU1 ; w <= Tunny.MU2; w++) {
            s.append(Patterns.toString(mu[w]));
            s.append("\n");
        }
        return s.toString();
    }
    public void print(boolean details){

        if (details) {
            for (boolean[][] patternGroup : new boolean[][][]{chi, mu, psi}) {
                for (boolean[] pattern : patternGroup) {
                    System.out.printf("%-80s, L=%2d, .=%2d x=%2d, d.=%2d, dx=%2d, max.=%2d, maxd=%2d\n",
                            toString(pattern), pattern.length, countDots(pattern), countCrosses(pattern), countDeltaDots(pattern), countDeltaCrosses(pattern), maxConsecutiveDots(pattern), maxConsecutiveCrosses(pattern));
                }
            }
            System.out.printf("d = %d, k = %d\n", countDots(mu[Tunny.MU2]), countCrosses(mu[Tunny.MU1]));
        } else {
            System.out.print(toString());
        }

    }
    public static String toString(boolean[] pattern) {
        StringBuilder s = new StringBuilder();
        for (boolean b : pattern) {
            s.append(b ? "x" : ".");
        }
        return s.toString();
    }

    // Private

    private static void parse(String s, boolean[] pattern) {
        if (s.length() != pattern.length) {
            throw new RuntimeException("Invalid pattern length - expecting " + pattern.length + " elements, actual " + s.length());
        }
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == 'X' || c == 'x' || c == '1') {
                pattern[i] = true;
            } else if (c == '.' || c == '0') {
                pattern[i] = false;
            } else {
                throw new RuntimeException("Invalid pattern symbol (expecting [Xx.10]): " + s);
            }
        }
    }

    private void randomizePsi() {
        for (int w = Tunny.PSI1 ; w <= Tunny.PSI5; w++) {
            int d = countDots(mu[Tunny.MU2]);
            int crosses = PsiRule.crosses(w);
            int deltaCrosses = PsiRule.randomDeltaCrosses(w, d);
            randomize(psi[w], crosses, deltaCrosses, 1000, 1000);
        }
    }
    private void randomizeMu61() {
        boolean[] pattern = mu[Tunny.MU1];
        do {
            randomizeCrosses(pattern, MuRule.mu1RandomCrosses());
        } while (maxConsecutiveCrosses(pattern) > MuRule.MU1_MAX_CONSECUTIVE_CROSSES || maxConsecutiveDots(pattern) > MuRule.MU1_MAX_CONSECUTIVE_DOTS);
    }
    private void randomizeMu37(int dots){
        boolean[] pattern = mu[Tunny.MU2];
        do {
            Patterns.randomizeDots(pattern, dots);
        } while (maxConsecutiveCrosses(pattern) > MuRule.MU2_MAX_CONSECUTIVE_CROSSES || maxConsecutiveDots(pattern) > MuRule.MU2_MAX_CONSECUTIVE_DOTS);
    }
    private void randomizeMu37(){
        randomizeMu37(MuRule.mu2RandomDots());
    }
    private void randomizeChi() {
        for (int w = Tunny.CHI1; w <= Tunny.CHI5; w++) {
            randomize(chi[w], ChiRule.randomCrosses(w), ChiRule.randomDeltaCrosses(w), ChiRule.MAX_CONSECUTIVE_CROSSES, ChiRule.MAX_CONSECUTIVE_DOTS);
        }
    }

    private static void randomize(boolean[] pattern, int crosses, int deltaCrosses, int maxConsecutiveCrosses, int maxConsecutiveDots) {
        int len = pattern.length;
        main:
        while (true) {
            do {
                randomizeCrosses(pattern, crosses);
            } while (maxConsecutiveCrosses(pattern) > maxConsecutiveCrosses || maxConsecutiveDots(pattern) > maxConsecutiveDots);

            int currentDeltaCrosses = countDeltaCrosses(pattern);
            while (currentDeltaCrosses < deltaCrosses) {
                int shift = Utils.random.nextInt(len);
                boolean improved = false;
                for (int i = 0; i < len && !improved; i++) {
                    int si = (i + shift) % len;
                    boolean bi = pattern[si];
                    for (int j = i + 1; j < len && !improved; j++) {
                        int sj = (j + shift) % len;
                        boolean bj = pattern[sj];
                        if (bi == bj) {
                            continue;
                        }
                        pattern[si] ^= true;
                        pattern[sj] ^= true;
                        int newDeltaCrosses = countDeltaCrosses(pattern);
                        if (newDeltaCrosses > currentDeltaCrosses
                                && newDeltaCrosses <= deltaCrosses
                                && maxConsecutiveCrosses(pattern) <= maxConsecutiveCrosses
                                && maxConsecutiveDots(pattern) <= maxConsecutiveDots) {
                            currentDeltaCrosses = newDeltaCrosses;
                            improved = true;
                        } else {
                            pattern[si] ^= true;
                            pattern[sj] ^= true;
                        }
                    }
                }
                if (!improved) {
                    continue main;
                }
            }

            while (currentDeltaCrosses > deltaCrosses) {
                int shift = Utils.random.nextInt(len);
                boolean improved = false;
                for (int i = 0; i < len && !improved; i++) {
                    int si = (i + shift) % len;
                    boolean bi = pattern[si];
                    for (int j = i + 1; j < len && !improved; j++) {
                        int sj = (j + shift) % len;
                        boolean bj = pattern[sj];
                        if (bi == bj) {
                            continue;
                        }
                        pattern[si] ^= true;
                        pattern[sj] ^= true;
                        int newDeltaCrosses = countDeltaCrosses(pattern);
                        if (newDeltaCrosses < currentDeltaCrosses
                                && newDeltaCrosses >= deltaCrosses
                                && maxConsecutiveCrosses(pattern) <= maxConsecutiveCrosses
                                && maxConsecutiveDots(pattern) <= maxConsecutiveDots) {
                            currentDeltaCrosses = newDeltaCrosses;
                            improved = true;
                        } else {
                            pattern[si] ^= true;
                            pattern[sj] ^= true;
                        }
                    }
                }
                if (!improved) {
                    continue main;
                }
            }
            return;
        }
    }
    private static void randomizeCrosses(boolean[] pattern, int crosses){
        long bitmap = Utils.random.nextLong();
        for (int i = 0; i < pattern.length; i++) {
            pattern[i] = (bitmap & 0x1) == 0x1;
            bitmap >>= 1;
        }
        while (countCrosses(pattern) < crosses) {
            int p;
            do {
                p = Utils.random.nextInt(pattern.length);
            } while (pattern[p]);
            pattern[p] = true;
        }
        while (countCrosses(pattern) > crosses) {
            int p;
            do {
                p = Utils.random.nextInt(pattern.length);
            } while (!pattern[p]);
            pattern[p] = false;
        }
    }
    private static void randomizeDots(boolean[] pattern, int dots){
        long bitmap = Utils.random.nextLong();
        for (int i = 0; i < pattern.length; i++) {
            pattern[i] = (bitmap & 0x1) == 0x1;
            bitmap >>= 1;
        }
        while (countDots(pattern) > dots) {
            int p;
            do {
                p = Utils.random.nextInt(pattern.length);
            } while (pattern[p]);
            pattern[p] = true;
        }
        while (countDots(pattern) < dots) {
            int p;
            do {
                p = Utils.random.nextInt(pattern.length);
            } while (!pattern[p]);
            pattern[p] = false;
        }
    }

    private static int count(boolean[] pattern, boolean value) {
        int count = 0;
        for (boolean p : pattern) {
            count += (p == value) ? 1 : 0;
        }
        return count;
    }
    private static int countDots(boolean[] pattern) {
        return count(pattern, false);
    }
    private static int countCrosses(boolean[] pattern) {
        return count(pattern, true);
    }

    private static int countDelta(boolean[] pattern, boolean value) {
        int count = 0;
        for (int i = 0; i < pattern.length; i++) {
            boolean p1 = pattern[i];
            boolean p2 = pattern[(i + 1) % pattern.length];
            count += ((p1 ^ p2) == value) ? 1 : 0;
        }
        return count;
    }
    private static int countDeltaDots(boolean[] pattern) {
        return countDelta(pattern, false);
    }
    private static int countDeltaCrosses(boolean[] pattern) {
        return countDelta(pattern, true);
    }

    private static int maxConsecutive(boolean[] pattern, boolean value) {
        int maxConsecutiveCrosses = 0;
        int consecutiveCrosses = 0;
        for (int i = 0; i < 2; i++) {
            for (boolean b : pattern) {
                if (b == value) {
                    consecutiveCrosses++;
                    if (consecutiveCrosses > maxConsecutiveCrosses) {
                        maxConsecutiveCrosses = consecutiveCrosses;
                    }
                } else {
                    consecutiveCrosses = 0;
                }
            }
        }
        return maxConsecutiveCrosses;
    }
    private static int maxConsecutiveDots(boolean[] pattern) {
        return maxConsecutive(pattern, false);
    }
    private static int maxConsecutiveCrosses(boolean[] pattern) {
        return maxConsecutive(pattern, true);
    }

}
