(
var backtrackMacroTile, generateMacroTiles, generateMetaTiling, metaTiling;

//create the macroTiles from the borders by backtracking
//possible TODO: the macroTiles could be hardcoded,
//but this is fast and with some tweaks could be used to see if there are more than one solutions for the macrotiles
backtrackMacroTile = {arg topWord, rightWord, bottomWord, leftWord;
	var i = 0, x = topWord.size, y = rightWord.size, tiles, macroTile, candidates;

	tiles = [
		[0, 0, 0, 1], [0, 1, 3, 2], [1, 2, 0, 2], [1, 2, 1, 0],
		[1, 3, 3, 2], [3, 1, 0, 1], [3, 1, 1, 1], [3, 2, 1, 2],
		[3, 1, 3, 3], [2, 0, 2, 1], [2, 2, 2, 0]
	];

	macroTile = Array.fill2D(y, x, {tiles});
	while({i < (x * y)}, {
		var leftColor, topColor, rightColor, bottomColor;

		leftColor = if((i % x) == 0, {leftWord[(i / x).trunc]}, {macroTile[(i / x).trunc][(i % x) - 1].first[2]});
		topColor = if(i < x, {topWord[i]}, {macroTile[(i / x).trunc - 1][i % x].first[3]});
		rightColor = if((i % x) == (x - 1),  {rightWord[(i / x).trunc]}, {nil});
		bottomColor = if(i > ((y - 1) * x), {bottomWord[i % x]}, {nil});

		candidates = macroTile[(i / x).trunc][i % x].select({arg tile;
			((leftColor == nil) || (tile[0] == leftColor)) &&
			((topColor == nil) || (tile[1] == topColor)) &&
			((rightColor == nil) || (tile[2] == rightColor)) &&
			((bottomColor == nil) || (tile[3] == bottomColor))});
		if(candidates.size > 0, {
			macroTile[(i / x).trunc][(i % x)] = candidates;
			i = i + 1;
		},{
			macroTile[(i / x).trunc][(i % x)] = tiles;
			i = i - 1;
			macroTile[(i / x).trunc][(i % x)].removeAt(0);
			macroTile[(i / x).trunc][(i % x)];
		});
	});
	macroTile.collect({arg r; r.collect({arg c; c.first})});
};

//create the macroTiles from the borders by backtracking
//this encapsulates the above function
generateMacroTiles = {var macroTileBoundaries;

	macroTileBoundaries = [
		[[1, 0, 0, 1, 1], [3, 3, 0, 2], [0, 0, 0, 0, 0], [3, 3, 0, 2]],
		[[1, 1, 1, 0, 0, 0, 1, 1], [3, 3, 0, 2], [0, 0, 0, 0, 0, 0, 0, 0], [3, 3, 0, 2]],
		[[1, 1, 1, 1, 1, 1, 1, 1], [3, 3, 0, 2], [0, 0, 1, 1, 1, 0, 0, 0], [3, 3, 0, 2]],
		[[1, 1, 1, 1, 1], [3, 3, 0, 2], [0, 0, 1, 1, 0], [3, 3, 0, 2]],

		[[0, 0], [0, 0 /*nil*/, 3, 1, 2, 3, 1, 1, 1, 2], [1, 1], [0, 0, 1, 3, 2, 0, 3, 1, 1, 2]],
		[[1, 1, 0, 0, 0, 0], [0, 0, 1, 3, 2, 0, 3, 1, 1, 2], [1, 1, 1, 1, 1, 1], [0, 1, 3, 1, 2, 3, 1, 1, 1, 2]],
		[[0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0], [0, 0, 1, 3, 2, 0, 3, 1, 1, 2], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 3, 1, 2, 3, 1, 1, 1, 2]],
		[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 3, 2, 0, 3, 1, 1, 2], [1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1], [0, 0, 3, 1, 2, 3, 1, 1, 1, 2]],
		[[0, 0, 0, 0, 0, 0], [0, 0, 1, 3, 2, 0, 3, 1, 1, 2], [1, 1, 1, 1, 0, 0], [0, 0, 3, 1, 2, 3, 1, 1, 1, 2]],

		[[1, 1, 1, 1, 1], [3, 3, 0, 2, 0, 0, 1, 3, 2, 0, 3, 1, 2], [0, 0, 0, 0, 0], [0, 3, 1, 2, 3, 1, 1, 1, 2, 3, 3, 0, 2]],
		[[1, 0, 0, 1, 1, 1, 1, 1], [0, 3, 1, 2, 3, 1, 1, 1, 2, 3, 3, 0, 2], [0, 0, 0, 0, 0, 0, 0, 0], [3, 3, 0, 2, 0, 0, 1, 3, 2, 0, 3, 1, 2]],
		[
			[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1],
			[0, 3, 1, 2, 3, 1, 1, 1, 2, 3, 3, 0, 2],
			[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
			[3, 3, 0, 2, 0, 0, 1, 3, 2, 0, 3, 1, 2]
		], [
			[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
			[0, 3, 1, 2, 3, 1, 1, 1, 2, 3, 3, 0, 2],
			[0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
			[3, 3, 0, 2, 0, 0, 1, 3, 2, 0, 3, 1, 2]
		],
		[[1, 1, 1, 1, 1, 1, 1, 1], [0, 3, 1, 2, 3, 1, 1, 1, 2, 3, 3, 0, 2], [0, 0, 0, 0, 0, 1, 1, 0], [3, 3, 0, 2, 0, 0, 1, 3, 2, 0, 3, 1, 2]]
	];

	macroTileBoundaries.collect({arg m; backtrackMacroTile.value(m[0], m[1], m[2], m[3])}).insert(0, []);
};

//function from Jaendel to generate the metatiles
generateMetaTiling = {arg n, t, x, y;
	var alpha, beta, gamma, delta, epsilon, g, f, dessin, res = [];

	alpha = 0;
	beta = 1;
	gamma = 2;
	delta = -2;
	epsilon = -1;

	g = Array.fib(32).drop(1);
	f = g.drop(1);

	dessin = {arg n, t, x, y;

		if(n <= 2, {
			res = res.add([x, y, n, t]);
		},{
			if((n % 2) == 1, {
				if(t == alpha, {
					dessin.value(n-2, alpha, x, y);
					dessin.value(n-2, gamma, x+g[n+1]-3, y);
					dessin.value(n-3, gamma, x-3, y+f[n-2]);
					dessin.value(n-3, alpha, x+g[n+1], y+f[n-2]);
					dessin.value(n-3, delta, x+g[n+2]-3, y+f[n-2]);
					dessin.value(n-2, delta, x-3, y+f[n-2]+f[n-3]);
					dessin.value(n-2, alpha, x+g[n+2], y+f[n-2]+f[n-3]);
				});

				if((t == beta) || (t == epsilon), {
					dessin.value(n-2, alpha, x+3, y);
					dessin.value(n-2, t, x+g[n+1], y);
					dessin.value(n-3, alpha, x+3, y+f[n-2]);
					dessin.value(n-3, -1 * t, x+g[n], y+f[n-2]);
					dessin.value(n-3, alpha, x+g[n+1]+3, y+f[n-2]);
					dessin.value(n-2, t, x, y+f[n-2]+f[n-3]);
					dessin.value(n-2, alpha, x+g[n]+3, y+f[n-2]+f[n-3]);
				});

				if((t == gamma) || (t == delta), {
					dessin.value(n-2, epsilon, x, y+f[n-2]+f[n-3]);
					dessin.value(n-2, alpha, x+g[n]+3, y+f[n-2]+f[n-3]);
					dessin.value(n-2, t, x+g[n+2], y+f[n-2]+f[n-3]);
					dessin.value(n-2, alpha, x+g[n+3]+g[n]+3, y+f[n-2]+f[n-3]);
					dessin.value(n-3, alpha, x+3, y+f[n-2]);
					dessin.value(n-3, beta, x+g[n], y+f[n-2]);
					dessin.value(n-3, alpha, x+g[n+1]+3, y+f[n-2]);
					dessin.value(n-3, -1 * t, x+g[n+2], y+f[n-2]);
					dessin.value(n-3, alpha, x+g[n+3]+3, y+f[n-2]);
					dessin.value(n-3, epsilon, x+g[n+2]+g[n+2], y+f[n-2]);
					dessin.value(n-3, alpha, x+g[n+3]+g[n+1]+3, y+f[n-2]);
					dessin.value(n-2, alpha, x+3, y);
					dessin.value(n-2, t, x+g[n+1], y);
					dessin.value(n-2, alpha, x+g[n+3]+3, y);
					dessin.value(n-2, beta, x+g[n+3]+g[n+1], y);
				});
			});

			if((n % 2) == 0, {
				if(t == alpha, {
					dessin.value(n-2, alpha, x, y);
					dessin.value(n-2, gamma, x+g[n+1]-3, y);
					dessin.value(n-3, gamma, x-3, y+f[n-2]);
					dessin.value(n-3, alpha, x+g[n+1], y+f[n-2]);
					dessin.value(n-3, delta, x+g[n+2]-3, y+f[n-2]);
					dessin.value(n-2, delta, x-3, y+f[n-2]+f[n-3]);
					dessin.value(n-2, alpha, x+g[n+2], y+f[n-2]+f[n-3]);
				});

				if((t == beta) || (t == epsilon), {
					dessin.value(n-2, alpha, x+3, y);
					dessin.value(n-2, t, x+g[n+1], y);
					dessin.value(n-3, alpha, x+3, y+f[n-2]);
					dessin.value(n-3, -1 * t, x+g[n], y+f[n-2]);
					dessin.value(n-3, alpha, x+g[n+1]+3, y+f[n-2]);
					dessin.value(n-2, t, x, y+f[n-2]+f[n-3]);
					dessin.value(n-2, alpha, x+g[n]+3, y+f[n-2]+f[n-3]);
				});

				if((t == gamma) || (t == delta), {
					dessin.value(n-2, epsilon, x, y+f[n-2]+f[n-3]);
					dessin.value(n-2, alpha, x+g[n]+3, y+f[n-2]+f[n-3]);
					dessin.value(n-2, t, x+g[n+2], y+f[n-2]+f[n-3]);
					dessin.value(n-2, alpha, x+g[n+3]+g[n]+3, y+f[n-2]+f[n-3]);
					dessin.value(n-3, alpha, x+3, y+f[n-2]);
					dessin.value(n-3, beta, x+g[n], y+f[n-2]);
					dessin.value(n-3, alpha, x+g[n+1]+3, y+f[n-2]);
					dessin.value(n-3, -1 * t, x+g[n+2], y+f[n-2]);
					dessin.value(n-3, alpha, x+g[n+3]+3, y+f[n-2]);
					dessin.value(n-3, epsilon, x+g[n+2]+g[n+2], y+f[n-2]);
					dessin.value(n-3, alpha, x+g[n+3]+g[n+1]+3, y+f[n-2]);
					dessin.value(n-2, alpha, x+3, y);
					dessin.value(n-2, t, x+g[n+1], y);
					dessin.value(n-2, alpha, x+g[n+3]+3, y);
					dessin.value(n-2, beta, x+g[n+3]+g[n+1], y);
				});

			});

		});
	};

	dessin.value(n, t, x, y);
	res.sort({ arg a, b; a[1] < b[1] }).separate({arg a, b; a[1] != b[1]}).collect({arg i; i.sort({ arg a, b; a[0] < b[0] })}).reverse
};

//function that does everthing: generate metatiling convert metatiles to macrotiles, then output tiling
~jaendel = {arg n, t, x = 0, y = 0;
	var metaTiling, metaTile2MacroTile, macroTiles, minXPos, maxRSize, res = [];

	metaTiling = generateMetaTiling.value(n, t, x, y);

	metaTile2MacroTile = {arg t; switch(t, 0, {0}, 1, {1}, 2, {2}, -2, {3}, -1, {4})};
	macroTiles = generateMacroTiles.value;

	//invert colors
	//macroTiles = (macroTiles - 3).abs;

	minXPos = metaTiling.slice(nil, 0).slice(nil, 0).minItem;

	metaTiling.do({arg metaTileRow;
		var height, shift;
		metaTileRow.do({arg metaTile, m;
			var macroTile;
			macroTile = macroTiles[(5 * metaTile[2]) + metaTile2MacroTile.value(metaTile[3])].deepCopy;
			if((metaTile[2] == 1) && (metaTile[3] == 0) && (m < (metaTileRow.size - 1)), {
				var nextMacroTile = macroTiles[(5 * metaTileRow[m + 1][2]) + metaTile2MacroTile.value(metaTileRow[m + 1][3])];
				macroTile[1][1][2] = nextMacroTile[1][0][0];
			});
			if(m == 0, {
				height = if(macroTile == [], {4}, {macroTile.size});
				shift = metaTile[0] + abs(minXPos);
				height.do({
					res = res.add([]);
					shift.do({res[res.size - 1] = res[res.size - 1].add([3, 3, 3, 3])});
				});
			});
			macroTile.do({arg row, r;
				row.do({arg tile; res[(res.size - height) + r] = res[(res.size - height) + r].add(tile)})});
		});
	});
	maxRSize = res.collect({arg row; row.size}).maxItem;
	res = res.collect({arg row; row.extend(maxRSize, [3, 3, 3, 3])});
	res
};

//sine tones for sonification
~jaendelCreateSynths = {
	SynthDef(\jaendel, {arg freq, col, attack, sustain, decay, del = 5, gate = 1, pan = 0;
		var env;
		env = EnvGen.kr(Env.new([0, 1, 1, 0], [attack, sustain, decay], \sin), 1, 1, 0, 1, doneAction: 2);
		//color is mapped to panning similar to the score which maps to different ways of playing the same note
		Out.ar(0, SinOsc.ar(freq, 0, (((col >= 1) * (col <= 1)) + (col >= 3)).lag(0.3) * env * 0.05));
		Out.ar(1, SinOsc.ar(freq, 0, (col >= 2).lag(0.3) * env * 0.05));
	}).add;
};

//generate musical data
~jaendelMusicify = {arg tiling, xOffset, yOffset, width, height;
	var subTiling, colors;

	//get a portion of the tiling passed as an argument
	//check that this is getting the correct subtiling
	subTiling = tiling.slice((yOffset..(yOffset + height - 1)), (xOffset..(xOffset + width - 1)));

	//turn rows of tiles into two rows of colors each
	//(bottom color is ignored as it is the same as the the top color of the next row)
	colors = subTiling.collect({arg row, i;
		row.collect({arg tile;
			[
				//this essentially elongates the tiles horizontally
				8.collect({tile[1]}),
				4.collect({tile[0]}) ++ 4.collect({tile[2]})
			];
		}).flop;
	}).flatten2(-1).flatten;

	//uncomment this to add last rows bottom color
	//colors = colors.add(subTiling.last.slice(nil, 3));
};

//sonify musical data
~jaendelSonify = {arg colors, version = 0 /*0 is full; 1 is reduced*/, startHarm = 1 /*must be odd*/, fund = 55;
	//generate patterns
	Ppar(colors.collect({arg row, r;
		var durUnit, del, eDur, start, stop, harms, fade;
		durUnit = 1 / 6; // 90 bpm
		// 2 measures for full version, 1 for reduced version
		del = if(version == 0, {(32 * (r % 16)) + (512 * (r / 16).asInteger)}, {(32 * (r % 8)) + (512 * (r / 8).asInteger)}) ;
		eDur = if(version == 0, {512 - 64}, {512 - 16}); // 32 - 4 measures for full version, 16 - 1 for reduced version
		start = del - r; // this skews the tiling
		stop = (start + eDur) - 1;
		harms =  ((0..(if(version == 0, {15}, {7}))) * 2).reverse + startHarm;
		fade = if(version == 0, {64}, {64});
		Pmono(\jaendel,
			\dur, Pseq([Rest(del * durUnit), Pseq([durUnit], inf)]),
			\freq, fund * harms[r % if(version == 0, {16}, {8})],
			\col, Pseq([0, Pseq(row[start..stop])]),
			\attack, fade * durUnit,
			\sustain, (eDur - (fade * 2)) * durUnit,
			\decay, fade * durUnit,
		)
	}));
};

)
