from siege import minutes, Random
from core.helper import select
from collections import deque

class MusicAssist(object):
    keys = "c c# d d# e f f# g g# a a# b cm c#m dm d#m em fm f#m gm g#m am a#m bm".split()
    roots = "I II III IV V VI VII".split()
    chords = {"c": "c dm em f g am bdim".split(),
              "c#": "c# d#m fm f# g# a#m cdim".split(),
              "d": "d em f#m g a bm c#dim".split(),
              "d#": "d# fm gm g# a# cm ddim".split(),
              "e": "e f#m g#m a b c#m d#dim".split(),
              "f": "f gm am a# c dm edim".split(),
              "f#": "f# g#m a#m b c# d#m fdim".split(),
              "g": "g am bm c d em f#dim".split(),
              "g#": "g# a#m cm c# d# fm gdim".split(),
              "a": "a bm c#m d e f#m g#dim".split(),
              "a#": "a# cm dm d# f gm adim".split(),
              "b": "b c#m d#m e f# g#m a#dim".split(),
              "cm": "cm ddim d# fm gm g# a#".split(),
              "c#m": "c#m d#dim e f#m g#m a# b".split(),
              "dm": "dm edim f gm am a# c".split(),
              "d#m": "d#m fdim f# g#m a#m b c#".split(),
              "em": "em f#dim g am bm c d".split(),
              "fm": "fm gdim g# a#m cm c# d#".split(),
              "f#m": "f#m g#dim a bm c#m d e".split(),
              "gm": "gm adim a# cm dm d# f".split(),
              "g#m": "g#m a#dim b c#m d#m e f#".split(),
              "am": "am bdim c dm em f g".split(),
              "a#m": "a#m cdim c# d#m fm f# g#".split(),
              "bm": "bm c#dim d em f#m g a".split(),
              }
    triads = {'c': 'c e g'.split(),
              'c#': 'c# f g#'.split(),
              'd': 'd f# a'.split(),
              'd#': 'd# g a#'.split(),
              'e': 'e g# b'.split(),
              'f': 'f a c'.split(),
              'f#': 'f# a# c#'.split(),
              'g': 'g b d'.split(),
              'g#': 'g# c d#'.split(),
              'a': 'a c# e'.split(),
              'a#': 'a# d f'.split(),
              'b': 'b d# f#'.split(),
              'cm': 'c d# g'.split(),
              'c#m': 'c# e g#'.split(),
              'dm': 'd f a'.split(),
              'd#m': 'd# f# a#'.split(),
              'em': 'e g b'.split(),
              'fm': 'f g# c'.split(),
              'f#m': 'f# a c#'.split(),
              'gm': 'g a# d'.split(),
              'g#m': 'g# b d#'.split(),
              'am': 'a c e'.split(),
              'a#m': 'a# c# f'.split(),
              'bm': 'b d f#'.split(),
              'cdim': 'c d# f#'.split(),
              'c#dim': 'c# e g'.split(),
              'ddim': 'd f g#'.split(),
              'd#dim': 'd# f# a'.split(),
              'edim': 'e g a#'.split(),
              'fdim': 'f g# b'.split(),
              'f#dim': 'f# a c'.split(),
              'gdim': 'g a# c#'.split(),
              'g#dim': 'g# b d'.split(),
              'adim': 'a c d#'.split(),
              'a#dim': 'a# c# e'.split(),
              'bdim': 'b d f'.split()
              }
    scales = {'c': 'c d e f g a b'.split(),
              'c#': 'c# d# f f# g# a# c'.split(),
              'd': 'd e f# g a b c#'.split(),
              'd#': 'd# f g g# a# c d'.split(),
              'e': 'e f# g# a b c# d#'.split(),
              'f': 'f g a a# c d e'.split(),
              'f#': 'f# g# a# b c# d# f'.split(),
              'g': 'g a b c d e f#'.split(),
              'g#': 'g# a# c c# d# e# g'.split(),
              'a': 'a b c# d e f# g#'.split(),
              'a#': 'a# c d d# f g a'.split(),
              'b': 'b c# d# e f# g# a#'.split(),
              'cm': 'c d d# f g g# a#'.split(),
              'c#m': 'c# d# e f# g# a b'.split(),
              'dm': 'd e f g a a# c'.split(),
              'd#m': 'd# f f# g# a# b c#'.split(),
              'em': 'e f# g a b c d'.split(),
              'fm': 'f g g# a# c c# d#'.split(),
              'f#m': 'f# g# a b c# d e'.split(),
              'gm': 'g a a# c d d# f'.split(),
              'g#m': 'g# a# b c# d# e f#'.split(),
              'am': 'a b c d e f g'.split(),
              'a#m': 'a# c c# d# f f# g#'.split(),
              'bm': 'b c# d e f# g a'.split(),
              }

    @staticmethod
    def getChords(key):
        return MusicAssist.chords[key]

    @staticmethod
    def getChordByRoot(key, root):
        return MusicAssist.chords[key][MusicAssist.roots.index(root)]

    @staticmethod
    def moveChordScale(chordRoot, movement):
        index = MusicAssist.roots.index(chordRoot) + movement
        if index >= len(MusicAssist.roots):
            index -= len(MusicAssist.roots)
        return MusicAssist.roots[index]


class MusicInfo(object):
    def __init__(self):
        self.volume = 100
        self.key = ''
        self.chord = ''
        self.signature = 4
        self.playTime = 0
        # The time the chord started
        self.chordStart = 0
        self.setTempo(60)

    def update(self, frameTime):
        self.playTime += frameTime

    def setTempo(self, tempo):
        self.tempo = tempo
        self.chordTime = minutes(1) / self.tempo * self.signature
        self.eighthTime = self.chordTime / 8

    def isChordFinished(self):
        return self.playTime - self.chordStart >= self.chordTime or self.playTime == 0

    def startChord(self, chord):
        self.chordStart = self.playTime
        self.chord = MusicAssist.getChordByRoot(self.key, chord)

    def isChordStarting(self):
        return self.chordStart == self.playTime


class MusicConductor(object):
    def __init__(self):
        self.info = MusicInfo()
        self.layers = []
        # Queue of chords to play
        self.chords = []

    def update(self, frameTime):
        if self.info.isChordFinished():
            if not self.chords:
                self.generate()
            self.info.startChord(self.chords[0])
            print "Starting chord {}".format(self.chords[0])
            del self.chords[0]

    def generate(self):
        pass


class MusicLayer(object):
    def __init__(self):
        self.sound = None
        self.lastPlayTime = 0
        self.notes = deque()
        self.path = ""

    def update(self, frameTime, info):
        if self.shouldPlayNext(info):
            note = self.notes.popleft()
            if note:
                self.play(info, self.path.format(note=note))
            else:
                self.lastPlayTime = info.playTime
                print "Skipping note"

    def shouldPlayNext(self, info):
        pass

    def generate(self, info, chords):
        pass

    def play(self, info, path):
        self.sound = game.audio.play(path)
        self.lastPlayTime = info.playTime
        print "Playing {}".format(path)


class TestConductor(MusicConductor):
    def __init__(self):
        super(TestConductor, self).__init__()
        self.layers.append(TestChordLayer())
        self.layers.append(TestMelodyLayer())
        self.info.key = MusicAssist.keys[Random.get(len(MusicAssist.keys) - 1)]
        print "Playing in key of {}".format(self.info.key)

    def update(self, frameTime):
        super(TestConductor, self).update(frameTime)
        for layer in self.layers:
            layer.update(frameTime, self.info)
        self.info.update(frameTime)

    def generate(self):
        # Generate eight chords always starting with the tonic
        self.chords.append("I")
        # Next randomly choose between the following
        self.chords.append(select(["IV", "III", "VI", "I"]))
        # Third chord we weight towards a IV and do not allow for I if second is I
        options = ["IV", "IV", "IV", "VI", "II"]
        if self.chords[-1] != "I":
            options.append("I")
        self.chords.append(select(options))
        # Forth chord split between V and VI
        self.chords.append("V" if Random.getBool() else "VI")
        # Move each chord up 2 scale degrees. First is always III.
        self.chords.append("III")
        self.chords.append(MusicAssist.moveChordScale(self.chords[1], 2))
        self.chords.append(MusicAssist.moveChordScale(self.chords[2], 2))
        #Always end on V
        self.chords.append("V")
        for layer in self.layers:
            layer.generate(self.info, self.chords)
        print self.chords


class TestChordLayer(MusicLayer):
    def __init__(self):
        super(TestChordLayer, self).__init__()
        self.path = "mods/core/audio/prototype/chords/{note}.wav"

    def shouldPlayNext(self, info):
        return info.isChordStarting()

    def generate(self, info, chords):
        for chord in chords:
            self.notes.append(MusicAssist.getChordByRoot(info.key, chord))


class TestMelodyLayer(MusicLayer):
    def __init__(self):
        super(TestMelodyLayer, self).__init__()
        self.path = "mods/core/audio/prototype/melody/{note}.wav"

    def shouldPlayNext(self, info):
        return (self.lastPlayTime + info.eighthTime < info.playTime or info.playTime == 0)

    def generate(self, info, chords):
        print "Generating melody"
        for chord in chords:
            self.notes.append(Random.get(MusicAssist.triads[MusicAssist.getChordByRoot(info.key, chord)]))
            for i in xrange(7):
                note = ""
                if Random.getBool():
                    note = Random.get(MusicAssist.scales[info.key])
                self.notes.append(note)
        for i in xrange(len(self.notes)):
            if self.notes[i]:
                self.notes[i] += "1" if Random.getBool() else "2"
        print self.notes

