#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <string>
#include <sstream>
#include <vector>
#include <array>
#include <iomanip>

enum class sidename
{
	a, b, c, d, x, y
};

enum class sidetype
{
	phpbenelux, php, phpelephant, dwa
};

std::string sidename_str(sidename sn) {
	switch (sn) {
		case sidename::a: return "a";
		case sidename::b: return "b";
		case sidename::c: return "c";
		case sidename::d: return "d";
		case sidename::x: return "x";
		case sidename::y: return "y";
	}
    throw std::runtime_error{"invalid sidename"};
}

std::string sidetype_str(sidetype st) {
	switch (st) {
		case sidetype::phpbenelux: return "phpbenelux";
		case sidetype::php: return "php";
		case sidetype::phpelephant: return "phpelephant";
		case sidetype::dwa: return "dwa";
	}
    throw std::runtime_error{"invalid sidetype"};
}

/**
 * cubes define 6 side types (or pictures)
 *
 * cubes calculate for themselves all possible layouts, meaning
 * if you rotate them into some direction, you get a side, followed by side+1, side+2, side+3.
 * there are a few possibilities: reverse order, and in each layout (or "path") you can start
 * rotating the cube on side, side+1, side+2 or side+4 (starting point "shifts").
 */
class cube
{
public:
	
	cube(sidetype a, sidetype b, sidetype c, sidetype d, sidetype x, sidetype y)
		: a_{a}, b_{b}, c_{c}, d_{d}, x_{x}, y_{y},
		  directionnames_({{ 
			 {sidename::a, sidename::b, sidename::c, sidename::d},
			 {sidename::x, sidename::b, sidename::y, sidename::d},
			 {sidename::a, sidename::y, sidename::c, sidename::x} }}),
		  directions_({{ {a, b, c, d}, {x, b, y, d}, {a, y, c, x} }})
	{
		for (int i=0; i<4; i++) {
			for (auto &sides : directions_) {
				// normal insert
				layouts_.push_back(sides);

				// reverse insert
				auto sidesrev = sides;
				std::reverse(std::begin(sidesrev), std::end(sidesrev));
				layouts_.push_back(sidesrev);

				// shift all
				sidetype temp = sides[0];
				for (int i=1; i<=3; i++)
					sides[i - 1] = sides[i];
				sides[3] = temp;
			}
		}
	}

	const std::vector<std::array<sidetype, 4>> & layouts() { return layouts_; }

private:

/**
 * This is how I labeled each sidetype:
 * 
 *	  X	 =   a
 *	X X X   = x b y 
 *	  X	 =   c
 *	  X	 =   d
*/

	sidetype a_;
	sidetype b_;
	sidetype c_;
	sidetype d_;
	sidetype x_;
	sidetype y_;

	std::array<std::array<sidename, 4>, 3> directionnames_;
	std::array<std::array<sidetype, 4>, 3> directions_;
	std::vector<std::array<sidetype, 4>> layouts_;
};

/**
 * helper class that can see if a given solution is a duplicate from a previous solution
 * 
 * if you have a solution that is simply the same one, but rotating in a different direction
 * is not really a new solution. also note the four possible starting point in each layout/path.
 * so it will check if duplicates exist in both forward and backward directions, and for each
 * possible four shifts
 */
class solutions
{
public:
	solutions()
	{}

	bool is_dupe(std::array<std::array<sidetype, 4>, 4> temp2)
	{
		// Check if found solution isn't a duplicate
		bool duplicate = false;
		for (auto &solution : solutions_) {
			for (int j=0; j<8; j++) {
				duplicate = true;
				int sidenum = 0;
				for (auto &side : solution) {
					auto &temp = temp2[sidenum++];

					int count = 0;
					int offset = j % 4;
					if (j < 4) {
						// Check if they are duplicates, as we use offsets of +0, +1, +2, +3 we can 
						//  detect shifted duplicate results.
						for (auto i = side.begin(); i != side.end(); i++) {
							duplicate = duplicate && temp[(count + offset) % 4] == *i;
							count++;
						}
					}
					else {
						// Check if they are duplicates simply in reverse order, also with the 
						//  detect for shifted duplicates.
						for (auto i = side.rbegin(); i != side.rend(); i++) {
							duplicate = duplicate && temp[(count + offset) % 4] == *i;
							count++;
						}
					}
				}
				if (duplicate)
					   return true;
			}
		}

		// Remember found solution, for duplicates checking
		solutions_.push_back(temp2);

		return false;
	}

private:
	std::vector<std::array<std::array<sidetype, 4>, 4>> solutions_;
};

int main (int argc, char *argv[]) 
{
/*
 * on the sheet:
 * 
 *	  cube 4 (sideways)
 * 
 *   cube 1, 2, 3
*/

	cube one{
		sidetype::dwa,
		sidetype::phpelephant,
		sidetype::phpbenelux,
		sidetype::dwa,
		sidetype::php,
		sidetype::phpbenelux};

	cube two{
		sidetype::phpelephant,
		sidetype::phpbenelux,
		sidetype::phpbenelux,
		sidetype::phpbenelux,
		sidetype::php,
		sidetype::dwa};

	cube three{
		sidetype::phpbenelux,
		sidetype::dwa,
		sidetype::phpelephant,
		sidetype::php,
		sidetype::dwa,
		sidetype::phpelephant};

	cube four{
		sidetype::php,
		sidetype::phpelephant,
		sidetype::phpbenelux,
		sidetype::phpelephant,
		sidetype::dwa,
		sidetype::php};

	solutions solution;

	for (auto &cube1sides : one.layouts()) {
	for (auto &cube2sides : two.layouts()) {
	for (auto &cube3sides : three.layouts()) {
	for (auto &cube4sides : four.layouts()) {

		// Pictures have to be unique on each four cubes to be considered a unique solution..
		bool flag = false;
		for (int i=0; i<4; i++) {
			// .. Also on each four rotations of course
			flag = flag || (!(cube1sides[i] != cube2sides[i] &&
							  cube1sides[i] != cube3sides[i] &&
							  cube1sides[i] != cube4sides[i] &&
							  cube2sides[i] != cube3sides[i] &&
							  cube2sides[i] != cube4sides[i] &&
							  cube3sides[i] != cube4sides[i]));
		}
		if (!flag){
			// Skip duplicate solutions
			if (solution.is_dupe({cube1sides, cube2sides, cube3sides, cube4sides})) {
				continue;
			}

			// Print the result
			std::cout << "The cube-layout for the solution:" << std::endl << std::endl;

			static auto print = [](const std::string &cube, decltype(cube1sides) &sides) {
				std::cout << cube << ": " 
					<< "	front: " << std::setw(15) << sidetype_str(sides[0]) << ", "
					<< "	up:	" << std::setw(15) << sidetype_str(sides[1]) << ", "
					<< "	top:   " << std::setw(15) << sidetype_str(sides[2]) << ", "
					<< "	back:  " << std::setw(15) << sidetype_str(sides[3])
					<< std::endl;
			};

			print("cube #1", cube1sides);
			print("cube #2", cube2sides);
			print("cube #3", cube3sides);
			print("cube #4", cube4sides);
		}
	}}}}
}