# GNU Solfege - free ear training software
# Copyright (C) 2010  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 random

import gtk

from solfege import gu
from solfege import abstract
from solfege import gu
from solfege import lessonfile
from solfege import soundcard
from solfege import utils

from solfege.mpd.parser import Lexer
from solfege.mpd.duration import Duration
from solfege.mpd.rat import Rat
from solfege.lessonfile import RhythmStaff, Bar
from solfege.rhythmwidget import RhythmWidget, Controller

def rat_len_of_digits(digits):
    """
    Return a Rat representing the length of the digits.
    "4 8 8" returns Rat(2, 4)
    """
    ret = Rat(0, 1)
    for d in digits.split():
        ret += Duration.new_from_string(d).get_rat_value()
    return ret


class Teacher(abstract.Teacher):
    ERR_PICKY = 1
    OK = 2
    def __init__(self, exname):
        abstract.Teacher.__init__(self, exname)
        self.lessonfileclass = lessonfile.HeaderLessonfile
    def new_question(self):
        """
        We will create a timelist of the question when we create the
        question, and compare it to the RhythmStaff.get_timelist.
        We will also create a  PercussionTrack that will be used when
        we play the question.
        """
        if self.get_bool('config/picky_on_new_question') \
                and self.q_status in [self.QSTATUS_NEW, self.QSTATUS_WRONG]:
            return self.ERR_PICKY
        self.q_status = self.QSTATUS_NEW
        self.m_timesig = Rat(4, 4)
        self.m_numbar = 2
        question = []
        cur_bar_len = Rat(0, 1)
        bar_count = 0
        # Here we try to find the shortest element in header.elements
        shortest = None
        for elem in self.m_P.header.elements:
            c = rat_len_of_digits(elem)
            if shortest is None or c < shortest:
                shortest = c
        # If the shortest element is longer than half a bar, then we will
        # have problems filling bars completely.
        if shortest * 2 > self.m_timesig:
            raise lessonfile.LessonfileException("shortest element > half a bar")
        # Create the question
        while bar_count < self.m_numbar:
            elem = random.choice(self.m_P.header.elements)
            d = rat_len_of_digits(elem)
            if (cur_bar_len + d == self.m_timesig
                or cur_bar_len + d + shortest <= self.m_timesig):
                question.append(elem)
                cur_bar_len += d
                if cur_bar_len == self.m_timesig:
                    cur_bar_len = Rat(0, 1)
                    bar_count += 1
        self.m_question = question
        self.m_track = self.generate_question_track(question)
        self.m_staff = self.get_empty_staff()
        return self.OK
    def generate_question_track(self, question):
        """
        Return a track that will play the rhythm the user should enter.
        """
        track = utils.new_percussion_track()
        for s in question:
            for i in s.split():
                track.note(Duration.new_from_string(i.strip()).get_rat_value(), 37)
        return track
    def play_question(self):
        soundcard.synth.play_track(self.m_track)
    def get_empty_staff(self):
        """
        Return an empty staff with the correct numbers of bars required
        to answer the current question.
        """
        staff = RhythmStaff()
        for x in range(self.m_numbar):
            staff.append(Bar(self.m_timesig))
        return staff
    def get_answer_staff(self):
        """
        Return a RhythmStaff that displays the question being asked.
        """
        staff = RhythmStaff()
        for elem in self.m_question:
            for i in elem.split():
                d = Duration.new_from_string(i.strip())
                if not staff or staff[-1].isfull():
                    staff.append(Bar(self.m_timesig))
                staff[-1].add_note(d)
        return staff
    def guess_answer(self, staff):
        assert self.q_status not in (self.QSTATUS_NO, self.QSTATUS_GIVE_UP)
        if self.m_staff.get_timelist() == self.get_question_timelist():
            self.q_status = self.QSTATUS_SOLVED
            return True
        else:
            self.q_status = self.QSTATUS_WRONG
            return False
    def get_question_timelist(self):
        """
        Return a  timelist (see lessonfile.py: _get_timelist(..)
        of the question being asked.
        """
        ret = []
        for s in self.m_question:
            for i in s.split():
                i = i.strip()
                ret.append([True, Duration.new_from_string(i.strip()).get_rat_value()])
        return ret
    def give_up(self):
        self.q_status = self.QSTATUS_GIVE_UP


class Gui(abstract.LessonbasedGui):
    def __init__(self, teacher):
        abstract.LessonbasedGui.__init__(self, teacher)
        self.g_w = RhythmWidget()
        self.practise_box.pack_start(self.g_w, False)
        self.g_c = Controller(self.g_w)
        self.practise_box.pack_start(self.g_c, False)
        self.g_flashbar = gu.FlashBar()
        self.practise_box.pack_start(self.g_flashbar, False)
        self.g_flashbar.show()
        self.std_buttons_add(
            ('new', self.new_question),
            ('guess_answer', self.guess_answer),
            ('repeat', self.repeat_question),
            ('give_up', self.give_up))
        self.g_w.show()
    def new_question(self, *w):
        def exception_cleanup():
            self.m_t.q_status = self.m_t.QSTATUS_NO
            self.std_buttons_exception_cleanup()
        try:
            g = self.m_t.new_question()
            if g == self.m_t.OK:
                self.m_t.play_question()
                self.std_buttons_new_question()
                self.g_w.grab_focus()
                self.g_w.set_staff(self.m_t.m_staff)
                self.g_c.set_editable(True)
        except Duration.BadStringException, e:
            gu.dialog_ok("Lesson file error", secondary_text=u"Bad rhythm string in the elements variable of the lessonfile. Only digits and dots expected: %s" % unicode(e))
            exception_cleanup()
        except Exception, e:
            if not self.standard_exception_handler(e, exception_cleanup):
                raise
    def guess_answer(self, *w):
        if self.m_t.q_status == Teacher.QSTATUS_SOLVED:
            if self.m_t.guess_answer(self.g_w.m_staff):
                self.g_flashbar.flash(_("Correct, but you have already solved this question"))
            else:
                self.g_flashbar.flash(_("Wrong, but you have already solved this question"))
        else:
            if self.m_t.guess_answer(self.g_w.m_staff):
                self.g_flashbar.flash(_("Correct"))
                self.std_buttons_answer_correct()
            else:
                self.g_flashbar.flash(_("Wrong"))
                self.std_buttons_answer_wrong()
                self.g_w.grab_focus()
    def repeat_question(self, *w):
        self.g_w.grab_focus()
        self.m_t.play_question()
    def give_up(self, *w):
        self.g_w.set_staff(self.m_t.get_answer_staff())
        self.m_t.give_up()
        self.g_c.set_editable(False)
        self.std_buttons_give_up()
    def on_start_practise(self):
        super(Gui, self).on_start_practise()
        self.std_buttons_start_practise()
        self.g_c.set_editable(False)
        self.g_flashbar.delayed_flash(self.short_delay,
            _("Click 'New' to begin."))
    def on_end_practise(self):
        super(Gui, self).on_end_practise()
        self.g_w.set_staff(RhythmStaff())
