#!/usr/bin/env python
"""
Tiny Ear Trainer
Copyright (C) 2009 Jonas Wagner

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 with_statement

import os
from random import choice, randint
import time

import gtk
import gobject
from gobject import timeout_add
gobject.threads_init()

from . import mygtk
mygtk.install()

try:
    import json
except ImportError:
    import simplejson as json

from . import fluidsynth

NAME = "Tiny Ear Trainer"
WEBSITE = "http://29a.ch/tinyeartrainer/"
VERSION = "0.1.0 beta"
SETTINGS_FILE = os.path.expanduser("~/.tinyeartrainer")
INTERVALS = [
        ("perfect unison", "#000000"),
        ("minor second", "#aa0000"), # red
        ("major second", "#ff0000"),
        ("minor third", "#aa5f00"), # orange
        ("major third", "#ff7f00"),
        ("perfect fourth", "#FFFF00"), # yellow
        ("tritone", "#00aa00"), # green
        ("perfect fifth", "#00FF00"),
        ("minor sixth", "#00aaaa"), # cyan
        ("major sixth", "#00FFFF"),
        ("minor seventh", "#0000aa"), # blue
        ("major seventh", "#0000FF"),
        ("perfect octave", "#8B00FF")
]

NOTES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]

DEFAULT_SETTINGS = {
        "intervals": range(len(INTERVALS)),
        "velocity": 100,
        "solution_delay": 3000,
        "next_question_delay": 5000,
        "key": 60,
        "soundfont": "/usr/share/sounds/sf2/FluidR3_GM.sf2",
        "instrument": 1,
        "melodic": True,
        "harmonic": True,
}


def init_fluidsynth(soundfont, preset):
    print "starting fluidsynth"
    fs = fluidsynth.Synth()
    print "loading soundfont", soundfont
    fs.sfid = fs.sfload(soundfont)
    print "selecting program"
    fs.program_select(0, fs.sfid, 0, preset)
    fs.start()
    return fs


class Window(gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self)
        self.set_default_size(640, 480)
        self.set_title(NAME)
        try:
            self.set_icon(mygtk.iconfactory.get_icon("tinyeartrainer", 128))
        except gobject.GError:
            print "could not load icon"

        vbox = gtk.VBox()
        self.add(vbox)
        self.eventbox_interval = gtk.EventBox()
        self.eventbox_interval.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
        self.label_interval = gtk.Label()
        self.eventbox_interval.add(self.label_interval)
        vbox.pack_start(self.eventbox_interval)
        buttons = gtk.HButtonBox()

        self.play_button = gtk.ToggleButton(gtk.STOCK_MEDIA_PLAY)
        self.play_button.set_use_stock(True)
        self.play_button.connect("toggled", self.play)
        buttons.pack_start(self.play_button)

        self.button_settings = gtk.Button(stock=gtk.STOCK_PREFERENCES)
        self.button_settings.connect("clicked", self.show_settings)
        buttons.pack_start(self.button_settings)

        self.about_button = gtk.Button(stock=gtk.STOCK_ABOUT)
        self.about_button.connect("clicked", self.about)
        buttons.pack_start(self.about_button)

        vbox.pack_end(buttons, False, False)
        self.connect("destroy", self.quit)
        self.fs = None
        self.playing = False
        self.load_settings()

    def show_settings(self, sender):
        dialog = PreferenceDialog(self.settings)
        dialog.connect("response", self.save_preferences)
        dialog.run()
        dialog.destroy()

    def save_preferences(self, sender, response):
        if response == gtk.RESPONSE_APPLY:
            self.save_settings()
            self.load_settings()

    def load_settings(self):
        self.settings = dict(DEFAULT_SETTINGS)
        try:
            f = open(SETTINGS_FILE, "r")
        except IOError:
            print "settings not found"
            self.save_settings()
        else:
            with f:
                try:
                    settings = json.load(f)
                    self.settings.update(settings)
                except ValueError:
                    print "settings could not be parsed"
        if self.fs:
            # stopping fluidsynth
            print "deleting fluidsynth"
            self.fs.sfunload(self.fs.sfid)
            self.fs.delete()
            print "deleted fluidsynth"
        self.fs = init_fluidsynth(self.settings["soundfont"], self.settings["instrument"])

    def save_settings(self):
        print "saving settings %r" % self.settings
        try:
            f = open(SETTINGS_FILE, "w")
        except IOError:
            print "could not open settings for writing"
        with f:
            json.dump(self.settings, f)


    def about(self, sender):
        """show an about dialog"""
        about = gtk.AboutDialog()
        about.set_transient_for(self)
        about.set_logo(mygtk.iconfactory.get_icon("tinyeartrainer", 128))
        about.set_name(NAME)
        about.set_version(VERSION)
        about.set_authors(["Jonas Wagner"])
#        about.set_translator_credits(_("translator-credits"))
        about.set_copyright("Copyright (c) 2009 Jonas Wagner")
        about.set_website(WEBSITE)
        about.set_website_label(WEBSITE)
        about.set_license("""
Copyright (C) 2009 Jonas Wagner
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.
""")
        about.run()
        about.destroy()

    def play_note(self, note):
        print "playing", note
        self.fs.noteon(0, note, self.settings["velocity"])
        timeout_add(1000,
                lambda: self.fs.noteoff(0, note) and False)

    def play(self, sender):
        if sender.get_active():
            self.playing = True
            self.play_notes()

        else:
            self.playing = False

    def play_notes(self):
        self.eventbox_interval.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
        self.firstnote = self.settings["key"] or 60 + randint(0, 12)
        self.secondnote = self.firstnote + choice(self.settings["intervals"])
        self.label_interval.set_markup("")

        if self.settings["melodic"]:
            self.play_note(self.firstnote)
            timeout_add(1000, self.play_second_note)
        else:
            self.play_both_notes()
        return False

    def play_second_note(self):
        self.play_note(self.secondnote)
        if self.settings["solution_delay"] == 0:
            self.show_interval()
        timeout_add(1000, self.play_both_notes)
        return False

    def play_both_notes(self):
        if self.settings["harmonic"]:
            self.play_note(self.secondnote)
            self.play_note(self.firstnote)
        if self.settings["solution_delay"] or not self.settings["melodic"]:
            timeout_add(self.settings["solution_delay"], self.show_interval)
        return False

    def show_interval(self):
        name, color = INTERVALS[self.secondnote-self.firstnote]
        self.eventbox_interval.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
        self.label_interval.set_markup('<span size="50000">%s</span>' % (name))
        if self.playing:
            timeout_add(self.settings["next_question_delay"], self.play_notes)
        return False


    def quit(self, sender):
        print "deleting fluidsynth"
        if self.fs:
            self.playing = False
            self.hide()
            timeout_add(2000, lambda: self.fs.delete() and False)
            timeout_add(2001, lambda: gtk.main_quit() and False)
        else:
            gtk.main_quit()


class PreferenceDialog(gtk.Dialog):
    def __init__(self, settings):
        gtk.Dialog.__init__(self, "%s Preferences" % NAME)
        try:
            self.set_icon(mygtk.iconfactory.get_icon("tinyeartrainer", 128))
        except gobject.GError:
            print "could not load icon"
        self.set_default_size(400, 320)

        self.settings = settings
        self.set_border_width(4)
        self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        self.add_button(gtk.STOCK_APPLY, gtk.RESPONSE_APPLY)

        prefs = []

        self.soundfont = gtk.FileChooserButton("Select a soundfont")
        self.soundfont.set_filename(self.settings["soundfont"])
        prefs.append(("Soundfont", self.soundfont))

        self.learnmode = gtk.CheckButton()
        self.learnmode.set_active(self.settings["solution_delay"] == 0)
        prefs.append(("Learning Mode", self.learnmode))

        self.melodic = gtk.CheckButton()
        self.melodic.set_active(self.settings["melodic"])
        prefs.append(("Play individual notes", self.melodic))

        self.harmonic = gtk.CheckButton()
        self.harmonic.set_active(self.settings["harmonic"])
        prefs.append(("Play notes together", self.harmonic))

        self.key = gtk.combo_box_new_text()
        self.key.append_text("Random")
        for note in NOTES:
            self.key.append_text(note)
        if self.settings["key"]:
            self.key.set_active(self.settings["key"]-59)
        else:
            self.key.set_active(1)
        prefs.append(("Key", self.key))

        form = mygtk.form(prefs)
        self.vbox.pack_start(form)

        self.vbox.pack_start(gtk.Label("Intervals"), False, False)

        self.intervals = [(name, gtk.CheckButton()) for name, color in INTERVALS ]
        for interval in self.settings["intervals"]:
            self.intervals[interval][1].set_active(True)
        self.vbox.pack_start(mygtk.form(self.intervals))

        self.show_all()
        self.connect("response", self.save)

    def save(self, sender, response):
        if response ==  gtk.RESPONSE_APPLY:
            print "storing settings"
            self.settings["soundfont"] = self.soundfont.get_filename()
            self.settings["melodic"] = self.melodic.get_active()
            self.settings["harmonic"] = self.harmonic.get_active()
            self.settings["key"] = 59 + self.key.get_active()
            if self.settings["key"] == 59: # random
                self.settings["key"] = None
            if self.learnmode.get_active():
                print "learnmode active"
                self.settings["solution_delay"] = 0
            else:
                print "learnmode inactive", DEFAULT_SETTINGS["solution_delay"]
                self.settings["solution_delay"] = DEFAULT_SETTINGS["solution_delay"]
            self.settings["intervals"] = [i for i in range(len(INTERVALS)) if self.intervals[i][1].get_active() ]


def main():
    win = Window()
    win.show_all()
    gtk.main()

if __name__ == "__main__":
    main()
