# This file is part of python-ly, https://pypi.python.org/pypi/python-ly
#
# Copyright (c) 2011 - 2015 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.

"""
Convert absolute music to relative music.
"""

from __future__ import unicode_literals

import itertools

import ly.lex.lilypond


def abs2rel(cursor, language="nederlands", startpitch=True, first_pitch_absolute=False):
    """Converts pitches from absolute to relative.
    
    language: language to start reading pitch names in
    
    startpitch: if True, write a starting pitch before the opening bracket of
        a relative expression.
        
    first_pitch_absolute: this option only makes sense when startpitch is False.
        If first_pitch_absolute is True, the first pitch of a \\relative
        expression is written as absolute. This mimics the behaviour of
        LilyPond >= 2.18. (In fact, the starting pitch is then assumed to be f.)
        
        If False, the first pitch is written as relative to c'
        (LilyPond < 2.18 behaviour).
    
    Existing \\relative expressions are not changed.
    
    """
    start = cursor.start
    cursor.start = 0
    
    source = ly.document.Source(cursor, True, tokens_with_position=True)

    pitches = ly.pitch.PitchIterator(source, language)
    psource = pitches.pitches()
    
    if start > 0:
        # consume tokens before the selection, following the language
        t = source.consume(pitches.tokens(), start)
        if t:
            psource = itertools.chain((t,), psource)
    
    # this class dispatches the tokens. we can't use a generator function
    # as that doesn't like to be called again while there is already a body
    # running.
    class gen(object):
        def __iter__(self):
            return self
        
        def __next__(self):
            t = next(psource)
            while isinstance(t, (ly.lex.Space, ly.lex.Comment)):
                t = next(psource)
            if t == '\\relative' and isinstance(t, ly.lex.lilypond.Command):
                relative()
                t = next(psource)
            elif isinstance(t, ly.lex.lilypond.ChordMode):
                consume() # do not change chords
                t = next(psource)
            elif isinstance(t, ly.lex.lilypond.MarkupScore):
                consume()
                t = next(psource)
            return t
        
        next = __next__
            
    tsource = gen()

    def getpitches(iterable):
        """Consumes iterable but only yields Pitch instances."""
        for p in iterable:
            if isinstance(p, ly.pitch.Pitch):
                yield p

    def context():
        """Consume tokens till the level drops (we exit a construct)."""
        depth = source.state.depth()
        for t in tsource:
            yield t
            if source.state.depth() < depth:
                return
    
    def consume():
        """Consume tokens from context() returning the last token, if any."""
        t = None
        for t in context():
            pass
        return t
    
    def relative():
        r"""Consume the whole \relative expression without doing anything. """
        # skip pitch argument
        t = next(tsource)
        if isinstance(t, ly.pitch.Pitch):
            t = next(tsource)
        
        while True:
            # eat stuff like \new Staff == "bla" \new Voice \notes etc.
            if isinstance(source.state.parser(), ly.lex.lilypond.ParseTranslator):
                t = consume()
            elif isinstance(t, ly.lex.lilypond.NoteMode):
                t = next(tsource)
            else:
                break
        
        if t in ('{', '<<', '<'):
            consume()
    
    # Do it!
    with cursor.document as document:
        for t in tsource:
            if t in ('{', '<<'):
                # Ok, parse current expression.
                pos = t.pos     # where to insert the \relative command
                lastPitch = None
                chord = None
                for t in context():
                    # skip commands with pitches that do not count
                    if isinstance(t, ly.lex.lilypond.PitchCommand):
                        consume()
                    elif isinstance(t, ly.lex.lilypond.ChordStart):
                        # Handle chord
                        chord = []
                    elif isinstance(t, ly.lex.lilypond.ChordEnd):
                        if chord:
                            lastPitch = chord[0]
                        chord = None
                    elif isinstance(t, ly.pitch.Pitch):
                        # Handle pitch
                        if lastPitch is None:
                            if startpitch:
                                lastPitch = ly.pitch.Pitch.c1()
                                lastPitch.octave = t.octave
                                if t.note > 3:
                                    lastPitch.octave += 1
                                document[pos:pos] = "\\relative {0} ".format(
                                        lastPitch.output(pitches.language))
                            else:
                                if first_pitch_absolute:
                                    lastPitch = ly.pitch.Pitch.f0()
                                else:
                                    lastPitch = ly.pitch.Pitch.c1()
                                document[pos:pos] = "\\relative "
                        p = t.copy()
                        t.makeRelative(lastPitch)
                        pitches.write(t)
                        lastPitch = p
                        # remember the first pitch of a chord
                        if chord == []:
                            chord.append(p)


