# 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
from gethomedir import get_home_dir
import gu
import configureoutput
from dataparser import Dataparser, DataparserException


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

class TrainingSetDialog(gtk.Window):
    instance_counter = 0
    fileformat_version = 1
    def __init__(self, app):
        gtk.Window.__init__(self)
        TrainingSetDialog.instance_counter += 1
        self.savedir = os.path.join(get_home_dir(), ".solfege", "trainingsets")
        self.connect('delete_event', self.on_delete_event)
        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)
        # The box containing one widget per lesson file that is used in the set
        sc = gtk.ScrolledWindow()
        self.g_vbox.pack_start(sc)
        self.g_ebox = gtk.VBox()
        self.g_ebox.set_border_width(6)
        sc.add_with_viewport(self.g_ebox)
        self.init_empty_file()
        self.add_newbutton()
    def add_newbutton(self):
        self.g_add_lesson = gtk.Button(_("Add lesson file"))
        self.g_add_lesson.connect('clicked', self.on_add_lesson_clicked)
        self.g_ebox.pack_start(self.g_add_lesson, False)
    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
        self.m_filedata.append({'lesson_id': lesson_id, 'count': 3, 'repeat': 3, 'delay': 4})
        self.m_changed = True
        self.append_lesson(self.get_lessonfile_title(lesson_id))
        self.move_newbutton_last()
    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 append_lesson(self, heading):
        """
        Create and append the gui for one exercise to g_ebox, and
        append refs to the widgets to self.m_filedata.
        """
        def label_spin(txt):
            hbox = gtk.HBox()
            hbox.set_spacing(6)
            l = gtk.Label(txt)
            hbox.pack_start(l, False)
            spin = gtk.SpinButton(gtk.Adjustment(1, 1, 100, 1, 10, 10))
            hbox.pack_start(spin, False)
            return hbox, spin
        vbox, content = gu.hig_category_vbox(heading)
        self.g_ebox.pack_start(vbox, False)
        #
        box, spin = label_spin(_("Number of questions to generate:"))
        self.m_filedata[-1]['count'] = spin
        content.pack_start(box, False)
        #
        box, spin = label_spin(_("Number of times to repeat each questions:"))
        self.m_filedata[-1]['repeat'] = spin
        content.pack_start(box, False)
        #
        box, spin = label_spin(_("Semitones delay after each question:"))
        self.m_filedata[-1]['delay'] = spin
        content.pack_start(box, False)
        content.show_all()
    def move_newbutton_last(self):
        """
        Move the button used to add a new lesson to the end of the list.
        """
        self.g_ebox.reorder_child(self.g_add_lesson, len(self.g_ebox.get_children()))
    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:
                    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()
        return menu
    def init_empty_file(self):
        self.m_filedata = []
        self.m_changed = False
        self.m_filename = None
        self.m_savetime = time.time()
        self.set_title(self._get_a_filename())
    def _get_a_filename(self):
        """
        Return a file name. UntitledN (where N is an integer) if we
        have to real name.
        """
        if not self.m_filename:
            return _("Untitled%s") % TrainingSetDialog.instance_counter
        return self.m_filename
    def setup_toolbar(self):
        self.g_toolbar = gtk.Toolbar()
        self.g_ui_manager = gtk.UIManager()
        actiongroup = gtk.ActionGroup('TrainingSetActions')
        actiongroup.add_actions([
         ('SetClose', gtk.STOCK_CLOSE, None, None, None, self.close_window),
         ('SetSave', gtk.STOCK_SAVE, None, None, None, self.on_save),
         ('SetSaveAs', gtk.STOCK_SAVE_AS, None, None, None, self.on_save_as),
         ('SetNew', gtk.STOCK_NEW, None, None, None, self.new_file),
         ('SetOpen', gtk.STOCK_OPEN, None, None, None, self.on_open),
         ('SetExport', gtk.STOCK_EXECUTE, _("Export"), None, None, self.on_export),
         ('SetAdd', gtk.STOCK_ADD, None, None, None, self.on_add_lesson_clicked),
         ('SetHelp', gtk.STOCK_HELP, None, None, None, self.on_show_help),
        ])
        self.g_ui_manager.insert_action_group(actiongroup, 0)
        uixml = """
        <ui>
         <toolbar name='ExportToolbar'>
          <toolitem action='SetAdd'/>
          <toolitem action='SetNew'/>
          <toolitem action='SetOpen'/>
          <toolitem action='SetSave'/>
          <toolitem action='SetSaveAs'/>
          <toolitem action='SetExport'/>
          <toolitem action='SetClose'/>
          <toolitem action='SetHelp'/>
         </toolbar>
        </ui>
        """
        merge_id = 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)
        hbox = gtk.HBox()
        hbox.set_spacing(8)
        self.g_vbox.pack_start(hbox, False)
    def on_show_help(self, widget):
        self.m_app.handle_href("trainingset-editor.html")
    def on_delete_event(self, *ignore):
        self.close_window()
        return True
    def close_window(self, *w):
        if not self.m_changed:
            self.destroy()
            return
        m = gtk.MessageDialog(self, gtk.DIALOG_MODAL,
            gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE,
            _("Save changes to \"%s\" before closing?") % self._get_a_filename())
        t = time.time() - self.m_savetime
        if t < 60:
            msg = _("If you don't save, changes from the past %i seconds will be permanently lost.") % int(time.time() - self.m_savetime)
        else:
            msg = _("If you don't save, changes from the past %i minutes will be permanently lost.") % int((time.time() - self.m_savetime) / 60.0)
        m.format_secondary_text(msg)
        m.add_button(_("_Close without Saving"), gtk.RESPONSE_CLOSE)
        m.add_button("gtk-cancel", gtk.RESPONSE_CANCEL)
        m.add_button("gtk-save", gtk.RESPONSE_OK)
        m.set_default_response(gtk.RESPONSE_OK)
        r = m.run()
        m.destroy()
        if r == gtk.RESPONSE_CLOSE:
            self.destroy()
        elif r == gtk.RESPONSE_CANCEL:
            return
        else:
            assert r == gtk.RESPONSE_OK
            self.on_save()
            self.destroy()
    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(get_home_dir(),
                                       ".solfege", "trainingsets"))
        except OSError, e:
            if e.errno != 2:
                print "warning: errno != 2"
            return []
    def on_open(self, widget):
        dialog = gtk.FileChooserDialog(None, self,
            gtk.FILE_CHOOSER_ACTION_OPEN,
            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
             gtk.STOCK_OK, gtk.RESPONSE_OK))
        dialog.set_current_folder(self.savedir)
        ret = dialog.run()
        if ret == gtk.RESPONSE_OK:
            try:
                if (not self.m_filename) and (not self.m_changed):
                    self.load_file(dialog.get_filename())
                else:
                    win = self.new_file()
                    win.load_file(dialog.get_filename())
            except Exception, e:
                self.m_app.m_ui.display_exception_message(e, __file__)
        dialog.destroy()
    def load_file(self, filename):
        p = Dataparser({'yes': True, 'no': False}, {})
        p.parse_file(filename)
        self.m_filedata = []#p.blocklists['lesson']
        s = p.globals.get('output_format')
        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['lesson']:#self.m_filedata:
            self.m_filedata.append({})
            lesson_id = lesson['lesson_id']
            self.append_lesson(self.get_lessonfile_title(lesson_id))
            self.m_filedata[-1]['lesson_id'] = lesson['lesson_id']
            self.m_filedata[-1]['count'].set_value(lesson['count'])
            self.m_filedata[-1]['repeat'].set_value(lesson['repeat'])
            self.m_filedata[-1]['delay'].set_value(lesson['delay'])
        self.move_newbutton_last()
        self.set_title(self.m_filename)
    def on_save_as(self, widget):
        """
        Return True if the file was saved, False if not.
        """
        dialog = gtk.FileChooserDialog(_("Save File As"), self,
            gtk.FILE_CHOOSER_ACTION_SAVE,
            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
             gtk.STOCK_OK, gtk.RESPONSE_OK))
        dialog.set_default_response(gtk.RESPONSE_OK)
        if not os.path.exists(self.savedir):
            try:
                os.mkdir(self.savedir)
            except OSError, e:
                pass
        # if self.m_filename is set to a real filename, we will
        # display the folder where that file exists. If not, we will
        # use .solfege/trainingsets
        if os.path.dirname(self._get_a_filename()):
            dialog.set_current_folder(os.path.dirname(self._get_a_filename()))
        else:
            dialog.set_current_folder(self.savedir)
        if dialog.get_current_folder() == os.path.dirname(self._get_a_filename()):
            dialog.set_current_name(os.path.basename(self._get_a_filename()))
        else:
            dialog.set_current_name(self._get_a_filename())
        ret = dialog.run()
        if ret == gtk.RESPONSE_OK:
            try:
                self.m_filename = dialog.get_filename()
                self.save()
                self.set_title(self.m_filename)
            except IOError, e:
                gu.dialog_ok(_("Error saving file"), self, str(e))
                dialog.destroy()
                return False
        dialog.destroy()
        return ret == gtk.RESPONSE_OK
    def on_save(self, widget=None):
        """
        Return True if the file was saved, False if not.
        """
        if not self.m_filename:
            return self.on_save_as(widget)
        else:
            self.save()
            return True
    def save(self):
        """
        Save the file to a file named by self.m_filename
        """
        assert self.m_filename
        f = file(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]
        for x in self.m_filedata:
            print >> f, "lesson {"
            if self.m_app.lessonfile_manager.get(x['lesson_id'], 'title'):
                print >> f, "  # %s (%s)" % (
                    self.m_app.lessonfile_manager.get(x['lesson_id'], 'title'),
                    self.m_app.lessonfile_manager.get(x['lesson_id'], 'filename')
                    )
            print >> f, '  lesson_id = "%s"' % x['lesson_id']
            print >> f, '  count = %i' % x['count'].get_value_as_int()
            print >> f, '  repeat = %i' % x['repeat'].get_value_as_int()
            print >> f, '  delay = %i' % x['delay'].get_value_as_int()
            print >> f, "}\n"
        f.close()
        self.m_changed = False
        self.m_savetime = time.time()
    def new_file(self, action=None):
        """
        Return the new dialog window.
        """
        m = TrainingSetDialog(self.m_app)
        m.show_all()
        return m
    def on_export(self, widget):
        msg = _("Select an empty directory, since we want to fill it with files.")
        dialog = gtk.FileChooserDialog(_("Select where to export the files"),
            self, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
             gtk.STOCK_OK, gtk.RESPONSE_OK))
        label = gtk.Label(msg)
        label.show()
        dialog.vbox.pack_start(label, False, False)
        while 1:
            res = dialog.run()
            if res == gtk.RESPONSE_CANCEL:
                dialog.destroy()
                return
            if os.listdir(dialog.get_filename()):
                msg = gtk.MessageDialog(self, gtk.DIALOG_MODAL,
                    gtk.MESSAGE_INFO, gtk.BUTTONS_OK, msg)
                msg.run()
                msg.destroy()
            else:
                break
        export_to = dialog.get_filename()
        dialog.destroy()
        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 = []
        for x in self.m_filedata:
            v.append({})
            for k in x.keys():
                if isinstance(x[k], gtk.SpinButton):
                    v[-1][k] = x[k].get_value_as_int()
                else:
                    v[-1][k] = x[k]

        output_format = [k for k in self.g_output if self.g_output[k].get_active()][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()


