import java.io.*;
import java.util.*;

public class DiamondsTestGen {

	final static Random rng = new Random("diamonds".hashCode());
	final int MAX_ALPH = 21;
	final String consonants = "bcdfghjklmnpqrstvwxyz";
	final int MAX_N = 5000;
	final int MAX_Q = 100_000;

	public static void main(String[] args) throws Exception {
		new DiamondsTestGen().generate();
	}

	PrintWriter out;
	int countTests = 0;

	void generate() {
		generateSamples();

		generateSmall();
		generateLarge();
	}

	private void generateSmall() {
		int alph = 3;
		for (int len = 1; len <= 10; len++) {
			TestCase tc = new TestCase(randomString(len, alph), allMasks(alph));
			printTest(tc);
		}

		printTest(new TestCase(repeat("a", 10), allMasks(1)));
		printTest(new TestCase(repeat("ab", 5), allMasks(2)));
		printTest(new TestCase(repeat("a", 5) + repeat("b", 5), allMasks(3)));
		printTest(new TestCase(repeat("abc", 4), allMasks(3)));
	}

	private void generateLarge() {
		int alph = MAX_ALPH;

		for (int len = 1000; len < MAX_N; len += 1000) {
			printTest(new TestCase(randomString(len, 2), allMasks(2)));
			printTest(new TestCase(randomString(len, 8), allMasks(8)));
			printTest(new TestCase(randomString(len),
					randomQueries(MAX_Q, alph)));
		}

		int len = MAX_N;
		printTest(new TestCase(repeat("a", len), allMasks(10)));
		printTest(new TestCase(repeat("ab", len / 2), allMasks(10)));
		printTest(new TestCase(repeat("a", len / 2) + repeat("b", len / 2),
				allMasks(10)));

		String str = alphabet(alph) + reverse(alphabet(alph));
		printDifferentPerversions(str);

		str = "";
		for (int i = 0; i < alph; i++) {
			str += repeat("" + (char) ('a' + i), len / alph);
		}
		printDifferentPerversions(str);

		str = thueMorseString(MAX_N);
		printDifferentPerversions(str);

		str = fibonacciString(MAX_N);
		printDifferentPerversions(str);

		str = abacabaString(MAX_N);
		printDifferentPerversions(str);
		
		str = repeat(alphabet(alph), MAX_N / alph);
		List<String> qs = new ArrayList<>();
		for (int i = 0; i < MAX_Q; i++) {
			qs.add(alphabet(alph));
		}
		printTest(new TestCase(str, qs));
	}

	private void printDifferentPerversions(String str) {
		int len = MAX_N;
		int alph = MAX_ALPH;
		str = repeat(str, len / str.length());
		printTest(new TestCase(str, allMasks(18).subList(0, MAX_Q)));
		printTest(new TestCase(str, allMasks(MAX_ALPH).subList(
				(1 << MAX_ALPH) - MAX_Q - 1, (1 << MAX_ALPH) - 1)));
		printTest(new TestCase(str, randomQueries(MAX_Q, alph)));

		String str2 = randomReplaces(str, 10, alph);
		printTest(new TestCase(str2, randomQueries(MAX_Q, alph)));
		str2 = randomReplaces(str, 100, alph);
		printTest(new TestCase(str2, randomQueries(MAX_Q, alph)));
		str2 = randomReplaces(str, 1000, alph);
		printTest(new TestCase(str2, randomQueries(MAX_Q, alph)));

	}

	private String randomReplaces(String s, int replaces, int alph) {
		char[] c = s.toCharArray();
		for (int j = 0; j < replaces; j++) {
			int pos = rng.nextInt(s.length());
			c[pos] = (char) ('a' + rng.nextInt(alph));
		}
		return new String(c);
	}

	private String thueMorseString(int length) {
		char[] c = new char[length];
		for (int i = 0; i < length; i++) {
			c[i] = (char) ('a' + Integer.bitCount(i));
		}
		return new String(c);
	}

	private String fibonacciString(int length) {
		String cur = "a";
		while (cur.length() < length) {
			StringBuilder sb = new StringBuilder();
			for (char c : cur.toCharArray()) {
				if (c == 'a') {
					sb.append('b');
				} else {
					sb.append("ba");
				}
			}
			cur = sb.toString();
		}

		return cur.substring(0, length);
	}

	private String abacabaString(int length) {
		char[] c = new char[length];
		for (int i = 0; i < length; i++) {
			c[i] = (char) ('a' + Integer.numberOfTrailingZeros(i + 1));
		}
		return new String(c);
	}

	String reverse(String s) {
		return new StringBuilder(s).reverse().toString();
	}

	String alphabet(int alph) {
		String res = "";
		for (int i = 0; i < alph; i++) {
			res += (char) (i + 'a');
		}
		return res;
	}

	String repeat(String what, int times) {
		StringBuilder sb = new StringBuilder(what);
		for (int i = 1; i < times; i++) {
			sb.append(what);
		}
		return sb.toString();
	}

	List<String> allMasks(int alph) {
		List<String> result = new ArrayList<>();
		for (int i = 1; i < (1 << alph); i++) {
			result.add(shuffleString(byMask(i)));
		}
		Collections.shuffle(result, rng);
		return result;
	}

	List<String> randomQueries(int count, int alph) {
		List<String> result = new ArrayList<>();
		for (int i = 0; i < count; i++) {
			int mask = rng.nextInt((1 << alph) - 1) + 1;
			result.add(shuffleString(byMask(mask)));
		}
		Collections.shuffle(result, rng);
		return result;
	}

	public String randomString(int len, int alph) {
		char[] result = new char[len];
		for (int i = 0; i < len; i++) {
			result[i] = (char) ('a' + rng.nextInt(alph));
		}
		return new String(result);
	}

	public String randomString(int len) {
		return randomString(len, MAX_ALPH);
	}

	public String byMask(int mask) {
		String res = "";
		for (int i = 0; i < MAX_ALPH; i++) {
			if ((mask & (1 << i)) != 0) {
				res += (char) ('a' + i);
			}
		}
		return res;
	}

	public String shuffleString(String s) {
		char[] c = s.toCharArray();
		for (int i = 1; i < c.length; i++) {
			int pos = rng.nextInt(i + 1);
			char tmp = c[i];
			c[i] = c[pos];
			c[pos] = tmp;
		}
		return new String(c);
	}

	private void generateSamples() {
		TestCase sample = new TestCase("aabcca", Arrays.asList("ab", "abcd",
				"abc"));
		printTest(sample);
	}

	class TestCase {
		private String s;
		private List<String> queries;

		public TestCase(String s, List<String> queries) {
			this.s = s;
			this.queries = queries;
		}

		public TestCase(String s) {
			this.s = s;
			this.queries = new ArrayList<>();
		}

		public void addQuery(String q) {
			this.queries.add(q);
		}
	}

	public String fixString(String s) {
		char[] c = s.toCharArray();
		for (int i = 0; i < c.length; i++) {
			c[i] = consonants.charAt(c[i] - 'a');
		}
		return new String(c);
	}

	void printTest(TestCase tc) {
		try {
			countTests++;
			String fileName = "../tests/";
			fileName += String.format("%02d", countTests);
			out = new PrintWriter(fileName);

			out.println(tc.s.length());
			out.println(fixString(tc.s));
			out.println(tc.queries.size());
			for (String s : tc.queries) {
				out.println(s.length() + " " + fixString(s));
			}

			System.out.println("Done printing test #" + countTests);
			out.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
}
