#!/usr/bin/python3.0
#
# Calcube: Checks if a calendar cube has valid stickers.
#
# 2009-04-02: Release by Johannes Bauer under the terms of the GNU GPL
#             version 2 (no later version).
import sys
import getopt

class Options():
	def __init__(self):
		self.debug = False
		self.enum = False

try:
	optlist, args = getopt.getopt(sys.argv[1:], "de")
except getopt.GetoptError as e:
	print("Error: %s" % (e))
	print("Syntax:")
	print("    -d       Output debug messages")
	print("    -e       Enumarate all broken possibilities")
	sys.exit(1)

pgmopts = Options()

for (opt, arg) in optlist:
	if opt == "-d":
		pgmopts.debug = True
	elif opt == "-e":
		pgmopts.enum = True

class Piece():
	def __init__(self, pos):
		self.__pos = set(pos)

	def getpos(self):
		return self.__pos

	def iscorner(self):
		for i in self.__pos:
			return i[0] in [ 0, 2, 6, 8]
	
	def isedge(self):
		for i in self.__pos:
			return i[0] in [ 1, 3, 5, 7]
	
	def isctr(self):
		for i in self.__pos:
			return i[0] == 4

	def __str__(self):
		if self.iscorner():
			s = "Corner"
		elif self.isedge():
			s = "Edge"
		else:
			s = "Center"
		s += " "

		poss = set()
		for x in self.__pos:
			poss.add(x[1])

		for x in poss:
			if x == " ":
				s += "' '/"
			else:
				s += x + "/"

		s = s[:-1]
		return s

	def atpos(self, pos):
		r = set()
		for (x, y) in self.__pos:
			if x == pos:
				r.add(y)
		return r



#    0 1 2 
#    3 4 5
#    6 7 8

# This is the cube representation. The keys of the dict represent the pieces (26 total in every
# 3x3x3 cube. The Pieces take as a constructor parameter which label the piece in question has
# at which position. In the example above the piece 0 can be a "Tues" or "Satur" in the upper left
# corner, or it may be an empty sticker in any corner (corners 0, 2, 6 and 8).

cube = {
	0:		Piece([ 
				(0, "Tues"),
				(0, "Satur"),
				(0, " "),
				(2, " "),
				(6, " "),
				(8, " "),
			]),
	1:		Piece([ 
				(1, "day"),
				(1, " "),
				(3, " "),
				(5, " "),
				(7, " "),
			]),
	2:		Piece([ 
				(8, "4"),
				(8, "2"),
				(0, "8"),
				(8, "8"),
			]),
	3:		Piece([ 
				(5, "C"),
				(1, " "),
				(3, " "),
				(5, " "),
				(7, " "),
			]),
	4:		Piece([ 
				(4, "O"),
			]),
	5:		Piece([ 
				(5, "P"),
				(3, "O"),
				(5, "O"),
			]),
	6:		Piece([ 
				(0, "0"),
				(8, "0"),
				(0, "9"),
				(8, "6"),
				(0, " "),
				(2, " "),
				(6, " "),
				(8, " "),
			]),
	7:		Piece([ 
				(5, "G"),
				(5, "N"),
				(3, "N"),
			]),
	8:		Piece([ 
				(0, " "),
				(2, " "),
				(6, " "),
				(8, " "),
				(8, "5"),
				(0, "6"),
				(8, "9"),
			]),
	9:		Piece([ 
				(1, " "),
				(3, " "),
				(5, " "),
				(7, " "),
				(7, "2"),
			]),
	10:		Piece([ 
				(4, "P"),
			]),
	11:		Piece([ 
				(5, "L"),
				(3, "A"),
			]),
	12:		Piece([ 
				(4, "E"),
			]),
	13:		Piece([ 
				(5, "V"),
				(3, "D"),
			]),
	14:		Piece([ 
				(4, "A"),
			]),
	15:		Piece([ 
				(3, "S"),
				(5, "S"),
				(5, "T"),
			]),
	16:		Piece([ 
				(4, "U"),
			]),
	17:		Piece([ 
				(8, "7"),
				(8, "1"),
				(8, "3"),
			]),
	18:		Piece([ 
				(5, "B"),
				(5, "Y"),
			]),
	19:		Piece([ 
				(0, "Fri"),
				(0, "Mon"),
				(0, " "),
				(2, " "),
				(6, " "),
				(8, " "),
			]),
	20:		Piece([ 
				(7, "3"),
				(7, "1"),
			]),
	21:		Piece([ 
				(4, "C"),
			]),
	22:		Piece([ 
				(3, "J"),
				(5, "R"),
			]),
	23:		Piece([ 
				(0, "Thurs"),
				(0, "Sun"),
				(0, " "),
				(2, " "),
				(6, " "),
				(8, " "),
			]),
	24:		Piece([ 
				(3, "M"),
				(3, "F"),
			]),
	25:		Piece([ 
				(0, "Wednes"),
				(0, " "),
				(2, " "),
				(6, " "),
				(8, " "),
			]),
}

corners = { }
edges = { }
centers = { }

for (pieceno, piece) in cube.items():
	if piece.iscorner():
		corners[pieceno] = piece
	elif piece.isedge():
		edges[pieceno] = piece
	else:
		centers[pieceno] = piece

sizecheck = [ ("corners", corners, 8), ("edges", edges, 12), ("centers", centers, 6) ]
correct = True
for (name, pieces, cnt) in sizecheck:
	if len(pieces) != cnt:
		print("Warning: The count of %s of the specified cube is %d although a correct cube should have %d %s." % (name, len(pieces), cnt, name))
		correct = False

if correct and pgmopts.debug:
	print("The cube definition you specified is formally correct.")

if pgmopts.debug:
	for (pieceno, piece) in corners.items():
		print(piece)

	for (pieceno, piece) in edges.items():
		print(piece)

	for (pieceno, piece) in centers.items():
		print(piece)

#    0 1 2 
#    3 4 5
#    6 7 8

def requirement_met(pcs):
#	print(pcs)
	for a in pcs[0]:
		for b in pcs[1]:
			for c in pcs[2]:
				for d in pcs[3]:
					for e in pcs[4]:
						for f in pcs[5]:
							for g in pcs[6]:
								for h in pcs[7]:
									for i in pcs[8]:
										st = set([a, b, c, d, e, f, g, h, i])
										if len(st) == 9:
											return True
#										print(len(st), a, b, c, d, e, f, g, h, i)
	return False

months = [ "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" ]
days = [ "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur", "Sun" ]

badcombinations = 0
combinations = 0

for month in months:
	for day in days:
		for date in range(1, 32):
			requirement = {
				0: day,
				1: "day",
				2: " ",
				3: month[0],
				4: month[1],
				5: month[2],
				6: " ",
			}
			if date < 10:
				requirement[7] = " "
				requirement[8] = str(date)
			else:
				requirement[7] = str(date)[0]
				requirement[8] = str(date)[1]

			fulfills = { }
			for pieceno in range(9):
				if pieceno in [ 0, 2, 6, 8 ]:
					available = corners
				elif pieceno in [ 1, 3, 5, 7 ]:
					available = edges
				else:
					available = centers

#				print("Piece #%d (%s), %d available pieces" % (pieceno, requirement[pieceno], len(available)))
			
				fulfills[pieceno] = set()
				for (avpieceno, avpiece) in available.items():
					canbe = avpiece.atpos(pieceno)
					if requirement[pieceno] in canbe:
						fulfills[pieceno].add(avpieceno)

			combinations += 1
			if not requirement_met(fulfills):
				badcombinations += 1
				if not pgmopts.enum:
					print("Combination not possible: %s %s %s" % (month, day, date))
					print("Met by: %s" % (requirement))
					print("Pieces available: %s" % (fulfills))
					print("Pieces in detail:")
					for (pieceno, pieces) in fulfills.items():
						print("    %d [%s] %s" % (pieceno, requirement[pieceno], pieces))
						for p in pieces:
							print("        %d (%s)" % (p, str(cube[p])))
					sys.exit(0)
				else:
					if pgmopts.debug:
						print("Combination not possible: %s %s %s" % (month, day, date))

if badcombinations == 0:
	print("Your calendar cube is in perfect shape (%d combinations checked) :-)" % (combinations))
else:
	print("Your calendar cube is not entirely solvable (%d of %d combinations not possible)." % (badcombinations, combinations))

