# GNU Solfege - free ear training software
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2007, 2008  Tom Cato Amundsen
#
# 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, see <http://www.gnu.org/licenses/>.

from __future__ import absolute_import

import operator

from solfege.mpd import Rat
from solfege.mpd import Track, PercussionTrack
from solfege.mpd import const
from solfege.mpd import elems

class MidiPerformer(object):
    def __init__(self, score):
        self.m_score = score
    def get_all_tpos_keys(self):
        t = set()
        for staff in self.m_score.m_staffs:
            [t.add(timepos) for timepos in staff.get_timeposes()]
        return sorted(t)
    def get_midi_events(self, start=None, end=None):
        kv = self.get_all_tpos_keys()
        if start is None and end is not None:
            return self._generate_midi_events(
                [i for i in self.get_all_tpos_keys() if i < end], Track)
        elif start is not None and end is None:
            return self._generate_midi_events(
                [i for i in self.get_all_tpos_keys() if i >= start], Track)
        elif start is None and end is None:
            return self._generate_midi_events(self.get_all_tpos_keys(), Track)
        else:
            assert start is not None and end is not None
            return self._generate_midi_events(
                [i for i in self.get_all_tpos_keys() if start <= i < end], Track)
    def get_first_beat_midi_events(self):
        return self._generate_midi_events([self.get_all_tpos_keys()[0]], Track)
    def get_last_beat_midi_events(self):
        return self._generate_midi_events([self.get_all_tpos_keys()[-1]], Track)
    def get_midi_events_as_percussion(self):
        return self._generate_midi_events(self.get_all_tpos_keys(), PercussionTrack)
    def _generate_midi_events(self, kv, tracktype):
        """
        kv is a list of rat.Rat that tell the timepos for all the tones
        we should generate midi events for.
        Return a list of tracks, one track for each voice.
        """
        track_list = []
        for staff in self.m_score.m_staffs:
            for voice in staff.m_voices:
                track_list.append(self.generate_track_for_voice(voice, kv, tracktype))
        return track_list
    def get_event_dict(self, voice, kv):
        """
        Return a dict that tell us where every note in the track starts
        and stops. The key is a Rat, and the values are a list of tuples.
        Each tuple has thee elements
        (id, 'start|stop-note', midiint)
        """
        ################
        D = {}
        i = 2
        last_timepos = kv[0]
        id_D = {}
        for idx, timepos in enumerate(kv):
            if timepos in voice.m_tdict:
                if not isinstance(voice.m_tdict[timepos]['elem'][0], elems.Note):
                    continue
                for n in sorted(voice.m_tdict[timepos]['elem'],
                        key=operator.attrgetter('m_musicalpitch')):
                    if timepos not in D:
                        D[timepos] = []
                    stop_pos = timepos + n.m_duration.get_rat_value()
                    if stop_pos not in D:
                        D[stop_pos] = []
                    if n.m_tieinfo in ('end', 'go'):
                        for idx, row in enumerate(D[timepos]):
                            if row[2] == int(n.m_musicalpitch):
                                del D[timepos][idx]
                                if not D[timepos]:
                                    del D[timepos]
                                break
                        D[stop_pos].append((i, const.STOP_NOTE, n.m_musicalpitch.semitone_pitch()))
                    else:
                        D[timepos].append((i, const.START_NOTE, n.m_musicalpitch.semitone_pitch()))
                        D[stop_pos].append((i, const.STOP_NOTE, n.m_musicalpitch.semitone_pitch()))
                    i = i + 1
            last_timepos = timepos
        return D
    def generate_track_for_voice(self, voice, kv, tracktype):
        D = self.get_event_dict(voice, kv)
        keys = D.keys()
        keys.sort()
        prev_time = Rat(0)
        ms = tracktype()
        for k in keys:
            delta = None
            if k != Rat(0, 1):
                delta = k-prev_time
            prev_time = k
            for e in D[k]:
                if e[1] == const.START_NOTE:
                    if delta:
                        ms.notelen_time(delta)
                    ms.start_note(e[2], const.DEFAULT_VELOCITY)
                elif e[1] == const.STOP_NOTE:
                    if delta:
                        ms.notelen_time(delta)
                    ms.stop_note(e[2], const.DEFAULT_VELOCITY)
                delta = None
        return ms

def score_to_tracks(score, start=None, end=None):
    return MidiPerformer(score).get_midi_events(start, end)

def score_to_percussion_tracks(score):
    return MidiPerformer(score).get_midi_events_as_percussion()
