# GNU Solfege - free ear training software
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 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 division
import gtk, gobject
import abstract, gu
import lessonfile
import const

class Teacher(abstract.Teacher, abstract.RhythmAddOnClass):
    OK = 0
    ERR_PICKY = 1
    ERR_NO_ELEMS = 2
    def __init__(self, exname, app):
        abstract.Teacher.__init__(self, exname, app)
        self.lessonfileclass = lessonfile.HeaderLessonfile
    def play_question(self):
        if self.q_status == self.QSTATUS_NO:
            return
        self.play_rhythm(self.get_music_string())
    def guess_answer(self, a):
        assert self.q_status in [self.QSTATUS_NEW, self.QSTATUS_WRONG]
        v = []
        for idx in range(len(self.m_question)):
            v.append(self.m_question[idx] == a[idx])
        if not [x for x in v if x == 0]:
            self.q_status = self.QSTATUS_SOLVED
            self.maybe_auto_new_question()
            self.m_app.play_happy_sound()
            return 1
        else:
            self.q_status = self.QSTATUS_WRONG
            self.m_app.play_sad_sound()


class RhythmViewer(gtk.Frame):
    def __init__(self, parent):
        gtk.Frame.__init__(self)
        self.set_shadow_type(gtk.SHADOW_IN)
        self.g_parent = parent
        self.g_box = gtk.HBox()
        self.g_box.show()
        self.g_box.set_spacing(gu.PAD_SMALL)
        self.g_box.set_border_width(gu.PAD)
        self.add(self.g_box)
        self.m_data = []
        # the number of rhythm elements the viewer is supposed to show
        self.m_num_beats = 0
        self.g_face = None
        self.__timeout = None
    def set_num_beats(self, i):
        self.m_num_beats = i
    def clear(self):
        for child in self.g_box.get_children():
            child.destroy()
        self.m_data = []
    def create_holders(self):
        """
        create those |__| that represents one beat
        """
        if self.__timeout:
            gobject.source_remove(self.__timeout)
        self.clear()
        for x in range(self.m_num_beats):
            self.g_box.pack_start(gu.create_png_image('holder'), False)
        self.m_data = []
    def clear_wrong_part(self):
        """When the user have answered the question, this method is used
        to clear all but the first correct elements."""
        # this assert is always true because if there is no rhythm element,
        # then there is a rhythm holder ( |__| )
        assert self.m_num_beats == len(self.g_parent.m_t.m_question)
        self.g_face.destroy()
        self.g_face = None
        for n in range(self.m_num_beats):
            if self.m_data[n] != self.g_parent.m_t.m_question[n]:
                break
        for x in range(n, len(self.g_box.get_children())):
            self.g_box.get_children()[n].destroy()
        self.m_data = self.m_data[:n]
        for x in range(n, self.m_num_beats):
            self.g_box.pack_start(gu.create_png_image('holder'), False)
    def add_rhythm_element(self, i):
        assert len(self.m_data) <= self.m_num_beats
        if len(self.g_box.get_children()) >= self.m_num_beats:
            self.g_box.get_children()[self.m_num_beats-1].destroy()
        vbox = gtk.VBox()
        vbox.show()
        im = gu.create_rhythm_image(const.RHYTHMS[i])
        vbox.pack_start(im)
        vbox.pack_start(gu.create_png_image('rhythm-wrong'), False, False)
        vbox.get_children()[-1].hide()
        self.g_box.pack_start(vbox, False)
        self.g_box.reorder_child(vbox, len(self.m_data))
        self.m_data.append(i)
    def backspace(self):
        if len(self.m_data) > 0:
            if self.g_face:
                self.g_box.get_children()[-2].destroy()
                self.g_face.destroy()
                self.g_face = None
            self.g_box.get_children()[len(self.m_data)-1].destroy()
            self.g_box.pack_start(gu.create_png_image('holder'), False)
            del self.m_data[-1]
    def mark_wrong(self, idx):
        """
        Mark the rhythm elements that was wrong by putting the content of
        graphics/rhythm-wrong.png (normally a red line) under the element.
        """
        self.g_box.get_children()[idx].get_children()[1].show()
    def len(self):
        "return the number of rhythm elements currently viewed"
        return len(self.m_data)
    def sad_face(self):
        l = gu.HarmonicProgressionLabel(_("Wrong"))
        l.show()
        self.g_box.pack_start(l, False)
        self.g_face = gtk.EventBox()
        self.g_face.connect('button_press_event', self.on_sadface_event)
        self.g_face.show()
        im = gtk.Image()
        im.set_from_stock('solfege-sadface', gtk.ICON_SIZE_LARGE_TOOLBAR)
        im.show()
        self.g_face.add(im)
        self.g_box.pack_start(self.g_face, False)
    def happy_face(self):
        l = gu.HarmonicProgressionLabel(_("Correct"))
        l.show()
        self.g_box.pack_start(l, False)
        self.g_face = gtk.EventBox()
        self.g_face.connect('button_press_event', self.on_happyface_event)
        self.g_face.show()
        im = gtk.Image()
        im.set_from_stock('solfege-happyface', gtk.ICON_SIZE_LARGE_TOOLBAR)
        im.show()
        self.g_face.add(im)
        self.g_box.pack_start(self.g_face, False)
    def on_happyface_event(self, obj, event):
        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1:
            self.g_parent.new_question()
    def on_sadface_event(self, obj, event):
        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1:
            self.clear_wrong_part()
    def flash(self, s):
        self.clear()
        l = gtk.Label(s)
        l.set_name("Feedback")
        l.set_alignment(0.0, 0.5)
        l.show()
        self.g_box.pack_start(l, True, True)
        lx, ly = l.size_request()
        sx, sy = self.g_box.size_request()
        self.g_box.set_size_request(max(lx + gu.PAD * 2, sx), max(ly + gu.PAD * 2, sy))
        self.__timeout = gobject.timeout_add(2000, self.unflash)
    def unflash(self, *v):
        self.__timeout = None
        self.clear()

class Gui(abstract.Gui, abstract.RhythmAddOnGuiClass):
    num_buttons_in_row = 8
    def __init__(self, teacher, window):
        abstract.Gui.__init__(self, teacher, window)
        self.m_key_bindings = {'backspace_ak': self.on_backspace}
        self.g_answer_box = gu.NewLineBox()
        self.practise_box.pack_start(self.g_answer_box, False)
        #-------
        hbox = gu.bHBox(self.practise_box)
        b = gtk.Button(_("Play"))
        b.show()
        b.connect('clicked', self.play_users_answer)
        hbox.pack_start(b, False, True)
        self.practise_box.pack_start(gtk.HBox(), False,
                                     padding=gu.PAD_SMALL)
        self.g_rhythm_viewer = RhythmViewer(self)
        #FIXME the value 52 is dependant on the theme used
        self.g_rhythm_viewer.set_size_request(-1, 52)
        self.g_rhythm_viewer.create_holders()
        hbox.pack_start(self.g_rhythm_viewer)
        #self.practise_box.pack_start(self.g_rhythm_viewer, False)

        # action area
        self.g_new = gu.bButton(self.action_area, _("_New"), self.new_question)
        self.g_repeat = gu.bButton(self.action_area, _("_Repeat"), self.repeat_question)
        self.g_repeat.set_sensitive(False)
        self.g_show = gu.bButton(self.action_area, _("_Give up"), self.give_up)
        self.g_show.set_sensitive(False)

        self.g_backspace = gu.bButton(self.action_area, _("_Backspace"),
                     self.on_backspace)
        self.g_backspace.set_sensitive(False)
        self.practise_box.show_all()
        ##############
        # config_box #
        ##############
        self.add_select_elements_gui()
        #--------    
        self.config_box.pack_start(gtk.HBox(), False,
                                   padding=gu.PAD_SMALL)
        self.add_select_num_beats_gui()
        #-----
        self.config_box.pack_start(gtk.HBox(), False,
                                   padding=gu.PAD_SMALL)
        #------
        hbox = gu.bHBox(self.config_box, False)
        hbox.set_spacing(gu.PAD_SMALL)
        hbox.pack_start(gu.nCheckButton(self.m_exname,
                 "not_start_with_rest",
                 _("Don't start the question with a rest")), False)
        sep = gtk.VSeparator()
        hbox.pack_start(sep, False)
        hbox.pack_start(gtk.Label(_("Beats per minute:")), False)
        spin = gu.nSpinButton(self.m_exname, 'bpm',
                 gtk.Adjustment(60, 20, 240, 1, 10))
        hbox.pack_start(spin, False)
        self._add_auto_new_question_gui(self.config_box)
        self.config_box.show_all()
    def pngbutton(self, i):
        "used by the constructor"
        btn = gtk.Button()
        if i > len(const.RHYTHMS):
            im = gtk.Image()
            im.set_from_stock("gtk-missing-image", gtk.ICON_SIZE_LARGE_TOOLBAR)
            im.show()
            btn.add(im)
        else:
            btn.add(gu.create_rhythm_image(const.RHYTHMS[i]))
        btn.show()
        btn.connect('clicked', self.guess_element, i)
        return btn
    def select_element_cb(self, button, element_num):
        super(Gui, self).select_element_cb(button, element_num)
        self.update_answer_buttons()
    def on_backspace(self, widget=None):
        if self.m_t.q_status == self.QSTATUS_SOLVED:
            return
        self.g_rhythm_viewer.backspace()
        if not self.g_rhythm_viewer.m_data:
            self.g_backspace.set_sensitive(False)
    def play_users_answer(self, widget):
        if self.g_rhythm_viewer.m_data:
            rhythm = " ".join([const.RHYTHMS[c] for c in self.g_rhythm_viewer.m_data])
            self.m_t.play_rhythm(r"\staff{ %s }" % rhythm)
    def guess_element(self, sender, i):
        if self.m_t.q_status == self.QSTATUS_NO:
            self.g_rhythm_viewer.flash(_("Click 'New' to begin."))
            return
        if self.m_t.q_status == self.QSTATUS_SOLVED:
            return
        if self.g_rhythm_viewer.len() == len(self.m_t.m_question):
            self.g_rhythm_viewer.clear_wrong_part()
        self.g_backspace.set_sensitive(True)
        self.g_rhythm_viewer.add_rhythm_element(i)
        if self.g_rhythm_viewer.len() == len(self.m_t.m_question):
            if self.m_t.guess_answer(self.g_rhythm_viewer.m_data):
                self.g_rhythm_viewer.happy_face()
                self.g_new.set_sensitive(True)
                self.g_new.grab_focus()
                self.g_backspace.set_sensitive(False)
                self.g_show.set_sensitive(False)
            else:
                v = []
                for idx in range(len(self.m_t.m_question)):
                    v.append(self.m_t.m_question[idx] == self.g_rhythm_viewer.m_data[idx])
                for x in range(len(v)):
                    if not v[x]:
                        self.g_rhythm_viewer.mark_wrong(x)
                self.g_rhythm_viewer.sad_face()
    def new_question(self, widget=None):
        g = self.m_t.new_question()
        if g == self.m_t.OK:
            self.g_first_rhythm_button.grab_focus()
            self.g_rhythm_viewer.set_num_beats(self.get_int('num_beats'))
            self.g_rhythm_viewer.create_holders()
            self.g_show.set_sensitive(True)
            self.g_repeat.set_sensitive(True)
            try:
                self.m_t.play_question()
            except Exception, e:
                if not self.standard_exception_handler(e, self.on_end_practise):
                    raise
                return
            self.g_new.set_sensitive(
               not self.get_bool('config/picky_on_new_question'))
        elif g == self.m_t.ERR_PICKY:
            self.g_rhythm_viewer.flash(_("You have to solve this question first."))
        else:
            assert g == self.m_t.ERR_NO_ELEMS
            self.g_repeat.set_sensitive(False)
            self.g_rhythm_viewer.flash(_("You have to configure this exercise properly"))
    def repeat_question(self, *w):
        self.m_t.play_question()
        self.g_first_rhythm_button.grab_focus()
    def update_answer_buttons(self):
        """
        (Re)create the buttons needed to answer the questions.
        We recreate the buttons for each lesson file because the
        header may specify a different set of rhythm elements to use.
        """
        self.g_answer_box.empty()
        self.g_first_rhythm_button = None
        for n in self.m_t.m_P.header.visible_rhythm_elements:
            if n == 'newline':
                self.g_answer_box.newline()
            else:
                b = self.pngbutton(n)
                if not self.g_first_rhythm_button:
                    self.g_first_rhythm_button = b
                self.g_answer_box.add_widget(b)
        self.g_answer_box.show_widgets()
    def on_start_practise(self):
        assert self.m_t.m_P
        self.m_t.m_custom_mode = bool(self.m_t.m_P.header.configurable_rhythm_elements)
        self.m_t.set_elements_variables()
        self.update_answer_buttons()
        if self.m_t.m_custom_mode:
            self.update_select_elements_buttons()
            self.g_element_frame.show()
        else:
            self.g_element_frame.hide()
        if 'lesson_heading' in self.m_t.m_P.header:
            self.set_lesson_heading(self.m_t.m_P.header.lesson_heading)
        else:
            self.set_lesson_heading(_("Identify the rhythm"))
        self.m_t.set_default_header_values()
        if 'not_start_with_rest' in self.m_t.m_P.header:
            self.m_t.set_bool('not_start_with_rest', self.m_t.m_P.header.not_start_with_rest)
        self.g_rhythm_viewer.flash(_("Click 'New' to begin."))
        self.g_new.grab_focus()
    def on_end_practise(self):
        self.m_t.end_practise()
        self.g_new.set_sensitive(True)
        self.g_repeat.set_sensitive(False)
        self.g_show.set_sensitive(False)
        self.g_rhythm_viewer.create_holders()
    def give_up(self, widget=None):
        if self.m_t.q_status == self.QSTATUS_NO:
            return
        self.g_rhythm_viewer.clear()
        for i in self.m_t.m_question:
            self.g_rhythm_viewer.add_rhythm_element(i)
        self.m_t.q_status = self.QSTATUS_SOLVED
        self.g_new.set_sensitive(True)
        self.g_new.grab_focus()
        self.g_show.set_sensitive(False)
        self.g_backspace.set_sensitive(False)

