# 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 inputwidgets, abstract, const, random, soundcard, gu, mpd, gnome
import configureoutput
import gtk
import gu
import statistics, statisticsviewer

class Teacher(abstract.Teacher):
    OK, ERR_PICKY, ERR_TONES = range(3)
    ERR_PICKY = 1
    ERR_CONFIG = 2
    OCTAVES = [-2, -1, 0, 1, 2, 3]
    def __init__(self, exname, app):
        abstract.Teacher.__init__(self, exname, app)
        self.m_statistics = statistics.Statistics(self)
        self.m_ask_tones =   {}
        self.m_question = None
    def new_question(self):
        """
        Return values:
        OK: sucess, new random tone selected
        ERR_PICKY: fail, you are not allowed to select a new tone before you
                   can identify the one you have now.
        ERR_CONFIG: fail, all notes have zero weight or no octaves selected
        """
        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) \
                and (not self.get_bool('one_chance_only')):
            return Teacher.ERR_PICKY

        v = []
        for n in ['c', 'cis', 'd', 'dis', 'e', 'f', 'fis',
                  'g', 'gis', 'a', 'ais', 'b']:
            v.extend([n] * self.get_int(n+"_weight"))
        if not v:
            return self.ERR_CONFIG
        self.m_question = random.choice(v)
        v = []
        for n in self.OCTAVES:
            if self.get_bool("octave"+str(n)):
                v.append(n)
        if not v:
            return self.ERR_CONFIG
        self.m_octave = random.choice(v)
        self.q_status = const.QSTATUS_NEW
        return self.OK
    def guess_answer(self, notename):
        if notename == self.m_question:
            if self.q_status == const.QSTATUS_NEW:
                self.m_statistics.add_correct(notename)
            self.maybe_auto_new_question()
            self.q_status = const.QSTATUS_SOLVED
            return 1
        else:
            if self.q_status == const.QSTATUS_NEW:
                self.m_statistics.add_wrong(self.m_question, notename)
                self.q_status = const.QSTATUS_WRONG
    def play_question(self):
        if self.q_status == const.QSTATUS_NO:
            return
        soundcard.play_note(self.get_int('config/preferred_instrument'), 4, 0, 
             mpd.notename_to_int(self.m_question)+self.m_octave*12,
             self.get_int('config/preferred_instrument_velocity'))
    def give_up(self):
        self.m_statistics.reset_session()
        self.q_status = const.QSTATUS_GIVE_UP
    def spank_me_play_question(self):
        track = soundcard.Track()
        track.note(8, 9, 71, self.get_int('config/preferred_instrument_velocity'))
        track.set_patch(0, self.get_int('config/preferred_instrument'))
        track.note(4, 0, mpd.notename_to_int(self.m_question)+self.m_octave*12, self.get_int('config/preferred_instrument_velocity'))
        soundcard.synth.play_track(track)
    def spank_me(self):
        soundcard.play_note(0, 4, 9, 71, self.get_int('config/preferred_instrument_velocity'))

class Gui(abstract.Gui):
    def __init__(self, teacher, window):
        abstract.Gui.__init__(self, teacher, window)
        if not configureoutput.WITH_GNOME:
            self.practise_box.add(gtk.Label(_(
"""This exercise don't work without GNOME.
It can pretty easy be converted to use Pixmap instead
of GnomeCanvas, but I (tca) don't have time to do this
right now. But I can answer questions if needed.""")))
            self.practise_box.show_all()
            return
        v = []
        for k in ('c', 'cis', 'd', 'dis', 'e', 'f', 'fis',
                  'g', 'gis', 'a', 'ais', 'b'):
            self.m_key_bindings['tone_%s_ak' % k] \
                = lambda self=self, k=k: self.on_answer_from_user(k)
            v.append(self.get_string('tone_%s_ak' % k))

        self.g_percentage = gu.bLabel(self.practise_box, "")
        self.g_percentage.set_name("Heading1")
        self.g_piano = inputwidgets.PianoOctaveWithAccelName(
                       self.on_answer_from_user, v)
        self.practise_box.pack_start(self.g_piano)

        self.g_flashbar = gu.FlashBar()
        self.g_flashbar.show()
        self.practise_box.pack_start(self.g_flashbar, gtk.FALSE)
        self.practise_box.set_spacing(gnome.ui.PAD)

        self.g_new_tone = gu.bButton(self.action_area, _("_New tone"),
                                     self.new_question)
        self.g_repeat = gu.bButton(self.action_area, _("_Repeat"),
                  lambda _o, 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.practise_box.show_all()
        ##############
        # config_box #
        ##############
        self.config_box.set_spacing(gnome.ui.PAD_SMALL)
        table = gtk.Table()
        table.set_border_width(gnome.ui.PAD_SMALL)
        frame = gtk.Frame(_("Weight"))
        self.config_box.pack_start(frame, gtk.FALSE)
        frame.add(table)
        for x, n in [(1, 'cis'), (3, 'dis'), (7, 'fis'),
                     (9, 'gis'), (11, 'ais')]:
             label = gtk.Label(mpd.MusicalPitch.new_from_notename(n).get_user_notename())
             label.set_name("Heading2")
             label.set_alignment(0.2, 1.0)
             table.attach(label, x, x+2, 0, 1, xoptions=gtk.FILL)
             b = gu.nSpinButton(self.m_exname, n+"_weight",
                      gtk.Adjustment(1, 0, 1000, 1, 10), digits=0)
             table.attach(b, x, x+2, 1, 2, xoptions=gtk.FILL)
        for x, n in [(0, 'c'), (2, 'd'), (4, 'e'), (6, 'f'),
                      (8, 'g'), (10, 'a'), (12, 'b')]:
             label = gtk.Label(mpd.MusicalPitch.new_from_notename(n).get_user_notename())
             label.set_name("Heading2")
             label.set_alignment(0.35, 1.0)
             table.attach(label, x, x+2, 2, 3, xoptions=gtk.FILL)
             b = gu.nSpinButton(self.m_exname, n+"_weight",
                   gtk.Adjustment(1, 0, 1000, 1, 10), digits=0)
             table.attach(b, x, x+2, 3, 4, xoptions=gtk.FILL)

        hbox = gu.bHBox(self.config_box, gtk.FALSE)
        hbox.pack_start(gtk.Label(_("Octave:")), gtk.FALSE, padding=4)
        for oct in self.m_t.OCTAVES:
            b = gu.nCheckButton(self.m_exname, "octave"+str(oct), str(oct),
                                default_value=1)
            hbox.pack_start(b, gtk.FALSE)
        #############
        self._add_auto_new_question_gui(self.config_box)
        #############
        frame = gtk.Frame(_("When you guess wrong"))
        vbox = gtk.VBox()
        vbox.set_border_width(gnome.ui.PAD_SMALL)
        frame.add(vbox)
        vbox.pack_start(gu.nCheckButton(self.m_exname,
                    "warning_sound", _("Play warning sound")))
        vbox.pack_start(gu.nCheckButton(self.m_exname, "one_chance_only",
                  _("Only one chance to get the answer right")))
        self.config_box.pack_start(frame, gtk.FALSE)
        self.config_box.show_all()
        ##############
        # statistics #
        ##############
        self.setup_statisticsviewer(statisticsviewer.StatisticsViewer,
                                   _("Identify tone"))
    def new_question(self, widget=None):
        s = self.m_t.q_status
        g = self.m_t.new_question()
        if g == Teacher.ERR_CONFIG:
            self.g_win.display_error_message(
_("""You have to select some tones practise. Do this on the config page by setting the weight of tones to a value greater than zero."""))
            return
        elif g == Teacher.OK:
            self.g_new_tone.set_sensitive(
                  not self.get_bool('config/picky_on_new_question'))
            self.g_repeat.set_sensitive(gtk.TRUE)
            if s in (const.QSTATUS_NO, const.QSTATUS_GIVE_UP):
                self.g_flashbar.flash(_("First tone is %s")
                                      % self.m_t.m_question)
            self.g_flashbar.clear()
            self.m_t.play_question()
        self.set_percentage_label()
    def on_answer_from_user(self, notename):
        if self.m_t.q_status == const.QSTATUS_NO:
            self.g_flashbar.flash(_("Click 'New tone' to begin."))
            return
        elif self.m_t.q_status == const.QSTATUS_SOLVED:
            if self.m_t.guess_answer(notename):
                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"))
        elif self.m_t.q_status in (const.QSTATUS_NEW, const.QSTATUS_WRONG):
            if self.m_t.guess_answer(notename):
                self.g_flashbar.flash(_("Correct"))
                self.g_new_tone.set_sensitive(gtk.TRUE)
                self.g_give_up.set_sensitive(gtk.FALSE)
            else:
                self.g_flashbar.flash(_("Wrong"))
                if self.get_bool('one_chance_only'):
                    self.m_t.new_question()
                    if self.get_bool("warning_sound"):
                        self.m_t.spank_me_play_question()
                    else:
                        self.m_t.play_question()
                else:
                    self.g_give_up.set_sensitive(gtk.TRUE)
                    if self.get_bool("warning_sound"):
                        if self.get_bool("config/auto_repeat_question_if_wrong_answer"):
                            self.m_t.spank_me_play_question()
                        else:
                            self.m_t.spank_me()
                    else:
                        if self.get_bool("config/auto_repeat_question_if_wrong_answer"):
                            self.m_t.play_question()
        self.set_percentage_label()
    def give_up(self, _o=None):
        if self.m_t.q_status == const.QSTATUS_WRONG:
            self.g_flashbar.push(_("The answer is: %s")
                % self.m_t.m_question)
            self.m_t.give_up()
            self.g_new_tone.set_sensitive(gtk.TRUE)
            self.g_new_tone.grab_focus()
            self.g_give_up.set_sensitive(gtk.FALSE)
    def set_percentage_label(self):
        self.g_percentage.set_text("%.1f %%" % (self.m_t.m_statistics.get()*100))
    def on_start_practise(self):
        if not configureoutput.WITH_GNOME:
            return # this exercise don't work without GnomeCanvas
        self.m_t.m_statistics.reset_session()
        self.set_percentage_label()
        gtk.timeout_add(const.SHORT_WAIT, lambda self=self:
            self.g_flashbar.flash(_("Click 'New tone' to begin.")))
        self.g_new_tone.set_sensitive(gtk.TRUE)
        self.g_new_tone.grab_focus()
        self.g_repeat.set_sensitive(gtk.FALSE)
        self.g_give_up.set_sensitive(gtk.FALSE)
        self.m_t.q_status = const.QSTATUS_NO
    def on_end_practise(self):
        self.m_t.end_practise()
        if configureoutput.WITH_GNOME:
            self.g_repeat.set_sensitive(gtk.FALSE)
