// Author: Ivan Kazmenko (gassa@mail.ru)
// Generator for problem "hex-operations"
module gen;
import std.algorithm;
import std.conv;
import std.exception;
import std.format;
import std.math;
import std.random;
import std.range;
import std.stdio;
import std.traits;

immutable string testFormat = "%03d";
enum generateAnswers = false;

immutable int minN =       2;
immutable int maxN =     500;
immutable int minV =       1;
immutable int maxV =      99;
immutable int minL =       1;
immutable int maxL = 250_000;

struct Test
{
	int [] [] a;
	string cmd;

	string comment;
	int shift = 2;

	@property auto n () const
	{
		return a.front.length.to !(int);
	}

	@property auto k () const
	{
		return cmd.length.to !(int);
	}

	void validate () const
	{
		enforce (minN <= n && n <= maxN);
		enforce (a.length == 2 * n - 1);
		foreach (row; -n + 1..n)
		{
			auto line = a[row + n - 1];
			enforce (line.length == n * 2 - 1 - abs (row));
			enforce (line.all !(v => minV <= v && v <= maxV));
		}
		enforce (minL <= k && k <= maxL);
		enforce (cmd.all !(c => "LRT".canFind (c)));
	}

	void print (File f) const
	{
		f.writeln (n);
		foreach (row; -n + 1..n)
		{
			auto line = a[row + n - 1];
			f.writef !("%*s") (abs (row) * shift, "");
			foreach (col, ref elem; line)
			{
				if (shift)
				{
					f.writef !("%*s")
					    (!!col * shift * 2, elem);
				}
				else
				{
					f.writef !("%*s%s")
					    (!!col + 0, "", elem);
				}
			}
			f.writefln !("%*s") (abs (row) * shift, "");
		}
		f.writeln (cmd);
	}

	string log () const
	{
		return format !("%s (n = %s, %s commands)") (comment, n, k);
	}
}

Test [] tests;

void printAllTests ()
{
	foreach (testNumber, test; tests)
	{
		test.validate ();
		auto testString = format (testFormat, testNumber + 1);
		auto f = File (testString, "wt");
		test.print (f);
		static if (generateAnswers)
		{
			auto g = File (testString ~ ".a", "wt");
			test.printAnswer (g);
		}
		writeln (testString, ": ", test.log ());
	}
}

Mt19937 rndLocal;
Unqual !(T) rndValue (T) () {return uniform !(T) (rndLocal);}
Unqual !(T) rndValue (T) (T lim)
    {return uniform (cast (T) (0), lim, rndLocal);}
Unqual !(T) rndValue (string boundaries = "[)", T) (T lo, T hi)
    {return uniform !(boundaries) (lo, hi, rndLocal);}

auto rndChoice (R) (R r) {return r[rndValue (cast (int) (r.length))];}

void rndShuffle (R) (R r)
{
	auto len = r.length.to !(int);
	foreach (i; 0..len)
	{
		int k = rndValue (i, len);
		r.swapAt (i, k);
	}
}

int [] [] genRandom (int n, int lo = minV, int hi = maxV)
{
	return iota (n * 2 - 1).map !(row =>
	    iota (2 * n - 1 - abs (row - n + 1)).map !(__ =>
	    rndValue !("[]") (lo, hi)).array).array;
}

string genRandomCmd (int k, int l = 1, int r = 1, int t = 1)
{
	auto s = new char [k];
	foreach (ref c; s)
	{
		auto v = rndValue (l + r + t);
		c = (v < l) ? 'L' : (v < l + r) ? 'R' : 'T';
	}
	return s.idup;
}

void main ()
{
	rndLocal.seed (123_123_812);
	tests ~= Test ([[4, 1, 8], [3, 5, 1, 7], [2, 1, 6, 1, 8],
	    [1, 7, 1, 9], [8, 9, 9]], "LTR", "example test 1", 1);

	tests ~= Test ([[99, 19], [23, 45, 6], [8, 7]],
	    "L", "minimal test 1", 0);
	tests ~= Test ([[1, 1], [1, 1, 1], [1, 1]],
	    "T", "minimal test 2", 1);
	tests ~= Test ([[91, 92], [96, 97, 93], [95, 94]],
	    "R", "minimal test 3", 2);

	tests ~= Test (genRandom (6),
	    genRandomCmd (3),
	    "medium test 1");

	tests ~= Test (genRandom (89),
	    genRandomCmd (maxL / 100 - 2),
	    "medium test 2");

	tests ~= Test (genRandom (126),
	    genRandomCmd (6798),
	    "medium test 3");

	tests ~= Test (genRandom (419),
	    genRandomCmd (maxL - 5),
	    "medium test 4");

	tests ~= Test (genRandom (2, 1, 9),
	    genRandomCmd (maxL),
	    "min-max test 1", 1);

	tests ~= Test (genRandom (maxN, 90, 99),
	    "RTL",
	    "min-max test 2");

	foreach (nt, pattern; ["R", "L", "TR", "TL", "TRTL", "TLTR"])
	{
		tests ~= Test ([[1, 2], [3, 4, 5], [6, 7]],
		    pattern.cycle.take (maxL).text,
		    format !("small pattern test %s") (nt + 1));
	}

	tests ~= Test (genRandom (maxN, maxV - 1, maxV),
	    "R" ~ "TRTL".repeat (maxL / 4 - 1).join ~ "R",
	    "large test 1");

	tests ~= Test (genRandom (maxN - 1, 1, 10),
	    genRandomCmd (maxL - 5, 1, 2, 3),
	    "large test 2", 0);

	tests ~= Test (genRandom (maxN, 93, 93),
	    genRandomCmd (maxL, 3, 2, 1),
	    "large test 3");

	tests ~= Test (genRandom (maxN - 4, maxV / 2, maxV - 1),
	    genRandomCmd (maxL - 1, 20_000, 1, 1),
	    "large test 4");

	tests ~= Test (genRandom (maxN - 5, 1, maxV / 3),
	    genRandomCmd (maxL - 2, 1, 20_000, 1),
	    "large test 5");

	tests ~= Test (genRandom (maxN - 6, minV, maxV),
	    genRandomCmd (maxL - 3, 1, 1, 20_000),
	    "large test 6");

	foreach (nt, pattern; ["R", "L", "TR", "TL", "TRTL", "TLTR"])
	{
		tests ~= Test (genRandom (maxN), "T" ~
		    pattern.repeat ((maxL / 3 * 3 - 1) / pattern.length).join,
		    format !("large pattern test %s") (nt + 1));
	}

	foreach (nt; 0..4)
	{
		tests ~= Test (genRandom (maxN - (nt & 1)),
		    genRandomCmd (maxL - ((nt / 2) & 1)),
		    format !("random test %s") (nt + 1));
	}

	printAllTests ();
}
