# -*- coding: utf-8 -*-
"""
converstion d'un fichier musicxml en objet song minwii.

$Id$
$URL$
"""
import sys
from types import StringTypes
from xml.dom.minidom import parse
from optparse import OptionParser
#from Song import Song

# Do4 <=> midi 60
OCTAVE_REF = 4
DIATO_SCALE = {'C' : 60,
               'D' : 62,
               'E' : 64,
               'F' : 65,
               'G' : 67,
               'A' : 69,
               'B' : 71}

FR_NOTES = {'C' : u'Do',
            'D' : u'Ré',
            'E' : u'Mi',
            'F' : u'Fa',
            'G' : u'Sol',
            'A' : u'La',
            'B' : u'Si'}

_marker = []

class Part(object) :
    
    def __init__(self, node, autoDetectChorus=True) :
        self.node = node
        self.notes = []
        self._parseMusic()
        self.verses = [[]]
        self.chorus = []
        if autoDetectChorus :
            self._findChorus()
        self._findVersesLoops()
    
    def _parseMusic(self) :
        divisions = 0
        noteIndex = 0
        next = previous = None
        for measureNode in self.node.getElementsByTagName('measure') :
            # divisions de la noire
            divisions = int(_getNodeValue(measureNode, 'attributes/divisions', divisions))
            for noteNode in measureNode.getElementsByTagName('note') :
                note = Note(noteNode, divisions, previous)
                self.notes.append(note)
                try :
                    self.notes[noteIndex-1].next = note
                except IndexError:
                    pass
                previous = note
                noteIndex += 1

    def _findChorus(self):
        """ le refrain correspond aux notes pour lesquelles
            il n'existe q'une seule syllable attachée.
        """
        start = stop = None
        for i, note in enumerate(self.notes) :
            ll = len(note.lyrics)
            if start is None and ll == 1 :
                start = i
            elif start is not None and ll > 1 :
                stop = i
                break
        self.chorus = self.notes[start:stop]
    
    def _findVersesLoops(self) :
        "recherche des couplets / boucles"
        verse = self.verses[0]
        for note in self.notes[:-1] :
            verse.append(note)
            ll = len(note.lyrics)
            nll = len(note.next.lyrics)
            if ll != nll :
                verse = []
                self.verses.append(verse)
        verse.append(self.notes[-1])
        
    
    def iterNotes(self) :
        "exécution de la chanson avec l'alternance couplets / refrains"
        for verse in self.verses :
            print "---partie---"
            repeats = len(verse[0].lyrics)
            if repeats > 1 :
                for i in range(repeats) :
                    # couplet
                    print "---couplet%d---" % i
                    for note in verse :
                        yield note, i
                    # refrain
                    print "---refrain---"
                    for note in self.chorus :
                        yield note, 0
            else :
                for note in verse :
                    yield note, 0
        
    def pprint(self) :
        for note, verseIndex in self.iterNotes() :
            print note.nom, note.name, note.midi, note.duration, note.lyrics[verseIndex]
        
        

class Note(object) :
    def __init__(self, node, divisions, previous) :
        self.node = node
        self.step = _getNodeValue(node, 'pitch/step')
        self.octave = int(_getNodeValue(node, 'pitch/octave'))
        self.alter = int(_getNodeValue(node, 'pitch/alter', 0))
        self._duration = float(_getNodeValue(node, 'duration'))
        self.lyrics = []
        for ly in node.getElementsByTagName('lyric') :
            self.lyrics.append(Lyric(ly))

        self.divisions = divisions
        self.previous = previous
        self.next = None
    
    @property
    def midi(self) :
        mid = DIATO_SCALE[self.step]
        mid = mid + (self.octave - OCTAVE_REF) * 12
        mid = mid + self.alter
        return mid
    
    @property
    def duration(self) :
        return self._duration / self.divisions
    
    @property
    def name(self) :
        name = '%s%d' % (self.step, self.octave)
        if self.alter < 0 :
            alterext = 'b'
        else :
            alterext = '#'
        name = '%s%s' % (name, abs(self.alter) * alterext)
        return name
    
    @property
    def nom(self) :
        name = FR_NOTES[self.step]
        if self.alter < 0 :
            alterext = 'b'
        else :
            alterext = '#'
        name = '%s%s' % (name, abs(self.alter) * alterext)
        return name
    

class Lyric(object) :
    
    _syllabicModifiers = {
        'single' : '%s',
        'begin'  : '%s -',
        'middle' : '- %s -',
        'end'    : '- %s'
        }
    
    def __init__(self, node) :
        self.node = node
        self.syllabic = _getNodeValue(node, 'syllabic', 'single')
        self.text = _getNodeValue(node, 'text')
    
    def __str__(self) :
        text = self._syllabicModifiers[self.syllabic] % self.text
        return text.encode('utf-8')
    __repr__  = __str__
        
        


def _getNodeValue(node, path, default=_marker) :
    try :
        for name in path.split('/') :
            node = node.getElementsByTagName(name)[0]
        return node.firstChild.nodeValue
    except :
        if default is _marker :
            raise
        else :
            return default

def musicXml2Song(input, partIndex=0, printNotes=False) :
    if isinstance(input, StringTypes) :
        input = open(input, 'r')
    
    d = parse(input)
    doc = d.documentElement
    
    # TODO conversion préalable score-timewise -> score-partwise
    assert doc.nodeName == u'score-partwise'
    
    parts = doc.getElementsByTagName('part')
    leadPart = parts[partIndex]
    
    part = Part(leadPart)
    
    if printNotes :
        part.pprint()

    return part

    
    # divisions de la noire
#    divisions = 0
#    midiNotes, durations, lyrics = [], [], []
#
#    for measureNode in leadPart.getElementsByTagName('measure') :
#        divisions = int(_getNodeValue(measureNode, 'attributes/divisions', divisions))
#        for noteNode in measureNode.getElementsByTagName('note') :
#            note = Note(noteNode, divisions)
#            if printNotes :
#                print note.name, note.midi, note.duration, note.lyric
#            midiNotes.append(note.midi)
#            durations.append(note.duration)
#            lyrics.append(note.lyric)
#    
#    song = Song(None,
#                midiNoteNumbers = midiNotes,
#                noteLengths = durations,
#                lyrics = lyrics,
#                notesInExtendedScale=None)
#    song.save(output)
    
    
def main() :
    usage = "%prog musicXmlFile.xml outputSongFile.smwi [options]"
    op = OptionParser(usage)
    op.add_option("-i", "--part-index", dest="partIndex"
                 , default = 0
                 , help = "Index de la partie qui contient le champ.")
    op.add_option("-p", '--print', dest='printNotes'
                  , action="store_true"
                  , default = False
                  , help = "Affiche les notes sur la sortie standard (debug)")
    
    options, args = op.parse_args()
    
    if len(args) != 1 :
        raise SystemExit(op.format_help())
    
    musicXml2Song(args[0], partIndex=options.partIndex, printNotes=options.printNotes)
    


if __name__ == '__main__' :
    sys.exit(main())
