# GNU Solfege - ear training for GNOME
# Copyright (C) 2000, 2001, 2002, 2003, 2004  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

try:
    app_running == True
except:
    import sys
    sys.path.append("src")
    sys.path.append(".")
    import i18n
    i18n.setup_srcdir()


import configureoutput
import errno, sys
import shutil
import signal
import soundcard, mpd
import lessonfile
import statistics
import abstract
import os
import cfg, utils
import webbrowser

class RunExternalFailed(Exception):
    def __init__(self, v):
        self.v = v
    def __str__(self):
        return "\nRunning external program failed.\nPlease check your setup.\nThe command that failed was: '%s'" % (" ".join(self.v[1:]))

__child_pid = None
def run_external_program(cmdstring, wdir, argument):
    global __child_pid
    if "%s" not in cmdstring:
        cmdline = "%s %s" % (cmdstring, argument)
    else:
        cmdline = cmdstring % argument
    wdir = os.path.normpath(wdir)
    cur_dir = os.getcwd()
    v = cmdline.split()
    v = v[:1] + v
    if not os.path.exists(v[0]):
        raise RunExternalFailed(v)
        return
    try:
        if __child_pid is not None:
            os.kill(__child_pid, signal.SIGKILL)
            os.wait()
        __child_pid = None
    except OSError:
        print "kill failed, OSError"
    except AttributeError:
        __child_pid = None
    pid = os.fork()
    if pid == 0:
        os.chdir(wdir)
        try:
            os.execlp(*v)
        except OSError, x:
            print >> sys.stderr, "OS Error:", x
            os._exit(-1)
            os.chdir(cur_dir)
        os.chdir(cur_dir)
    else:
        __child_pid = pid

class SolfegeApp(cfg.ConfigUtils):
    def __init__(self, options, ui):
        """
        options -- command line options parsed by optparse
        """
        cfg.ConfigUtils.__init__(self, 'solfege-app')
        self.m_options = options
        self.m_ui = ui
        self.check_rcfile()
        self.m_teachers = {}
        self.m_running_exercise = None
        self.lessonfile_manager = lessonfile.LessonFileManager()
        self.m_sound_init_exception = None
        self.setup_sound()
    def check_rcfile(self):
        """See default.config for rcfileversion values, meanings and
        a description of how to add config variables.
        """
        rcfileversion = 6
        if cfg.get_int("app/rcfileversion") > rcfileversion:
            cfg.drop_user_config()
            return
        if cfg.get_int("app/rcfileversion") <= 1:
            if not "example-files" in cfg.get_string('config/lessoncollections'):
                cfg.set_string('config/lessoncollections', 
                    "%s example-files" % cfg.get_string('config/lessoncollections'))
        if cfg.get_int("app/rcfileversion") <= 5:
            # This is more complicated that necessary to fix an old
            # error.
            if cfg.get_string("sound/commandline"):
                cfg.del_key("sound/commandline")
        if cfg.get_int("app/rcfileversion") <= 3:
            cfg.set_list("config/lessoncollections",
                cfg.get_string("config/lessoncollections").split())
        if cfg.get_int("app/rcfileversion") <= 4:
            cfg.del_key("config/web_browser")
        if sys.platform == 'win32':
            if cfg.get_string('sound/wav_player'):
                cfg.del_key('sound/wav_player')
        if cfg.get_int("app/rcfileversion") <= 5:
            cfg.set_string("mainwin/history_back_ak", "<alt>Left")
            cfg.set_string("mainwin/history_forward_ak", "<alt>Right")
            cfg.set_string("mainwin/history_reload_ak", "<ctrl>r")
        cfg.set_int("app/rcfileversion", rcfileversion)
    def setup_sound(self):
        if sys.platform == 'win32' and \
                    cfg.get_string("sound/type") == "sequencer-device":
            # just in case c:\home\.solfegerc is wrong
            cfg.set_string("sound/type", "winsynth")
        if self.m_options.no_sound \
           or cfg.get_string("sound/type") == "fake-synth":
            soundcard.initialise_using_fake_synth(self.m_options.verbose_sound_init)
        elif cfg.get_string("sound/type") == "winsynth":
            try:
                soundcard.initialise_winsynth(cfg.get_int("sound/synth_number"),
                      verbose_init=self.m_options.verbose_sound_init)
            except:#FIXME should use except: ExceptionName
                cfg.set_int("sound/synth_number", 0)
                soundcard.initialise_winsynth(cfg.get_int("sound/synth_number"),
                      self.m_options.verbose_sound_init)
        elif cfg.get_string("sound/type") == "external-midiplayer":
            soundcard.initialise_external_midiplayer(
                    cfg.get_string("sound/midi_player"),
                    verbose_init=self.m_options.verbose_sound_init)
        elif cfg.get_string("sound/type") == '':
            self.m_ui.display_error_message(
"""You should configure sound
from the 'Sound' page of
the preferences window.
""")
        elif cfg.get_string("sound/type") == "sequencer-device":
            if not configureoutput.HAVE_LINUX_AWE_VOICE_H and \
                    cfg.get_string("sound/card_info") == "awe":
                cfg.set_string("sound/card_info", "");
            try:
                soundcard.initialise_devicefile(
                             cfg.get_string("sound/device_file"),
                             cfg.get_int("sound/synth_number"),
                             cfg.get_string("sound/card_info"),
                             verbose_init=self.m_options.verbose_sound_init)
            except (soundcard.SoundInitException, OSError, ImportError), e:
                self.m_sound_init_exception = e
                soundcard.initialise_using_fake_synth(True)
    def display_sound_init_error_message(self, e):
        if isinstance(e, soundcard.SoundInitException):
            self.m_ui.display_error_message(
            """%s""" % e)
        elif isinstance(e, ImportError):
            self.m_ui.display_error_message("%(message)s\n%(exception)s\n\n%(todo)s" % {
              'exception':e,
              'message': "<b>%s</b>" % _("Error loading python module:"),
              'todo': _("Now you should configure sound from the preferences window."),
              })
        elif e.errno == errno.EACCES:
            self.m_ui.display_error_message(
"""The sound init failed: %s
The errno EACCES indicates that you don't have write
permission to the device.""" % e)
        elif e.errno == errno.EBUSY:
            self.m_ui.display_error_message(
"""The sound init failed: %s
It seems like some other program is using the device. You
should try to quit that other program and restart Solfege."""
 % e)
        else:
            self.m_ui.display_error_message(
"""The sound init failed: 
    %s
You should configure sound from the 'Sound' page of the preferences window.

It is also possible that the OS sound setup is incorrect.
"""
 % (e))
    def play_happy_sound(self):
        mpd.play_music(r"\staff\relative c'{c16 e g a}", 180, 8,
               cfg.get_int('config/preferred_instrument_velocity'))
    def play_sad_sound(self):
        mpd.play_music(r"\staff\relative c'{<c,,8 cis>", 80, 58,
               cfg.get_int('config/preferred_instrument_velocity'))
    def please_help_me(self):
        if self.m_ui.m_viewer != 'docviewer':
            self.handle_href('%s-help.html' % self.m_ui.m_viewer)
    def handle_href(self, href, display_docfile_set_adj=1):
        if self.m_ui.m_viewer:
            if display_docfile_set_adj == 1:
                # we get here when we click on urls, because we want to
                # save the place in the current document when we load the
                # new one. We don't get here if handle_href is called from
                # history_back.
                self.m_ui.m_history.set_adj_of_current(self.m_ui.box_dict['docviewer'].get_vadjustment().value)
        protocol, action, fn, anchor, lessoncollection, lessonfile, config = \
           utils.parse_url(href)
        if protocol is None:
            if fn.endswith(".midi"):
                if os.path.exists(os.path.join(self.m_ui.box_dict['docviewer'].m_htmlwidget.m_document_wd,fn)):
                    midifilename = fn
                elif len(fn.split('-')) > 1:
                    # Filename-nl.midi => Filename.midi
                    v = fn.split('-')
                    w = v[-1].split('.')
                    midifilename = "-".join(v[:-1])+"."+w[1]
                else:
                    midifilename = fn
                run_external_program(self.get_string("sound/midi_player"),
                        self.m_ui.box_dict['docviewer'].m_htmlwidget.m_document_wd, midifilename)
            else:
                self.m_ui.display_docfile(fn, anchor)
        elif protocol == 'solfege':
            self.handle_solfege_href(href, action, fn, anchor, lessoncollection,
                                     lessonfile, config)
        elif protocol == 'http':
            webbrowser.open_new(href)
        elif protocol == 'mailto':
            os.system(self.get_string("config/mua") % href)
        else:
            print "unknown link type", protocol
    def handle_solfege_href(self, href, action, fn, anchor, lessoncollection,
                            lessonfile, config):
        if action == 'all-lessonfiles':
            self.m_ui.display_html(self.lessonfile_manager.m_htmldoc)
            self.m_ui.m_history.add('solfege:all-lessonfiles')
            return
        if lessonfile:
            filepath = os.path.join(self.get_string('lessoncollections/%s' %lessoncollection), lessonfile)
            if not os.path.isfile(filepath):
                self.m_ui.display_error_message("Lessonfile not found:\n%s" % filepath)
                return
        if (self.m_ui.m_viewer == 'docviewer'
                   and self.m_running_exercise is not None
                   and self.m_running_exercise != fn) or \
                  (self.m_ui.m_viewer != 'docviewer'):
            self.m_ui.box_dict[self.m_running_exercise].on_end_practise()
            self.m_running_exercise = None
        _old_viewer = self.m_ui.m_viewer
        if fn not in self.m_teachers:
            new_ex = True
            n = utils.exercise_name_to_module_name(fn)
            exec("import %s" % n)
            if issubclass(locals()[n].Teacher, abstract.LessonbasedTeacher):
                self.m_teachers[fn] = locals()[n].Teacher(fn, self, lessoncollection, lessonfile, config)
            else:
                self.m_teachers[fn] = locals()[n].Teacher(fn, self, config)
            # if we don't have a teacher, then the Gui has not been
            # initialized either
            self.m_ui.initialise_exercise(self.m_teachers[fn])
            self.m_teachers[fn].g_view = self.m_ui.box_dict[fn]
        else:
            new_ex = False
            self.m_teachers[fn].configure_exercise(lessoncollection, lessonfile, config)
        self.m_ui.activate_exercise(href)
        if not (_old_viewer == 'docviewer'
                and self.m_running_exercise is not None
                and self.m_running_exercise == fn):
            self.m_ui.box_dict[self.m_ui.m_viewer].on_start_practise()
        self.m_running_exercise = fn
    def quit_program(self):
        cfg.sync()
        for teacher in self.m_teachers.itervalues():
            if teacher.m_statistics is not None:
                teacher.m_statistics.save_data()
        if soundcard.synth:
            soundcard.synth.close()

