import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Random;

public class TestsGen {

    static Random rnd = new Random(3);

    static class TestWriter {
        int tests;
        String pathPrefix;

        TestWriter(String pathPrefix) {
            this.pathPrefix = pathPrefix;
            File f = new File(pathPrefix);
            if (!f.exists()) {
                System.err.println("Creating directory \"" + pathPrefix + "\"");
                f.mkdir();
            }
            tests = 0;
        }

        void out(Test t) throws FileNotFoundException {
            tests++;
            String fname = pathPrefix + "/" + String.format("%02d", tests);
            System.err.println(fname);
            PrintWriter out = new PrintWriter(fname);
            out.println(t);
            out.close();
        }
    }

    static interface Test {
    }

    static class BasicTest implements Test {
        String s;

        BasicTest(String s) {
            this.s = s;
        }

        @Override
        public String toString() {
            return s;
        }
    }

    static class Abacaba implements Test {
        int n;

        Abacaba(int n) {
            this.n = n;
        }

        @Override
        public String toString() {
            String res = "";
            char ch = 'a';
            while (res.length() < n) {
                res = res + ch + res;
            }
            return res.substring(0, n);
        }
    }

    static class Repeat implements Test {
        int n;
        String ch;

        Repeat(int n, String ch) {
            this.n = n;
            this.ch = ch;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i++) {
                sb.append(ch);
            }
            return sb.toString();
        }
    }

    static class TM implements Test {
        int n;

        TM(int n) {
            this.n = n;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i++) {
                sb.append((char)('a' + Integer.bitCount(i) % 2));
            }
            return sb.toString();
        }
    }

    static class Concat implements Test {
        Test[] tests;

        Concat(Test... tests) {
            this.tests = tests;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (Test t : tests) {
                sb.append(t);
            }
            return sb.toString();
        }
    }

    static class RandomTest implements Test {
        int n;
        int letters;

        RandomTest(int n, int letters) {
            this.n = n;
            this.letters = letters;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i++) {
                sb.append((char)('a' + rnd.nextInt(letters)));
            }
            return sb.toString();
        }
    }

    static class Fibonacci implements Test {
        int n;

        Fibonacci(int n) {
            this.n = n;
        }

        @Override
        public String toString() {
            String a = "a";
            String b = "b";
            while (b.length() < n) {
                String c = a + b;
                a = b;
                b = c;
            }
            return b.substring(0, n);
        }
    }

    static class Noise implements Test {
        double p;
        Test t;

        Noise(double p, Test t) {
            this.p = p;
            this.t = t;
        }

        @Override
        public String toString() {
            char[] s = t.toString().toCharArray();
            for (int i = 0; i < s.length; i++) {
                if (rnd.nextDouble() < p) {
                    s[i] = (char) ('a' + rnd.nextInt(26));
                }
            }
            return new String(s);
        }
    }

    static Test prelimTest1() {
        return new BasicTest("aabaa");
    }

    static Test[] fromList(ArrayList<Test> tests) {
        Test[] res = new Test[tests.size()];
        for (int i = 0; i < res.length; i++) {
            res[i] = tests.get(i);
        }
        return res;
    }

    static Test[] smallTests() {
        ArrayList<Test> tests = new ArrayList<>();
        tests.add(new Repeat(1, "a"));
        tests.add(new Repeat(50, "ab"));
        tests.add(new Abacaba(100));
        tests.add(new TM(100));
        tests.add(new RandomTest(100, 26));
        tests.add(new RandomTest(100, 2));
        return fromList(tests);
    }

    static Test[] mediumTests() {
        int N = 4000;
        ArrayList<Test> tests = new ArrayList<>();
        tests.add(new Repeat(N, "a"));
        tests.add(new Abacaba(N));
        tests.add(new TM(N));
        tests.add(new RandomTest(N, 2));
        tests.add(new Fibonacci(N));
        tests.add(new RandomTest(N, 26));
        return fromList(tests);
    }

    static Test[] largeTests() {
        final int N = 100000;
        ArrayList<Test> tests = new ArrayList<>();
        tests.add(new TM(N));
        tests.add(new Repeat(N, "a"));
        tests.add(new Concat(new Repeat(N / 2, "z"), new TM(N / 2)));
        tests.add(new Concat(new Repeat(N / 2, "ab")));
        tests.add(new Concat(new Fibonacci(N / 3), new Repeat(N / 3, "z"), new Abacaba(N / 3)));
        tests.add(new Concat(new TM(N / 3), new Fibonacci(N / 3), new Repeat(N / 6, "ab")));
        tests.add(new RandomTest(N, 2));
        tests.add(new Noise(0.0003, new Concat(new TM(N / 3), new Fibonacci(N / 3), new Repeat(N / 6, "ab"))));
        return fromList(tests);
    }

    public static void main(String[] args) throws IOException {
        TestWriter prelim = new TestWriter("../preliminary");
        prelim.out(prelimTest1());
        TestWriter tests = new TestWriter("../tests");
        System.err.println("Generating small tests");
        for (Test t : smallTests()) {
            tests.out(t);
        }
        System.err.println("Generating medium tests");
        for (Test t : mediumTests()) {
            tests.out(t);
        }
        System.err.println("Generating large tests");
        for (Test t : largeTests()) {
            tests.out(t);
        }
    }

}
