# GNU Solfege - free ear training software
# Copyright (C) 2007 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., 51 Franklin ST, Fifth Floor, Boston, MA  02110-1301  USA

import gobject
import gtk
import os
import time
import pickle
import filesystem
import gu
import configureoutput
from dataparser import Dataparser, DataparserException
import lessonfile


# The definition of a training set will be stored in a file in the
# ~/.solfege/trainingsets

class TrainingSetDialog(gu.EditorDialogBase):
    fileformat_version = 1
    STORE_LESSON_ID = 0
    STORE_TITLE = 1
    STORE_COUNT = 2
    STORE_REPEAT = 3
    STORE_DELAY = 4
    def __init__(self, app):
        gu.EditorDialogBase.__init__(self)
        self.savedir = os.path.join(filesystem.user_data(), "trainingsets")
        self.set_default_size(800, 300)
        self.m_app = app
        # This VBox will have 2 parts.
        # 1: the tool bar
        # 2: another container widget that has the content of a file
        self.g_vbox = gtk.VBox()
        self.add(self.g_vbox)
        self.setup_toolbar()
        #
        self.g_settings_box = gtk.VBox()
        self.g_settings_box.set_border_width(6)
        self.g_vbox.pack_start(self.g_settings_box, False)
        self.g_output = {}
        self.g_output['midi'] = gtk.RadioButton(None, _("MIDI"))
        self.g_output['wav'] = gtk.RadioButton(self.g_output['midi'], _("WAV"))
        self.g_output['mp3'] = gtk.RadioButton(self.g_output['midi'], _("MP3"))
        self.g_output['ogg'] = gtk.RadioButton(self.g_output['midi'], _("OGG"))
        hbox = gtk.HBox()
        hbox.set_spacing(6)
        self.g_settings_box.pack_start(hbox)
        hbox.pack_start(gtk.Label(_("Preferred output format:")), False)
        for s in ('midi', 'wav', 'mp3', 'ogg'):
            hbox.pack_start(self.g_output[s], False)
        ####
        self.g_liststore = gtk.ListStore(
            gobject.TYPE_STRING, # lesson_id
            gobject.TYPE_STRING, # visible exercise name
            gobject.TYPE_INT, # count
            gobject.TYPE_INT, # repeat
            gobject.TYPE_INT) # delay
        self.g_treeview = gtk.TreeView(self.g_liststore)
        self.g_treeview.set_size_request(400, 100)
        self.g_treeview.connect('cursor-changed',
            self.on_treeview_cursor_changed)

        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn(_("Title"), renderer, text=self.STORE_TITLE)
        self.g_treeview.append_column(column)
        renderer = gtk.CellRendererText()
        renderer.set_property('editable', True)
        renderer.connect('edited', self.on_count_edited)
        column = gtk.TreeViewColumn(_("Count"), renderer,
                                    text=self.STORE_COUNT)
        self.g_treeview.append_column(column)
        renderer = gtk.CellRendererText()
        renderer.set_property('editable', True)
        renderer.connect('edited', self.on_repeat_edited)
        column = gtk.TreeViewColumn(_("Repeat"), renderer,
                                    text=self.STORE_REPEAT)
        self.g_treeview.append_column(column)
        renderer = gtk.CellRendererText()
        renderer.set_property('editable', True)
        renderer.connect('edited', self.on_delay_edited)
        column = gtk.TreeViewColumn(_("Delay"), renderer, text=self.STORE_DELAY)
        self.g_treeview.append_column(column)
        self.g_vbox.pack_start(self.g_treeview)
        self.init_empty_file()
        self.show_all()
    def on_treeview_cursor_changed(self, treeview):
        self.g_ui_manager.get_widget("/ExportToolbar/Remove").set_sensitive(True)
    def on_count_edited(self, renderer, path, text):
        self._edit_col(2, path, text)
    def on_repeat_edited(self, renderer, path, text):
        self._edit_col(3, path, text)
    def on_delay_edited(self, renderer, path, text):
        self._edit_col(4, path, text)
    def _edit_col(self, col_num, path, text):
        """
        This method does the real work when on_XXXX_edited is called.
        """
        try:
            i = int(text)
        except ValueError:
            i = None
        if i is not None:
            iter = self.g_liststore.get_iter_from_string(path)
            self.g_liststore.set_value(iter, col_num, i)
    def on_remove_lesson_clicked(self, *w):
       path, column = self.g_treeview.get_cursor()
       assert path
       iter = self.g_liststore.get_iter(path)
       if iter:
           if self.g_liststore.remove(iter):
               self.g_treeview.set_cursor(self.g_liststore.get_path(iter))
           elif path[0] > 0:
               self.g_treeview.set_cursor((path[0]-1,))
           else:
               self.g_ui_manager.get_widget("/ExportToolbar/Remove").set_sensitive(False)
       else:
           print "NOt delete because not iter"
       print "ITER:", iter
    def on_add_lesson_clicked(self, button):
        menu = self.create_learning_tree_menu()
        menu.popup(None, None, None, 1, 0)
    def on_select_exercise(self, item):
        """
        This method is called when the user has selected an exercise to
        add.
        """
        lesson_id = item.get_data('lesson_id')
        module = self.m_app.lessonfile_manager.get(lesson_id, 'module')
        if module not in ('harmonicinterval', 'melodicinterval', 'idbyname'):
            print "Only harmonicinterval, melodicinterval and idbyname module exercises are working now. Ignoring..."
            return
        if module == 'idbyname':
            fn = self.m_app.lessonfile_manager.get(lesson_id, 'filename')
            p = lessonfile.LessonfileCommon(fn)
            p.parse_file(fn)
            if not [q for q in p.m_questions if q['music'].is_mpd_parsable()]:
                gu.dialog_ok(_("This lesson file cannot be exported because some of the music in the file are not parsable by the mpd module."))
                return
        self.m_changed = True
        self.g_liststore.append((
            lesson_id, self.get_lessonfile_title(lesson_id),
            3, 3, 4))
    def get_lessonfile_title(self, lesson_id):
        """
        Return a string we use the name the lesson file in the GUI.
        """
        return self.m_app.lessonfile_manager.get(lesson_id, 'title') \
            + " (%s)" % self.m_app.lessonfile_manager.get(lesson_id, 'filename')
    def create_learning_tree_menu(self):
        """
        Create and return a gtk.Menu object that has submenus that
        let us select all lessons on the learning tree.
        """
        def create_submenu(menudata, parent_name):
            """
            Menudata is a dict. Key we will use:
            'name'
            'children': list of dict or string (or maybe both?).
            """
            if isinstance(menudata, list):
                menu = gtk.Menu()
                for lesson_id in menudata:
                    if self.m_app.lessonfile_manager.get(lesson_id, 'module') not in ('melodicinterval', 'harmonicinterval', 'idbyname'):
                        continue
                    # We don't want to add these lesson files because we know
                    # that they cannot be exported. It would be better
                    # to catch these with a more generit algorithm, but
                    # then we would have to parse all the files, and that
                    # would be too slow.
                    if lesson_id in (
                            # melodic-interval-self-config
                            "f62929dc-7122-4173-aad1-4d4eef8779af",
                            # harmonic-interval-self-config
                            "466409e7-9086-4623-aff0-7c27f7dfd13b",
                            # the csound-fifth-* files:
                            "b465c807-d7bf-4e3a-a6da-54c78d5b59a1",
                            "aa5c3b18-664b-4e3d-b42d-2f06582f4135",
                            "5098fb96-c362-45b9-bbb3-703db149a079",
                            "3b1f57e8-2983-4a74-96da-468aa5414e5e",
                            "a06b5531-7422-4ea3-8711-ec57e2a4ce22",
                            "e67c5bd2-a275-4d9a-96a8-52e43a1e8987",
                            "1cadef8c-859e-4482-a6c4-31bd715b4787",
                            ):
                        continue
                    i = gtk.MenuItem(
                        self.m_app.lessonfile_manager.get(lesson_id, 'title'))
                    i.set_data('lesson_id', lesson_id)
                    i.set_data('menus', parent_name)
                    i.connect('activate', self.on_select_exercise)
                    menu.append(i)
                return menu
            item = gtk.MenuItem(_(menudata['name']))
            menu = gtk.Menu()
            for m in menudata['children']:
                i = gtk.MenuItem(_(m['name']))
                menu.append(i)
                i.set_submenu(create_submenu(m['children'], parent_name + [_(m['name'])]))
            item.set_submenu(menu)
            return item
        menu = gtk.Menu()
        for m in self.m_app.m_ui.m_tree.m_menus:
            menu.append(create_submenu(m, [_(m['name'])]))

        menu.show_all()
        self._menu_hide_stuff(menu)
        return menu
    def _menu_hide_stuff(self, menu):
        """
        Hide the menu if it has no menu items, or all menu items are hidden.
        """
        for sub in menu.get_children():
            assert isinstance(sub, gtk.MenuItem)
            if sub.get_submenu():
                self._menu_hide_stuff(sub.get_submenu())
                if not [c for c in sub.get_submenu().get_children() if c.get_property('visible')]:
                    sub.hide()
    def init_empty_file(self):
        self.m_changed = False
        self.m_savetime = time.time()
        self.set_title(self._get_a_filename())
        self.g_ui_manager.get_widget("/ExportToolbar/Remove").set_sensitive(False)
    def setup_toolbar(self):
        self.g_actiongroup.add_actions([
         ('Export', gtk.STOCK_EXECUTE, _("Export"), None, None, self.on_export),
         ('Add', gtk.STOCK_ADD, None, None, None, self.on_add_lesson_clicked),
         ('Remove', gtk.STOCK_REMOVE, None, None, None, self.on_remove_lesson_clicked),
        ])
        self.g_ui_manager.insert_action_group(self.g_actiongroup, 0)
        uixml = """
        <ui>
         <toolbar name='ExportToolbar'>
          <toolitem action='Add'/>
          <toolitem action='Remove'/>
          <toolitem action='New'/>
          <toolitem action='Open'/>
          <toolitem action='Save'/>
          <toolitem action='SaveAs'/>
          <toolitem action='Export'/>
          <toolitem action='Close'/>
          <toolitem action='Help'/>
         </toolbar>
        </ui>
        """
        self.g_ui_manager.add_ui_from_string(uixml)
        self.g_vbox.pack_start(self.g_ui_manager.get_widget("/ExportToolbar"), False)
        self.g_ui_manager.get_widget("/ExportToolbar").set_style(gtk.TOOLBAR_BOTH)
    def on_show_help(self, widget):
        self.m_app.handle_href("trainingset-editor.html")
    def get_trainingset_filenames(self):
        #FIXME we need to know 100% sure that we will get
        #unicode strings from this method
        try:
            return os.listdir(os.path.join(filesystem.user_data(), "trainingsets"))
        except OSError, e:
            if e.errno != 2:
                print "warning: errno != 2"
            return []
    def load_file(self, filename):
        p = Dataparser({'yes': True, 'no': False}, {})
        p.parse_file(filename)
        s = p.globals.setdefault('output_format', 'midi')
        s = s.lower()
        if s in self.g_output:
            self.g_output[s].set_active(True)
        else:
            # MIDI is the default format
            self.g_output['midi'].set_active(True)
        self.m_filename = filename
        for lesson in p.blocklists.setdefault('lesson', []):
            self.g_liststore.append((lesson['lesson_id'],
                self.get_lessonfile_title(lesson['lesson_id']),
                lesson['count'],
                lesson['repeat'],
                lesson['delay']))
        self.set_title(self.m_filename)
    def save(self):
        """
        Save the file to a file named by self.m_filename
        """
        assert self.m_filename
        f = open(self.m_filename, 'w')
        print >> f, "# Training set definition file for GNU Solfege %s" % configureoutput.VERSION_STRING
        print >> f, "\nfileformat_version = %i" % self.fileformat_version
        print >> f, "output_format = \"%s\"" % [k for k in self.g_output if self.g_output[k].get_active()][0]
        iter = self.g_liststore.get_iter_first()
        while iter:
            print >> f, "lesson {"
            lesson_id = self.g_liststore.get_value(iter, self.STORE_LESSON_ID)
            if self.m_app.lessonfile_manager.get(lesson_id, 'title'):
                print >> f, "  # %s (%s)" % (
                    self.m_app.lessonfile_manager.get(lesson_id, 'title'),
                    self.m_app.lessonfile_manager.get(lesson_id, 'filename')
                    )
            print >> f, '  lesson_id = "%s"' % lesson_id
            print >> f, '  count = %i' \
                % self.g_liststore.get_value(iter, self.STORE_COUNT)
            print >> f, '  repeat = %i' \
                % self.g_liststore.get_value(iter, self.STORE_REPEAT)
            print >> f, '  delay = %i' \
                % self.g_liststore.get_value(iter, self.STORE_DELAY)
            print >> f, "}\n"
            iter = self.g_liststore.iter_next(iter)
        f.close()
        self.m_changed = False
        self.m_savetime = time.time()
    def on_export(self, widget):
        export_to = \
            self.select_empty_directory(_("Select where to export the files"))
        if not export_to:
            return
        progress_dialog = gtk.Dialog(_("Exporting training set"), self,
            0, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
        progress_dialog.show()
        label = gtk.Label()
        label.set_markup('<span weight="bold">%s</span>' % _("Export training set"))
        label.show()
        progress_dialog.vbox.pack_start(label, False)
        def _cancel(widget, response):
            self.m_app.m_abort_export = True
        progress_dialog.connect('response', _cancel)
        progress_bar = gtk.ProgressBar()
        progress_bar.show()
        progress_dialog.vbox.pack_start(progress_bar)
        # We have to make a version of the data without gtk widgets
        v = []
        iter = self.g_liststore.get_iter_first()
        while iter:
            v.append({
                'lesson_id': \
                        self.g_liststore.get_value(iter, self.STORE_LESSON_ID),
                'count': self.g_liststore.get_value(iter, self.STORE_COUNT),
                'repeat': self.g_liststore.get_value(iter, self.STORE_REPEAT),
                'delay': self.g_liststore.get_value(iter, self.STORE_DELAY),
                        })
            iter = self.g_liststore.iter_next(iter)
        output_format = [k for k in self.g_output if self.g_output[k].get_active()][0]
        progress_dialog.queue_draw()
        while gtk.events_pending():
            gtk.main_iteration(0)
        time.sleep(0.1)
        while gtk.events_pending():
            gtk.main_iteration(0)
        for prog in self.m_app.export_training_set(v, export_to, output_format):
            progress_bar.set_fraction(prog)
            while gtk.events_pending():
                gtk.main_iteration(0)
        progress_dialog.destroy()


