from __future__ import generators
import sys

class Note:
	names = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"]

	def __init__(self, value):
		if type(value) is str:
			self.value = self.names.index(value)
		else:
			self.value = value % 12

	# It doesn't make sense to add two Notes, but you can add an interval.
	def __add__(self, r):
		return Note(self.value + r)
	def __cmp__(self, r):
		return cmp(self.value, r.value)

	def name(self): return self.names[self.value]
	def __str__(self): return self.name()
	def __repr__(self): return "Note(" + self.name() + ")"
	# This uses the octave starting at middle C.
	def frequency(self): return 261.625 * (1.05946309434 ** self.value)

class Chord:
	def __init__(self, name, label, notes):
		self.name = name
		self.label = label
		self.notes = notes
	def getnotes(self, root):
		return [root] + [root + n for n in self.notes]

def btsearch(length, values, test, provided = []):
	fill = [None] * length
	for i in values:
		p = provided + [i]
		if not test((p + fill)[:length]):
			continue
		if len(p) == length:
			yield p
		else:
			for v in btsearch(length, values, test, p):
				yield v

class Stringed:
	def notes(self, fingering):
		ns = []
		for (s, f) in zip(self.strings, fingering):
			if f is not None:
				ns.append(s + f)
		return ns
	def playable(self, fingering):
		return True
	def search(self, notes):
		def t(f):
			if not self.playable(f):
				return False
			for n in self.notes(f):
				if not n in notes:
					return False
			return True
		for f in btsearch(len(self.strings), range(self.scale), t):
			yield f
	def showfingering(self, fingering):
		ns = self.notes(fingering)
		for i in range(len(self.strings) - 1, -1, -1):
			b = ["-"] * self.scale
			if fingering[i] != 0:
				b[fingering[i] - 1] = "X"
			s = ("%5s |" + "".join(b) + " %2d %s") % (self.strings[i], fingering[i], ns[i])
			print s

def fingers(fingering):
	return [f for f in fingering if f is not None and f != 0]

def monotonic(fs):
	if len(fs) < 2:
		return True
	dr = 0
	for i in range(len(fs) - 1):
		d = cmp(fs[i], fs[i + 1])
		if d != 0:
			if dr != 0 and dr != d:
				return False
			dr = d
	return True

def canstretch(fs, max):
	if len(fs) < 2:
		return True
	ff = fs + []
	ff.sort()
	return (ff[-1] - ff[0]) <= max

def numfingers(fs, max):
	seen = {}
	for f in fs:
		seen[f] = 1
	if len(seen.keys()) > max:
		return False
	return True

class GuitarLike(Stringed):
	def playable(self, fingering):
		fs = fingers(fingering)
		return monotonic(fs) and canstretch(fs, self.stretch) and numfingers(fs, self.fingers)

class Mandolin(GuitarLike):
	name = "Mandolin"
	strings = [Note("G"), Note("D"), Note("A"), Note("E")]
	scale = 12
	stretch = 4
	fingers = 2

class Mandola(GuitarLike):
	name = "Mandola"
	#strings = [Note("C"), Note("G"), Note("D"), Note("G")]
	strings = [Note("C"), Note("G"), Note("D"), Note("A")]
	scale = 12
	stretch = 4
	fingers = 3

class Guitar(GuitarLike):
	name = "Guitar"
	strings = [Note("E"), Note("A"), Note("D"), Note("G"), Note("B"), Note("E")]
	scale = 16
	stretch = 5
	fingers = 3

chords = [
	Chord("major", "", [4, 7]),
	Chord("sustained fourth", "sus4", [5, 7]),
	Chord("open fifth", "5", [7]),
	Chord("minor", "m", [3, 7]),
	Chord("seventh", "7", [4, 7, 10]),
	Chord("seventh sustained fourth", "7sus4", [5, 7, 10]),
	Chord("seventh minor", "m7", [3, 7, 10]),
	Chord("sixth", "6", [4, 7, 9]),
	Chord("sixth minor", "m6", [3, 7, 9]),
	Chord("major seventh", "M7", [4, 7, 11]),
	Chord("diminished", "dim", [3, 6]),
	Chord("diminished #7", "dim#7", [3, 6, 11]),
	]

def getchord(name):
	if len(name) > 1 and name[1] == "b":
		root = Note(name[:2])
		name = name[2:]
	else:
		root = Note(name[0])
		name = name[1:]
	for c in chords:
		if c.label == name:
			return (root, c)
	print "can't parse chord", name
	sys.exit(20)

if __name__ == "__main__":
	m = Mandola()
	(root, c) = getchord(sys.argv[1])
	print "Chord:", str(root) + c.label
	for f in m.search(c.getnotes(root)):
		m.showfingering(f)
		print

