# 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 random

import gtk, gnome
import abstract, gu, const, mpd, mpd.musicdisplayer
import utils
import statistics, statisticsviewer

scaledata = {
    'ionian': ('[c8 d][e f][g a][b c]|[c b][a g][f e][d c]',
               _("Ionian"), 'c', 0, 0),
    'dorian': ('[c8 d][es f][g a][bes c]|[c bes][a g][f es][d c]',
               _("Dorian"), 'bes', 1, 0),
    'phrygian': ('[c8 des][es f][g as][bes c]|[c bes][as g][f es][des c]',
                _("Phrygian"), 'as', 2, 0),
    'lydian': ('[c8 d][e fis][g a][b c]|[c b][a g][fis e][d c]',
               _("Lydian"), 'g', 3, 0),
    'mixolydian': ('[c8 d][e f][g a][bes c]|[c bes][a g][f e][d c]',
                   _("Mixolydian"), 'f', 0, 1),
    'aeolian': ('[c8 d][es f][g as][bes c]|[c bes][as g][f es][d c]',
                _("Aeolian"), 'es', 1, 1),
    'locrian': ('[c8 des][es f][ges as][bes c]|[c bes][as ges][f es][des c]',
                _("Locrian"), 'des', 2, 1),
    'harmonic_minor': ('[c8 d][es f][g as][b c]|[c b][as g][f es][d c]',
                       _("Harmonic minor"), 'es', 3, 1),
    'melodic_minor': ('[c8 d][es f][g a][b c]|[c bes][as g][f es][d c]',
                      _("Melodic minor"), 'es', 0, 2)
}
class Teacher(abstract.Teacher):
    OK = 0
    ERR_NO_SCALES = 1
    ERR_PICKY = 2
    def scale_key_to_name(self, key):
        return scaledata[key][1]
    def __init__(self, exname, app):
        abstract.Teacher.__init__(self, exname, app)
        self._scale = None
        self.m_statistics = statistics.Statistics(self)
        self.m_statistics.key_to_pretty_name = self.scale_key_to_name
    def give_up(self):
        self.q_status = const.QSTATUS_GIVE_UP
    def new_scale(self):
        """Return values:
        OK
        ERR_NO_SCALES
        ERR_PICKY
        """
        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
        self.q_status = const.QSTATUS_NO
        active_scales = []
        for s in scaledata.keys():
            if self.get_bool(s):
                active_scales.append(s)
        if active_scales:
            self._scale = random.choice(active_scales)
            self.m_tonika = mpd.MusicalPitch().randomize("f", "c''")
            self.q_status = const.QSTATUS_NEW
            return self.OK
        else:
            return self.ERR_NO_SCALES
    def get_mpd_string(self):
        assert self._scale
        return r"\staff\transpose %s\relative c'{ \clef violin \key %s \major %s }" \
                   % (self.m_tonika.get_octave_notename(), scaledata[self._scale][2], scaledata[self._scale][0])
    def play(self):
        if self.q_status == const.QSTATUS_NO:
            return
        if self.get_bool('override_global_settings'):
            bpm = self.get_int('default_bpm')
        else:
            bpm = self.get_int('config/default_bpm')
        mpd.play_music(self.get_mpd_string(), bpm,
                       self.get_int('config/preferred_instrument'),
                       self.get_int('config/preferred_instrument_velocity'))
    def play_slowly(self):
        if self.q_status == const.QSTATUS_NO:
           return
        if self.get_bool('override_global_settings'):
            bpm = self.get_int('slow_bpm')
        else:
            bpm = self.get_int('config/slow_bpm')
        mpd.play_music(self.get_mpd_string(), bpm,
                       self.get_int('config/preferred_instrument'),
                       self.get_int('config/preferred_instrument_velocity'))
    def guess_answer(self, answer):
        """
        Return: 1 if correct, None if not
        """
        if answer == self._scale:
            if self.q_status == const.QSTATUS_NEW:
                self.m_statistics.add_correct(self._scale)
            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._scale, answer)
                self.q_status = const.QSTATUS_WRONG
    def configure_exercise(self, x, y, dict):
        for k in dict.keys():
            if k == 'scales':
                continue
            self.set_string(k, dict[k])
        if dict.has_key('scales'):
            scales = eval(dict['scales'])
            for s in scaledata.keys():
                self.set_bool(s, s in scales)

class Gui(abstract.Gui):
    def __init__(self, teacher, window):
        abstract.Gui.__init__(self, teacher, window)
        ################
        # practise_box #
        ################
        self.g_score_displayer = mpd.musicdisplayer.MusicDisplayer(utils.play_tone)
        self.practise_box.pack_start(self.g_score_displayer)

        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.table = gtk.Table(3, 3, gtk.TRUE)
        self.practise_box.pack_start(self.table, gtk.FALSE)
        self.g_scale_buttons = {}
        for k in scaledata.keys():
            self.g_scale_buttons[k] = b = gtk.Button(scaledata[k][1])
            b.connect('clicked', self.on_scale_click, k)
            self.table.attach(b, scaledata[k][3], scaledata[k][3]+1,
                              scaledata[k][4], scaledata[k][4]+1)
        self.g_new = gu.bButton(self.action_area, _("_New scale"),
                                   self.new_question)
        self.g_repeat = gu.bButton(self.action_area, _("_Repeat"),
                                   self.repeat_question)
        self.g_repeat.set_sensitive(gtk.FALSE)
        self.g_repeat_slowly = gu.bButton(self.action_area, _("Repeat s_lowly"),
                                          self.repeat_question_slowly)
        self.g_repeat_slowly.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.set_spacing(gnome.ui.PAD)
        self.practise_box.show_all()
        ##############
        # config_box #
        ##############
        self.config_box.set_spacing(gnome.ui.PAD)
        frame = gtk.Frame(_("Ask for these scales"))
        self.config_box.pack_start(frame, gtk.FALSE)
        table = gtk.Table(4, 3)
        table.set_border_width(gnome.ui.PAD_SMALL)
        frame.add(table)
        for k in scaledata.keys():
            b = gu.nCheckButton(self.m_exname, k, scaledata[k][1], default_value=1)
            table.attach(b, scaledata[k][3], scaledata[k][3]+1,
                         scaledata[k][4], scaledata[k][4]+1)
        frame = gtk.Frame()
        self.config_box.pack_start(frame, gtk.FALSE)
        vbox = gtk.VBox()
        vbox.set_border_width(gnome.ui.PAD)
        frame.add(vbox)
        self.g_override_global_settings = \
               gu.nCheckButton(self.m_exname, 'override_global_settings',
                            _("Override global settings"), callback=self.update_config_override)
        frame.set_label_widget(self.g_override_global_settings)
        self.mt = table = gtk.Table()
        vbox.pack_start(table, gtk.FALSE)
        gu.tLabel(table, 0, 1, 0, 1, _("Slow bpm:"),
                  xalign=1.0, xoptions=gtk.FILL, xpadding=gnome.ui.PAD)
        gu.tLabel(table, 0, 1, 1, 2, _("Default bpm:"),
                  xalign=1.0, xoptions=gtk.FILL, xpadding=gnome.ui.PAD)
        table.attach(gu.nSpinButton(self.m_exname, 'slow_bpm',
                               gtk.Adjustment(0, 10, 255, 1, 10), digits=0),
                     1, 2, 0, 1, xoptions=0)
        table.attach(gu.nSpinButton(self.m_exname, 'default_bpm',
                               gtk.Adjustment(0, 10, 255, 1, 10), digits=0),
                     1, 2, 1, 2, xoptions=0)
        self.update_config_override()
        # ------------------------------------------
        self._add_auto_new_question_gui(self.config_box)
        # ----------------------------------------------

        self.config_box.show_all()
        ###############
        # statistics
        ###############
        self.setup_statisticsviewer(statisticsviewer.StatisticsViewer,
                                   _("Identify scale"))
    def update_config_override(self, _o=None):
        if self.g_override_global_settings.get_active():
            self.mt.set_sensitive(gtk.TRUE)
        else:
            self.mt.set_sensitive(gtk.FALSE)
    def give_up(self, widget=None):
        if self.m_t.q_status == const.QSTATUS_WRONG:
            fontsize = self.get_int('config/feta_font_size=20')
            self.g_score_displayer.display(self.m_t.get_mpd_string(), fontsize)
            self.m_t.give_up()
            self.g_new.set_sensitive(gtk.TRUE)
            self.g_new.grab_focus()
            self.g_give_up.set_sensitive(gtk.FALSE)
            self.g_flashbar.push(_("The answer is: %s") % self.m_t.scale_key_to_name(self.m_t._scale))
    def new_question(self, widget=None):
        self.g_score_displayer.clear()
        g = self.m_t.new_scale()
        if g == Teacher.OK:
            self.m_t.play()
            self.g_repeat_slowly.set_sensitive(gtk.TRUE)
            self.g_repeat.set_sensitive(gtk.TRUE)
            self.g_give_up.set_sensitive(gtk.FALSE)
            self.g_new.set_sensitive(
                     not self.get_bool('config/picky_on_new_question'))
            self.g_scale_buttons['ionian'].grab_focus()
            self.g_flashbar.clear()
        elif g == Teacher.ERR_NO_SCALES:
            self.g_flashbar.flash(_("You have to select some scales to practise."))
            self.g_give_up.set_sensitive(gtk.FALSE)
            self.g_repeat_slowly.set_sensitive(gtk.FALSE)
            self.g_repeat.set_sensitive(gtk.FALSE)
            self.g_flashbar.clear()
        else:
            assert g == Teacher.ERR_PICKY
            self.g_flashbar.flash(_("You have to solve this question first."))
    def repeat_question(self, *w):
        self.m_t.play()
        self.g_scale_buttons['ionian'].grab_focus()
    def repeat_question_slowly(self, *w):
        self.m_t.play_slowly()
        self.g_scale_buttons['ionian'].grab_focus()
    def on_start_practise(self):
        self.m_t.m_statistics.reset_session()
        self.g_new.grab_focus()
        self.g_score_displayer.clear()
        gtk.timeout_add(const.SHORT_WAIT, lambda self=self:
           self.g_flashbar.flash(_("Click 'New scale' to begin.")))
    def on_end_practise(self):
        self.m_t.end_practise()
        self.g_new.set_sensitive(gtk.TRUE)
        self.g_repeat.set_sensitive(gtk.FALSE)
        self.g_repeat_slowly.set_sensitive(gtk.FALSE)
        self.g_give_up.set_sensitive(gtk.FALSE)
        self.g_flashbar.clear()
    def on_scale_click(self, button, name):
        if self.m_t.q_status == const.QSTATUS_GIVE_UP:
            return
        if self.m_t.q_status == const.QSTATUS_NO:
            self.g_flashbar.flash(_("Click 'New scale' to begin."))
            return
        if self.m_t.q_status == const.QSTATUS_SOLVED:
            if self.m_t.guess_answer(name):
                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(name):
                self.g_flashbar.flash(_("Correct"))
                self.g_new.set_sensitive(gtk.TRUE)
                self.g_new.grab_focus()
                self.g_give_up.set_sensitive(gtk.FALSE)
                fontsize = self.get_int('config/feta_font_size=20')
                self.g_score_displayer.display(self.m_t.get_mpd_string(), fontsize)
            else:
                self.g_flashbar.flash(_("Wrong"))
                if self.get_bool("config/auto_repeat_question_if_wrong_answer"):
                    self.m_t.play()
                self.g_give_up.set_sensitive(gtk.TRUE)

