# GNU Solfege - ear training for GNOME
# Copyright (C) 2005  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

from __future__ import division

import cfg
import gtk
import gobject
import os
import pickle
import random
import time
import inputwidgets
import utils
import gu
import soundcard
import gethomedir
import mpd

class Test(cfg.ConfigUtils):
    """
    This class is shared by harmonicinterval and melodicinterval.
    """
    def __init__(self, exname):
        cfg.ConfigUtils.__init__(self, exname)
        self.m_question_idx = -1
        self.m_result = {}
        self.m_status = 'no'
        self.m_savepath = os.path.join(gethomedir.get_home_dir(), '.solfege', 'tests', exname)
    def store_results(self):
        filename = str(int(time.time()))
        if not os.path.exists(self.m_savepath):
            os.makedirs(self.m_savepath)
        f = open(os.path.join(self.m_savepath, filename), 'w')
        pickle.dump(self.m_result, f)
        f.close()
    def guess_answer(self, interval):
        assert self.m_status == 'asked'
        self.m_result.setdefault(self.m_interval_list[self.m_question_idx], []).append(interval)
        self.m_status = 'answered'
        if self.m_question_idx + 1 == len(self.m_interval_list):
            self.m_status = 'completed'
        return interval == abs(self.m_interval_list[self.m_question_idx])
    def new_question(self):
        self.m_question_idx += 1
        l, h = utils.adjust_low_high_to_irange(self.get_string('lowest_tone'),
           self.get_string('highest_tone'), self.m_interval_list)
        self.m_tonika = random.randint(l, h)
        self.m_interval = mpd.Interval()
        self.m_interval.set_from_int(self.m_interval_list[self.m_question_idx])
        self.play_question()
        self.m_status = 'asked'
    def get_total_results(self):
        """
        return a dictionary
        {IntervalInt: [totalcount, correctcount]}
        """
        if not os.path.isdir(self.m_savepath):
          os.makedirs(self.m_savepath)
        totals = {}
        for filename in os.listdir(self.m_savepath):
            f = file(os.path.join(self.m_savepath, filename), 'r')
            d = pickle.load(f)
            f.close()
            for k in d:
                if not k in totals:
                    totals[k] = {'correct': 0, 'count': 0}
                correct = len([i for i in d[k] if i == k])
                count = len(d[k])
                totals[k]['count'] += count
                totals[k]['correct'] += correct
        for d in totals.values():
            if not d['count']:
                d['percentage'] = 0.0
            else:
                d['percentage'] = d['correct'] / d['count'] * 100
        return totals

class HarmonicIntervalTest(Test):
    def __init__(self, exname):
        Test.__init__(self, exname)
        self.m_interval_list = range(1, 17) * 2
        random.shuffle(self.m_interval_list)
    def play_question(self):
        low_tone = mpd.MusicalPitch.new_from_int(self.m_tonika)
        high_tone = low_tone + self.m_interval
        m = soundcard.Track()
        m.set_bpm(self.get_int('config/default_bpm'))
        instr_low_vel = instr_high_vel = 100
        m.start_note(0, low_tone, instr_low_vel)
        m.start_note(1, high_tone, instr_high_vel)
        m.notelen_time(4)
        m.stop_note(0, low_tone, instr_low_vel)
        m.stop_note(1, high_tone, instr_high_vel)
        soundcard.synth.play_track(m)
    def get_test_results_list(self):
        """Return a list [(intervalname, num correct, num asked), (...)]
        of the test you just completed.
        """
        retval = []
        for i in self.m_result:
            retval.append(
               (utils.int_to_intervalname(i),
                len([x for x in self.m_result[i] if x == i]),
                len(self.m_result[i])))
        return retval
    def get_test_results_dict(self):
        retval = {}
        for i in self.m_result:
            retval[i] = {'count': len(self.m_result[i]),
                         'correct': len([x for x in self.m_result[i] if x == i])}
            if retval[i]['count'] > 0:
                retval[i]['percentage'] = retval[i]['correct'] / retval[i]['count']
            else:
                retval[i]['percentage'] = 0.0
        return retval

class MelodicIntervalTest(Test):
    def __init__(self, exname):
        Test.__init__(self, exname)
        self.m_interval_list = range(-16, 0) + range(1, 17)
        random.shuffle(self.m_interval_list)
    def play_question(self):
        low_tone = mpd.MusicalPitch.new_from_int(self.m_tonika)
        high_tone = low_tone + self.m_interval
        m = soundcard.Track()
        m.set_bpm(self.get_int('config/default_bpm'))
        instr_low_vel = instr_high_vel = 100
        m.start_note(0, low_tone, instr_low_vel)
        m.notelen_time(4)
        m.stop_note(0, low_tone, instr_low_vel)
        m.start_note(1, high_tone, instr_high_vel)
        m.notelen_time(4)
        m.stop_note(1, high_tone, instr_high_vel)
        soundcard.synth.play_track(m)
    def get_test_results_list(self):
        """Return a list [(intervalname, num correct, num asked), (...)]
        of the test you just completed. Ignores direction, since we use
        the button interface to enter intervals, and it does not get
        any direction information.
        """
        retval = []
        for i in self.m_result:
            retval.append(
               (utils.int_to_intervalname(i),
                len([x for x in self.m_result[i] if x == abs(i)]),
                len(self.m_result[i])))
        return retval
    def get_test_results_dict(self):
        retval = {}
        for i in self.m_result:
            retval[i] = {'count': len(self.m_result[i]),
                         'correct': len([x for x in self.m_result[i] if x == abs(i)])}
            if retval[i]['count'] > 0:
                retval[i]['percentage'] = retval[i]['correct'] / retval[i]['count']
            else:
                retval[i]['percentage'] = 0.0
        return retval



class TestDialog(gtk.Window, cfg.ConfigUtils):
    def __init__(self, exname, title):
        gtk.Window.__init__(self)
        cfg.ConfigUtils.__init__(self, exname)
        self.set_title(title)
        self.set_resizable(False)
        self.connect('delete-event', self.on_delete_event)
        self.vbox = gtk.VBox()
        self.add(self.vbox)
        self.vbox.show()
        self.vbox.set_spacing(18)
        self.vbox.set_border_width(12)
        self.g_info = gtk.Label("""<span size='x-large'>%s</span>
Each interval is asked twice. Please complete the test. If you cancel, then the results of the questions asked so far, will be stored.""" % title)
        self.g_info.set_use_markup(True)
        self.g_info.set_line_wrap(True)
        self.vbox.pack_start(self.g_info)
        self.m_test = None
        self.g_start_test = gu.bButton(self.vbox, _("Start test"), 
            self.on_start_test, False, False)
    def on_delete_event(self, *v):
        if not self.m_test:
            gtk.main_quit()
            return False
        if self.m_test.m_status == 'completed':
            gtk.main_quit()
            return False
        m = gtk.MessageDialog(self, gtk.DIALOG_MODAL,
            gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
            _("Quit test? Your results so far will be stored."))
        ret = m.run()
        m.destroy()
        if ret == gtk.RESPONSE_YES:
            self.m_test.store_results()
            gtk.main_quit()
            return False
        return True
    def on_start_test(self, w):
        self.m_test = self.TestConstructor(self.m_exname)
        self.g_info.destroy()
        self.g_start_test.destroy()
        self.g_input = inputwidgets.IntervalButtonsWidget(self.m_exname,
           'intervals', self.click_on_interval, self.get_interval_input_list, [])
        self.vbox.pack_start(self.g_input, False)

        self.g_flash = gu.FlashBar()
        self.g_flash.show()
        self.vbox.pack_start(self.g_flash, False)

        self.g_input.show()
        self.g_button_box = hbox = gtk.HButtonBox()
        self.vbox.pack_start(hbox, False)
        self.g_repeat = gu.bButton(hbox, _("_Repeat"), 
            lambda w: self.m_test.play_question())
        hbox.show_all()
        self.m_test.new_question()
    def get_interval_input_list(self):
        return range(1, 17)
    def click_on_interval(self, btn, interval, ignore):
        if not self.m_test.m_status == 'asked':
            self.g_flash.flash(_("You get only one try per question"))
            return
        if self.m_test.guess_answer(interval):
            self.g_flash.flash(_("Correct"))
        else:
            self.g_flash.flash(_("Wrong"))
        if self.m_test.m_status == 'completed':
            self.g_flash.flash('Test completed')
            self.m_test.store_results()
            gtk.main_quit()
        else:
            gobject.timeout_add(500, self.m_test.new_question)


class HarmonicIntervalTestDialog(TestDialog):
    def __init__(self):
        TestDialog.__init__(self, 'harmonic-interval',
            _("Harmonic interval test"))
        self.TestConstructor = HarmonicIntervalTest


class MelodicIntervalTestDialog(TestDialog):
    def __init__(self):
        TestDialog.__init__(self, 'melodic-interval',
            _("Melodic interval test"))
        self.TestConstructor = MelodicIntervalTest


class TestStatisticsDialog(gtk.Dialog):
    def __init__(self, parent, title, data, show_percentages=True):
        gtk.Dialog.__init__(self, title)
        self.CONFIGURE = 100
        self.add_button(_("Practise selected intervals"), self.CONFIGURE)
        self.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
        self.set_default_size(200, 400)
        self.m_parent = parent
        self.g_liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT,
            gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_INT)
        for k in data:
            self.g_liststore.append((utils.int_to_intervalname (k, False, self.m_parent.m_exname == 'melodic-interval'),
                data[k]['correct'], data[k]['count'], "%.2f" % data[k]['percentage'], k))
        self.g_treeview = gtk.TreeView(self.g_liststore)
        self.g_treeview.set_headers_clickable(True)
        self.g_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn(_("Interval"), renderer, text=0)
        def sf0(store, iter1, iter2):
            return cmp(float(store.get(iter1, 4)[0]),
                       float(store.get(iter2, 4)[0]))
        self.g_liststore.set_sort_func(0, sf0)
        column.set_sort_column_id(0)
        self.g_treeview.append_column(column)
        column = gtk.TreeViewColumn(_("Correct"), renderer, text=1)
        column.set_sort_column_id(1)
        self.g_treeview.append_column(column)
        column = gtk.TreeViewColumn(_("Number asked"), renderer, text=2)
        column.set_sort_column_id(2)
        self.g_treeview.append_column(column)
        if show_percentages:
            column = gtk.TreeViewColumn(_("Percentage correct"), renderer, text=3)
            def sf(store, iter1, iter2):
                return cmp(float(store.get(iter1, 3)[0]),
                           float(store.get(iter2, 3)[0]))
            self.g_liststore.set_sort_func(3, sf)
            column.set_sort_column_id(3)
            self.g_treeview.append_column(column)
        self.g_treeview.show_all()
        sc = gtk.ScrolledWindow()
        sc.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
        sc.add(self.g_treeview)
        self.vbox.pack_start(sc)
        self.connect('response', self.on_response)
    def on_response(self, d, response_id):
        if response_id == gtk.RESPONSE_CLOSE:
            self.m_parent.destroy_test_stat_dlg()
        elif response_id == self.CONFIGURE:
            selected_list = self.g_treeview.get_selection().get_selected_rows()[1]
            selected_list = [i[0] for i in selected_list]
            cfg_str = []
            for x in selected_list:
                cfg_str.append(self.g_liststore.get(self.g_liststore.get_iter(x), 4)[0])
            if self.m_parent.m_t.m_exname == 'harmonic-interval':
                self.m_parent.m_t.configure_exercise(None, None,
                    {'intervals': str(cfg_str),
                     'disable_unused_intervals': 'true',
                    })
            else:
                assert self.m_parent.m_t.m_exname == 'melodic-interval'
                self.m_parent.m_t.configure_exercise(None, None,
                    {'ask_for_intervals_0': str(cfg_str),
                     'disable_unused_intervals': 'true',
                    })
            self.m_parent.destroy_test_stat_dlg()
            self.m_parent.m_t.m_app.handle_href("solfege:practise/%s"
                    % self.m_parent.m_t.m_exname)

