import cbox
from gui_tools import *

#################################################################################################################################

class EffectWindow(Gtk.Window):
    engine_name = None
    
    def __init__(self, location, main_window, path):
        Gtk.Window.__init__(self, Gtk.WindowType.TOPLEVEL)
        self.set_type_hint(Gdk.WindowTypeHint.UTILITY)
        self.set_transient_for(main_window)
        self.main_window = main_window
        self.path = path
        self.vpath = cbox.VarPath(path)
        self.set_title("%s - %s" % (self.effect_name, location))
        self.vbox = Gtk.VBox()
        menu_bar = Gtk.MenuBar()
        menu_bar.append(create_menu("_Effect", [
            ("_Save as...", self.on_effect_save_as if self.engine_name is not None else None),
            ("_Close", lambda w: self.destroy()),
        ]))
        self.vbox.pack_start(menu_bar, False, False, 0)
        if hasattr(self, 'params'):
            values = cbox.GetThings(self.path + "/status", [p.name for p in self.params], [])
            self.refreshers = []
            t = Gtk.Table(2, len(self.params))
            for i in range(len(self.params)):
                p = self.params[i]
                t.attach(p.create_label(), 0, 1, i, i+1, Gtk.AttachOptions.SHRINK | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
                widget, refresher = p.create_widget(self.vpath)
                refresher(values)
                t.attach(widget, 1, 2, i, i+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
                self.refreshers.append(refresher)
            self.vbox.pack_start(t, True, True, 5)
        self.add(self.vbox)
        
    def create_param_table(self, cols, rows, values, extra_rows = 0):
        t = Gtk.Table(4, rows + 1 + extra_rows)
        self.cols = cols
        self.table_refreshers = []
        for i in range(len(self.cols)):
            par = self.cols[i]
            t.attach(par.create_label(), i, i + 1, 0, 1, Gtk.AttachOptions.SHRINK | Gtk.AttachOptions.FILL)
            for j in range(rows):
                widget, refresher = par.create_widget(self.vpath.plus(None, j))
                t.attach(widget, i, i + 1, j + 1, j + 2, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL)
                refresher(values)
                self.table_refreshers.append(refresher)
        return t
        
    def get_save_params(self):
        if hasattr(self, 'params'):
            values = cbox.GetThings(self.path + "/status", [p.name for p in self.params], [])
            result = {'engine' : self.engine_name}
            for p in self.params:
                result[p.name] = str(getattr(values, p.name))
            return result
        return None
        
    def refresh(self):
        if hasattr(self, 'params'):
            values = cbox.GetThings(self.path + "/status", [p.name for p in self.params], [])
            for refresher in self.refreshers:
                refresher(values)
                
    def on_effect_save_as(self, w):
        data = self.get_save_params()
        if data is None:
            print ("Save not implemented for this effect")
            return
            
        dlg = SaveConfigObjectDialog(self, "Select name for effect preset")
        try:
            if dlg.run() == Gtk.ResponseType.OK and dlg.get_name() != "":
                cs = cbox.CfgSection("fxpreset:" + dlg.get_name())
                for name in sorted(data.keys()):
                    cs[name] = data[name]
                cbox.Config.save()
        finally:
            dlg.destroy()
                
#################################################################################################################################

class PhaserWindow(EffectWindow):
    params = [
        MappedSliderRow("Center", "center_freq", LogMapper(100, 2000, freq_format)),
        SliderRow("Mod depth", "mod_depth", 0, 7200),
        SliderRow("Feedback", "fb_amt", -1, 1),
        MappedSliderRow("LFO frequency", "lfo_freq", lfo_freq_mapper),
        SliderRow("Stereo", "stereo_phase", 0, 360),
        SliderRow("Wet/dry", "wet_dry", 0, 1),
        IntSliderRow("Stages", "stages", 1, 12)
    ]
    engine_name = "phaser"
    effect_name = "Phaser"
    
class ChorusWindow(EffectWindow):
    params = [
        SliderRow("Min. delay", "min_delay", 1, 20),
        SliderRow("Mod depth", "mod_depth", 1, 20),
        MappedSliderRow("LFO frequency", "lfo_freq", lfo_freq_mapper),
        SliderRow("Stereo", "stereo_phase", 0, 360),
        SliderRow("Wet/dry", "wet_dry", 0, 1)
    ]
    engine_name = "chorus"
    effect_name = "Chorus"

class DelayWindow(EffectWindow):
    params = [
        SliderRow("Delay time (ms)", "time", 1, 1000),
        SliderRow("Feedback", "fb_amt", 0, 1),
        SliderRow("Wet/dry", "wet_dry", 0, 1)
    ]
    engine_name = "delay"
    effect_name = "Delay"

class ReverbWindow(EffectWindow):
    params = [
        SliderRow("Decay time", "decay_time", 500, 5000),
        SliderRow("Dry amount", "dry_amt", -100, 12),
        SliderRow("Wet amount", "wet_amt", -100, 12),
        MappedSliderRow("Lowpass", "lowpass", LogMapper(300, 20000, freq_format)),
        MappedSliderRow("Highpass", "highpass", LogMapper(30, 2000, freq_format))
    ]
    engine_name = "reverb"
    effect_name = "Reverb"

class ToneControlWindow(EffectWindow):
    params = [
        MappedSliderRow("Lowpass", "lowpass", LogMapper(300, 20000, freq_format)),
        MappedSliderRow("Highpass", "highpass", LogMapper(30, 2000, freq_format))
    ]
    engine_name = "tone_control"
    effect_name = "Tone Control"

class CompressorWindow(EffectWindow):
    params = [
        SliderRow("Threshold", "threshold", -100, 12),
        MappedSliderRow("Ratio", "ratio", LogMapper(1, 100, "%0.2f")),
        MappedSliderRow("Attack", "attack", LogMapper(1, 1000, ms_format)),
        MappedSliderRow("Release", "release", LogMapper(1, 1000, ms_format)),
        SliderRow("Make-up gain", "makeup", -48, 48),
    ]
    engine_name = "compressor"
    effect_name = "Compressor"

class GateWindow(EffectWindow):
    params = [
        SliderRow("Threshold", "threshold", -100, 12),
        MappedSliderRow("Ratio", "ratio", LogMapper(1, 100, "%0.2f")),
        MappedSliderRow("Attack", "attack", LogMapper(1, 1000, ms_format)),
        MappedSliderRow("Hold", "hold", LogMapper(1, 1000, ms_format)),
        MappedSliderRow("Release", "release", LogMapper(1, 1000, ms_format)),
    ]
    engine_name = "gate"
    effect_name = "Gate"

class DistortionWindow(EffectWindow):
    params = [
        SliderRow("Drive", "drive", -36, 36),
        SliderRow("Shape", "shape", -1, 2),
    ]
    engine_name = "distortion"
    effect_name = "Distortion"

class FuzzWindow(EffectWindow):
    params = [
        SliderRow("Drive", "drive", -36, 36),
        SliderRow("Wet/dry", "wet_dry", 0, 1),
        SliderRow("Rectify", "rectify", 0, 1),
        MappedSliderRow("Pre freq", "band", LogMapper(100, 5000, freq_format)),
        SliderRow("Pre width", "bandwidth", 0.25, 4),
        MappedSliderRow("Post freq", "band2", LogMapper(100, 5000, freq_format)),
        SliderRow("Post width", "bandwidth2", 0.25, 4),
    ]
    engine_name = "fuzz"
    effect_name = "Fuzz"

class LimiterWindow(EffectWindow):
    params = [
        SliderRow("Threshold", "threshold", -100, 12),
        MappedSliderRow("Attack", "attack", LogMapper(1, 1000, ms_format)),
        MappedSliderRow("Release", "release", LogMapper(1, 5000, ms_format)),
    ]
    engine_name = "limiter"
    effect_name = "Limiter"

class EQCommon(object):
    columns = [
        CheckBoxRow("Active", "active"),
        MappedSliderRow("Center Freq", "center", filter_freq_mapper),
        MappedSliderRow("Filter Q", "q", LogMapper(0.01, 100, "%f")),
        SliderRow("Gain", "gain", -36, 36),
    ]
    def get_save_params(self):
        values = cbox.GetThings(self.path + "/status", ["%active", "%center", "%q", "%gain"], [])
        result = {'engine':self.engine_name}
        for row in range(self.bands):
            row2 = 1 + row
            result['band%s_active' % row2] = values.active[row]
            result['band%s_center' % row2] = values.center[row]
            result['band%s_q' % row2] = values.q[row]
            result['band%s_gain' % row2] = values.gain[row]
        return result

class EQWindow(EffectWindow, EQCommon):
    def __init__(self, instrument, main_window, path):
        EffectWindow.__init__(self, instrument, main_window, path)
        values = cbox.GetThings(self.path + "/status", ["%active", "%center", "%q", "%gain"], [])
        self.vbox.add(self.create_param_table(self.columns, 4, values))
    def get_save_params(self):
        return EQCommon.get_save_params(self)
    effect_name = "Equalizer"
    engine_name = "parametric_eq"
    bands = 4
        
class FBRWindow(EffectWindow, EQCommon):
    effect_name = "Feedback Reduction"
    engine_name = "feedback_reducer"
    bands = 16
    
    def __init__(self, instrument, main_window, path):
        EffectWindow.__init__(self, instrument, main_window, path)
        values = cbox.GetThings(self.path + "/status", ["%active", "%center", "%q", "%gain"], [])
        t = self.create_param_table(self.columns, 16, values, 1)        
        self.vbox.add(t)
        self.ready_label = Gtk.Label("-")
        t.attach(self.ready_label, 0, 2, 17, 18)
        set_timer(self, 100, self.update)
        sbutton = Gtk.Button.new_with_mnemonic("_Start")
        sbutton.connect("clicked", lambda button, path: cbox.do_cmd(path + "/start", None, []), self.path)
        t.attach(sbutton, 2, 4, 17, 18)
        
    def refresh_table(self):
        values = cbox.GetThings(self.path + "/status", ["%active", "%center", "%q", "%gain"], [])
        for refresher in self.table_refreshers:
            refresher(values)
        
    def update(self):
        values = cbox.GetThings(self.path + "/status", ["finished", "refresh"], [])
        if values.refresh:
            self.refresh_table()
        
        if values.finished > 0:
            self.ready_label.set_text("Ready")
        else:
            self.ready_label.set_text("Not Ready")
        return True
        
    def get_save_params(self):
        return EQCommon.get_save_params(self)

class FXChainWindow(EffectWindow):
    effect_name = "Effect chain"
    
    def __init__(self, instrument, main_window, path):
        EffectWindow.__init__(self, instrument, main_window, path)
        self.fx_table = None
        self.choosers = []
        self.refresh_table()
        
    def refresh_table(self):
        res = cbox.GetThings(self.path + "/status", ["*module", "%bypass"], [])
        values = res.module
        bypass = res.bypass
        fx_count = len(values)
        t = Gtk.Table(fx_count + 2, 9)
        for c in self.choosers:
            c.close_popup()
        self.choosers = []
        for i in range(1, fx_count + 1):
            engine, preset = values[i - 1]
            chooser = InsertEffectChooser("%s/module/%s" % (self.path, i), "%s: slot %s" % (self.get_title(), i), engine, preset, bypass[i], self.main_window)
            t.attach(chooser.fx_engine, 0, 1, i, i + 1, 0, Gtk.AttachOptions.SHRINK)
            t.attach(chooser.fx_preset, 1, 2, i, i + 1, 0, Gtk.AttachOptions.SHRINK)
            t.attach(chooser.fx_edit, 2, 3, i, i + 1, 0, Gtk.AttachOptions.SHRINK)
            t.attach(chooser.fx_bypass, 3, 4, i, i + 1, 0, Gtk.AttachOptions.SHRINK)
            buttons = [
                ("+", self.on_add_clicked, lambda pos: True),
                ("-", self.on_delete_clicked, lambda pos: True),
                ("Up", self.on_up_clicked, lambda pos: pos > 1),
                ("Down", self.on_down_clicked, lambda pos: pos < fx_count)
            ]
            for j in range(len(buttons)):
                label, method, cond = buttons[j]
                if not cond(i):
                    continue
                button = Gtk.Button(label)
                button.connect('clicked', lambda button, method, pos: method(pos), method, i)
                t.attach(button, 4 + j, 5 + j, i, i + 1, 0, Gtk.AttachOptions.SHRINK)
            self.choosers.append(chooser)
        button = Gtk.Button("+")
        button.connect('clicked', lambda button, pos: self.on_add_clicked(pos), fx_count + 1)
        t.attach(button, 3, 4, fx_count + 1, fx_count + 2, 0, Gtk.AttachOptions.SHRINK)
        if self.fx_table is not None:
            self.vbox.remove(self.fx_table)
        self.vbox.pack_start(t, True, True, 5)
        t.show_all()
        self.fx_table = t
    def on_add_clicked(self, pos):
        cbox.do_cmd(self.path + "/insert", None, [pos])
        self.refresh_table()
    def on_delete_clicked(self, pos):
        cbox.do_cmd(self.path + "/delete", None, [pos])
        self.refresh_table()
    def on_up_clicked(self, pos):
        cbox.do_cmd(self.path + "/move", None, [pos, pos - 1])
        self.refresh_table()
    def on_down_clicked(self, pos):
        cbox.do_cmd(self.path + "/move", None, [pos, pos + 1])
        self.refresh_table()


#################################################################################################################################

effect_engines = ['', 'phaser', 'reverb', 'chorus', 'feedback_reducer', 'tone_control', 'delay', 'parametric_eq', 'compressor', 'gate', 'distortion', 'fuzz', 'fxchain', 'limiter']

effect_window_map = {
    'phaser': PhaserWindow,
    'chorus': ChorusWindow,
    'delay': DelayWindow,
    'reverb' : ReverbWindow,
    'feedback_reducer': FBRWindow,
    'parametric_eq': EQWindow,
    'tone_control': ToneControlWindow,
    'compressor': CompressorWindow,
    'gate': GateWindow,
    'distortion': DistortionWindow,
    'fuzz': FuzzWindow,
    'limiter': LimiterWindow,
    'fxchain': FXChainWindow,
}

#################################################################################################################################

class EffectListModel(Gtk.ListStore):
    def __init__(self):
        self.presets = {}
        Gtk.ListStore.__init__(self, GObject.TYPE_STRING)
        for engine in effect_engines:
            self.presets[engine] = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING)
            self.append((engine,))
            
        for preset in cbox.Config.sections("fxpreset:"):
            engine = preset["engine"]
            if engine in self.presets:
                title = preset.title if hasattr(preset, 'title') else preset.name[9:] 
                self.presets[engine].append((preset.name[9:], title))
    
    def get_model_for_engine(self, engine):
        return self.presets[engine]

effect_list_model = EffectListModel()

#################################################################################################################################

class InsertEffectChooser(object):
    def __init__(self, opath, location, engine, preset, bypass, main_window):
        self.opath = opath
        self.location = location
        self.main_window = main_window
        self.popup = None
        
        self.fx_engine = standard_combo(effect_list_model, ls_index(effect_list_model, engine, 0), width = 120)
        self.fx_engine.connect('changed', self.fx_engine_changed)
        
        if engine in effect_engines:
            model = effect_list_model.get_model_for_engine(engine)
            self.fx_preset = standard_combo(model, active_item_lookup = preset, column = 1, lookup_column = 0, width = 120)
        else:
            self.fx_preset = standard_combo(None, active_item = 0, column = 1, width = 120)
        self.fx_preset.connect('changed', self.fx_preset_changed)

        self.fx_edit = Gtk.Button.new_with_mnemonic("_Edit")
        self.fx_edit.connect("clicked", self.edit_effect_clicked)
        self.fx_edit.set_sensitive(engine in effect_window_map)
        
        self.fx_bypass = Gtk.ToggleButton.new_with_mnemonic("_Bypass")
        self.fx_bypass.set_active(bypass > 0)
        self.fx_bypass.connect("clicked", self.bypass_effect_clicked)
        
    def edit_effect_clicked(self, button):
        if self.popup is not None:
            self.popup.present()
            return
        engine = cbox.GetThings(self.opath + "/status", ['insert_engine'], []).insert_engine
        wclass = effect_window_map[engine]
        popup = wclass(self.location, self.main_window, "%s/engine" % self.opath)
        popup.show_all()
        popup.present()
        popup.connect('delete_event', self.on_popup_closed)
        self.popup = popup
            
    def fx_engine_changed(self, combo):
        if self.popup is not None:
            self.popup.destroy()
            self.popup = None
            
        engine = combo.get_model()[combo.get_active()][0]
        cbox.do_cmd(self.opath + '/insert_engine', None, [engine])
        self.fx_preset.set_model(effect_list_model.get_model_for_engine(engine))
        self.fx_preset.set_active(0)
        self.fx_edit.set_sensitive(engine in effect_window_map)
        
    def fx_preset_changed(self, combo):
        if combo.get_active() >= 0:
            cbox.do_cmd(self.opath + '/insert_preset', None, [combo.get_model()[combo.get_active()][0]])
        if self.popup is not None:
            self.popup.refresh()

    def on_popup_closed(self, popup, event):
        self.popup = None
        
    def close_popup(self):
        if self.popup is not None:
            self.popup.destroy();

    def bypass_effect_clicked(self, button):
        cbox.do_cmd(self.opath + "/set_bypass", None, [1 if button.get_active() else 0])

#################################################################################################################################

class LoadEffectDialog(SelectObjectDialog):
    title = "Load an aux effect"
    def __init__(self, parent):
        SelectObjectDialog.__init__(self, parent)
    def update_model(self, model):
        for s in cbox.Config.sections("fxpreset:"):
            title = s["title"]
            model.append((s.name[9:], s['engine'], s.name, title))

