# GNU Solfege - eartraining for GNOME
# Copyright (C) 2000, 2001, 2002, 2003  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 2 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import gtk, gnome.ui
import random

import gu, widgets, utils
import mpd, mpd.musicdisplayer, soundcard
import abstract, const


class Teacher(abstract.Teacher):
    OK = 0
    ERR_PICKY = 1
    ERR_CONFIGURE = 2
    def __init__(self, exname, app):
        abstract.Teacher.__init__(self, exname, app)
    def give_up(self):
        self.q_status = const.QSTATUS_GIVE_UP
    def new_question(self):
        """
        Return a true value if a new question was created otherwise false.
        """
        if self.m_timeout_handle:
            gtk.timeout_remove(self.m_timeout_handle)
            self.m_timeout_handle = None

        if self.get_bool('config/picky_on_new_question') \
                 and self.q_status in [const.QSTATUS_NEW, const.QSTATUS_WRONG]:
             return Teacher.ERR_PICKY

        if self.get_string('first_interval_type') in ['harmonic', 'up', 'up/down']:
            first = self.get_list('first_interval_up')
        else:
            first = []
        if self.get_string('first_interval_type') in ['down', 'up/down']:
            first = first + map(lambda a: -a, self.get_list('first_interval_down'))
        if self.get_string('last_interval_type') in ['harmonic', 'up', 'up/down']:
            last = self.get_list('last_interval_up')
        else:
            last = []
        if self.get_string('last_interval_type') in ['down', 'up/down']:
            last = last + map(lambda a: -a, self.get_list('last_interval_down'))
        if not (first and last):
            return self.ERR_CONFIGURE
        self.m_intervals = [random.choice(first), random.choice(last)]
        self.m_tonikas = [mpd.MusicalPitch().randomize("f", "f'"),
                          mpd.MusicalPitch().randomize("f", "f'")]
        self.q_status = const.QSTATUS_NEW
        return self.OK
    def play_question(self):
        if self.q_status == const.QSTATUS_NO:
            return
        low_instr, low_vel, high_instr, high_vel = self.get_instrument_config(2)
        m = soundcard.Track()
        m.set_patch(0, low_instr)
        m.set_patch(1, high_instr)
        if self.get_string('first_interval_type') == 'harmonic':
            m.start_note(0, self.m_tonikas[0], low_vel)
            m.start_note(1, self.m_tonikas[0] + self.m_intervals[0], high_vel)
            m.notelen_time(4)
            m.stop_note(0, self.m_tonikas[0], low_vel)
            m.stop_note(1, self.m_tonikas[0] + self.m_intervals[0], high_vel)
        else:
            m.start_note(0, self.m_tonikas[0], low_vel)
            m.notelen_time(4)
            m.stop_note(0, self.m_tonikas[0], low_vel)
            m.start_note(1, self.m_tonikas[0] + self.m_intervals[0], high_vel)
            m.notelen_time(4)
            m.stop_note(1, self.m_tonikas[0] + self.m_intervals[0], high_vel)
        if self.get_string('last_interval_type') == 'harmonic':
            m.start_note(0, self.m_tonikas[1], low_vel)
            m.start_note(1, self.m_tonikas[1] + self.m_intervals[1], high_vel)
            m.notelen_time(4)
            m.stop_note(0, self.m_tonikas[1], low_vel)
            m.stop_note(1, self.m_tonikas[1] + self.m_intervals[1], high_vel)
        else:
            m.start_note(0, self.m_tonikas[1], low_vel)
            m.notelen_time(4)
            m.stop_note(0, self.m_tonikas[1], low_vel)
            m.start_note(1, self.m_tonikas[1] + self.m_intervals[1], high_vel)
            m.notelen_time(4)
            m.stop_note(1, self.m_tonikas[1] + self.m_intervals[1], high_vel)
        soundcard.synth.play_track(m)
    def guess_answer(self, c):
        """
        argument c:
        -1 : first is largest
         0 : equal
         1 : last is largest
        Return: 1 if correct, 0 if wrong
        """
        a = cmp(abs(self.m_intervals[1]), abs(self.m_intervals[0])) == c
        if a:
            self.maybe_auto_new_question()
            self.q_status = const.QSTATUS_SOLVED
        else:
            self.q_status = const.QSTATUS_WRONG
        return a

class Gui(abstract.Gui):
    def __init__(self, teacher, window):
        abstract.Gui.__init__(self, teacher, window)
        self._ignore_events = 0
        ##############
        # practise_box 
        ##############
        self.practise_box.set_spacing(gnome.ui.PAD)
        hbox = gu.bHBox(self.practise_box)
        self.g_music_displayer = mpd.musicdisplayer.MusicDisplayer(utils.play_tone)
        self.g_music_displayer.clear()
        hbox.pack_start(self.g_music_displayer)

        self.g_flashbar = gu.FlashBar()
        self.g_flashbar.show()
        self.practise_box.pack_start(self.g_flashbar, gtk.FALSE)

        hbox = gu.bHBox(self.practise_box, gtk.FALSE)
        hbox.set_homogeneous(gtk.TRUE)
        self.g_first_is_largest = gu.bButton(hbox, _("F_irst interval\nis largest"), lambda f, self=self: self.guess_answer(-1))
        self.g_first_is_largest.get_child().set_justify(gtk.JUSTIFY_CENTER)
        self.g_first_is_largest.set_sensitive(gtk.FALSE)
        self.g_equal_size = gu.bButton(hbox, _("The intervals\nare _equal"), lambda f, self=self: self.guess_answer(0))
        self.g_equal_size.get_child().set_justify(gtk.JUSTIFY_CENTER)
        self.g_equal_size.set_sensitive(gtk.FALSE)
        self.g_last_is_largest = gu.bButton(hbox, _("_Last interval\nis largest"), lambda f, self=self: self.guess_answer(1))
        self.g_last_is_largest.set_sensitive(gtk.FALSE)
        self.g_last_is_largest.get_child().set_justify(gtk.JUSTIFY_CENTER)
        self.g_new = gu.bButton(self.action_area, _("_New"), self.new_question)
        self.g_repeat = gu.bButton(self.action_area, _("_Repeat"),
             lambda w, self=self: self.m_t.play_question())
        self.g_repeat.set_sensitive(gtk.FALSE)
        self.g_give_up = gu.bButton(self.action_area, _("_Give up"),
                                 self.give_up)
        self.g_give_up.set_sensitive(gtk.FALSE)
        self.action_area.set_homogeneous(gtk.TRUE)
        self.practise_box.show_all()
        ##############
        # config_box #
        ##############
        self.g_instrument_configurator \
                 = widgets.InstrumentConfigurator(self.m_exname, 2)
        self.config_box.pack_start(self.g_instrument_configurator, gtk.FALSE)
        # ------------------------------------------
        self._add_auto_new_question_gui(self.config_box)
        # ----------------------------------------------

        def pack_rdbs(box, callback):
            D = {}
            D['harmonic'] = b = gu.RadioButton(None, _("Harmonic"), callback)
            b.set_data('idir', 'harmonic')
            box.pack_start(b, gtk.FALSE)
            D['up'] = b = gu.RadioButton(b, _("Melodic up"), callback)
            b.set_data('idir', 'up')
            box.pack_start(b, gtk.FALSE)
            D['down'] = b = gu.RadioButton(b, _("Melodic down"), callback)
            b.set_data('idir', 'down')
            box.pack_start(b, gtk.FALSE)
            D['up/down'] = b = gu.RadioButton(b, _("Melodic up/down"), callback)
            b.set_data('idir', 'up/down')
            box.pack_start(b, gtk.FALSE)
            return D
        #---------
        hbox = gu.bHBox(self.config_box, gtk.FALSE)
        hbox.pack_start(gtk.Label(_("First interval:")), gtk.FALSE,
                        padding=gnome.ui.PAD_SMALL)
        self.g_rdbs = [pack_rdbs(hbox, self.update_first)]
        self.g_first_interval_up = widgets.IntervalCheckBox(self.m_exname,
                                                         'first_interval_up')
        self.config_box.pack_start(self.g_first_interval_up, gtk.FALSE)
        self.g_first_interval_down = widgets.IntervalCheckBox(self.m_exname,
                             'first_interval_down')
        self.config_box.pack_start(self.g_first_interval_down, gtk.FALSE)
        #----------
        hbox = gu.bHBox(self.config_box, gtk.FALSE)
        hbox.pack_start(gtk.Label(_("Last interval:")), gtk.FALSE,
                        padding=gnome.ui.PAD_SMALL)
        self.g_rdbs.append (pack_rdbs(hbox, self.update_last))
        self.g_last_interval_up = widgets.IntervalCheckBox(self.m_exname,
                     'last_interval_up')
        self.config_box.pack_start(self.g_last_interval_up, gtk.FALSE)
        self.g_last_interval_down = widgets.IntervalCheckBox(self.m_exname,
                        'last_interval_down')
        self.config_box.pack_start(self.g_last_interval_down, gtk.FALSE)
        #------------
        self.g_rdbs[0][self.get_string('first_interval_type')].set_active(gtk.TRUE)
        self.update_first(self.g_rdbs[0][self.get_string('first_interval_type')])
        self.g_rdbs[1][self.get_string('last_interval_type')].set_active(gtk.TRUE)
        self.update_last(self.g_rdbs[1][self.get_string('last_interval_type')])
        self.config_box.show_all()
        self.add_watch('first_interval_type', self._watch_1_cb)
        self.add_watch('last_interval_type', self._watch_2_cb)
    def _watch_1_cb(self, name):
        self._ignore_events = 1
        self.g_rdbs[0][self.get_string('first_interval_type')].set_active(1)
        self.g_first_interval_up.set_sensitive(
                  self.get_string('first_interval_type') \
                    in ['harmonic', 'up', 'up/down'])
        self.g_first_interval_down.set_sensitive(
                self.get_string('first_interval_type') in ['up/down', 'down'])
        self._ignore_events = 0
    def _watch_2_cb(self, name):
        self._ignore_events = 1
        self.g_rdbs[1][self.get_string('last_interval_type')].set_active(1)
        self.g_last_interval_up.set_sensitive(
                 self.get_string('last_interval-type') \
                   in ['harmonic', 'up', 'up/down'])
        self.g_last_interval_down.set_sensitive(
                self.get_string('last_interval_type') in ['up/down', 'down'])
        self._ignore_events = 0
    def update_first(self, button):
        if self._ignore_events:
            return
        self.set_string('first_interval_type', button.get_data('idir'))
        self.g_first_interval_up.set_sensitive(button.get_data('idir') in ['harmonic', 'up', 'up/down'])
        self.g_first_interval_down.set_sensitive(button.get_data('idir') in ['up/down', 'down'])
    def update_last(self, button):
        if self._ignore_events:
            return
        self.set_string('last_interval_type', button.get_data('idir'))
        self.g_last_interval_up.set_sensitive(button.get_data('idir') in ['harmonic', 'up', 'up/down'])
        self.g_last_interval_down.set_sensitive(button.get_data('idir') in ['up/down', 'down'])
    def guess_answer(self, g):
        if self.m_t.q_status == const.QSTATUS_NO:
            return
        if self.m_t.q_status == const.QSTATUS_SOLVED:
            if self.m_t.guess_answer(g):
                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"))
            return
        if self.m_t.q_status != const.QSTATUS_GIVE_UP:
            if self.m_t.guess_answer(g):
                self.g_new.set_sensitive(gtk.TRUE)
                self.g_new.grab_focus()
                self.show_intervals()
                self.g_flashbar.flash(_("Correct"))
                self.g_give_up.set_sensitive(gtk.FALSE)
                self.g_first_is_largest.set_sensitive(gtk.FALSE)
                self.g_equal_size.set_sensitive(gtk.FALSE)
                self.g_last_is_largest.set_sensitive(gtk.FALSE)
            else:
                self.g_flashbar.flash(_("Wrong"))
                if self.get_bool("config/auto_repeat_question_if_wrong_answer"):
                    self.m_t.play_question()
                self.g_give_up.set_sensitive(gtk.TRUE)
    def give_up(self, widget=None):
        if self.m_t.q_status == const.QSTATUS_WRONG:
            self.m_t.give_up()
            self.g_first_is_largest.set_sensitive(gtk.FALSE)
            self.g_equal_size.set_sensitive(gtk.FALSE)
            self.g_last_is_largest.set_sensitive(gtk.FALSE)
            self.g_give_up.set_sensitive(gtk.FALSE)
            if self.m_t.m_intervals[0] < self.m_t.m_intervals[1]:
                s = _("Last interval is largest")
            elif self.m_t.m_intervals[0] == self.m_t.m_intervals[1]:
                s = _("The intervals are equal")
            else:
                s = _("First interval is largest")
            self.g_flashbar.push(_("The answer is: %s") % s)
            self.g_new.set_sensitive(gtk.TRUE)
            self.g_new.grab_focus()
            self.show_intervals()
    def show_intervals(self, widget=None):
        tv = [self.get_string('first_interval_type'),
                self.get_string('last_interval_type')]
        clefs = {}
        music = {}
        for x in range(2):
            music[x] = self.m_t.m_tonikas[x].get_octave_notename() + " " \
                + (self.m_t.m_tonikas[x] + mpd.Interval().set_from_int(self.m_t.m_intervals[x])).get_octave_notename()
            clefs[x] = mpd.select_clef(music[x])
            if tv[x] == 'harmonic':
                music[x] = "< %s >" % music[x]
        m = r"\staff{ \clef %s %s |" % (clefs[0], music[0])
        if clefs[0] != clefs[1]:
            m = m + r"\clef %s " % clefs[1]
        m = m + music[1]
        self.g_music_displayer.display(m,
              self.get_int('config/feta_font_size=20'))
    def new_question(self, widget=None):
        self.g_music_displayer.clear()
        q = self.m_t.new_question()
        if q == Teacher.OK:
            self.m_t.play_question()
            self.g_first_is_largest.set_sensitive(gtk.TRUE)
            self.g_first_is_largest.grab_focus()
            self.g_equal_size.set_sensitive(gtk.TRUE)
            self.g_last_is_largest.set_sensitive(gtk.TRUE)
            self.g_new.set_sensitive(
                not self.get_bool('config/picky_on_new_question'))
            self.g_repeat.set_sensitive(gtk.TRUE)
            self.g_give_up.set_sensitive(gtk.FALSE)
            self.g_flashbar.clear()
        elif q == Teacher.ERR_PICKY:
            self.g_flashbar.flash(_("You have to solve this question first."))
        else:
            assert q == Teacher.ERR_CONFIGURE
            self.g_flashbar.flash(_("You have to configure the exercise properly"))
    def on_start_practise(self):
        gtk.timeout_add(const.SHORT_WAIT, lambda self=self:
            self.g_flashbar.flash(_("Click 'New' to begin.")))
        self.g_new.grab_focus()
    def on_end_practise(self):
        self.m_t.end_practise()
        self.g_first_is_largest.set_sensitive(gtk.FALSE)
        self.g_equal_size.set_sensitive(gtk.FALSE)
        self.g_last_is_largest.set_sensitive(gtk.FALSE)
        self.g_repeat.set_sensitive(gtk.FALSE)
        self.g_give_up.set_sensitive(gtk.FALSE)
        self.g_new.set_sensitive(gtk.TRUE)
        self.g_music_displayer.clear()
        self.g_flashbar.clear()

