import cbox
import math
import re
from gi.repository import GObject, Gdk, Gtk, GLib as glib

# Compatibility stuff
Gtk.TreeView.insert_column_with_attributes = lambda self, col, title, renderer, xarg = None, **kwargs: self.insert_column(Gtk.TreeViewColumn(title = title, cell_renderer = renderer, **kwargs), col)

def callback(cmd, cb, args):
    print ("%s %s %s" % (cmd, cb, args))

def bold_label(text, halign = 1):
    l = Gtk.Label()
    l.set_markup("<b>%s</b>" % text)
    l.set_alignment(halign, 0.5)
    return l

def left_label(text):
    l = Gtk.Label(text)
    l.set_alignment(0, 0.5)
    return l

def standard_hslider(adj):
    sc = Gtk.HScale(adjustment = adj)
    sc.set_size_request(160, -1)
    sc.set_value_pos(Gtk.PositionType.RIGHT)
    return sc

notenames = {
    'c' : 0, 'c#' : 1, 'db' : 1,
    'd' : 2, 'd#' : 3, 'eb' : 3,
    'e' : 4, 'e#' : 5, 'fb' : 5,
    'f' : 5, 'f#' : 6, 'gb' : 6,
    'g' : 7, 'g#' : 8, 'ab' : 8,
    'a' : 9, 'a#' : 10, 'bb' : 10,
    'b' : 11, 'b#' : 0, 'cb' : 11,
}

def sfznote2value(note):
    if re.match("[0-9]+$", note):
        return int(rdata['key'])
    else:
        g = re.match("([cdefgab](#|b)?)(-?[0-9])", note)
        if g is None:
            raise ValueError(note)
        return 12 + int(g.group(3)) * 12 + notenames[g.group(1).lower()]

class LogMapper:
    def __init__(self, min, max, format = "%f"):
        self.min = min
        self.max = max
        self.format_string = format
    def map(self, value):
        return float(self.min) * ((float(self.max) / self.min) ** (value / 100.0))
    def unmap(self, value):
        return math.log(value / float(self.min)) * 100 / math.log(float(self.max) / self.min)
    def format_value(self, value):
        return self.format_string % self.map(value)
    

freq_format = "%0.1f"
ms_format = "%0.1f ms"
lfo_freq_mapper = LogMapper(0.01, 20, "%0.2f")
env_mapper = LogMapper(0.002, 20, "%f")
filter_freq_mapper = LogMapper(20, 20000, "%0.1f Hz")

def standard_mapped_hslider(adj, mapper):
    sc = Gtk.HScale(adjustment = adj)
    sc.set_size_request(160, -1)
    sc.set_value_pos(Gtk.PositionType.RIGHT)
    sc.connect('format-value', lambda scale, value, mapper: mapper.format_value(value), mapper)
    return sc

def standard_align(w, xo, yo, xs, ys):
    a = Gtk.Alignment(xalign = xo, yalign = yo, xscale = xs, yscale = ys)
    a.add(w)
    return a
    
def standard_vscroll_window(width, height, content = None):
    scroller = Gtk.ScrolledWindow()
    scroller.set_size_request(width, height)
    scroller.set_shadow_type(Gtk.ShadowType.NONE);
    scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
    if content is not None:
        scroller.add_with_viewport(content)
    return scroller

def standard_combo(model, active_item = None, column = 0, active_item_lookup = None, lookup_column = None, width = None):
    cb = Gtk.ComboBox(model = model)
    if active_item_lookup is not None:
        if lookup_column is None:
            lookup_column = column
        active_item = ls_index(model, active_item_lookup, lookup_column)
    if active_item is not None:
        cb.set_active(active_item)
    cell = Gtk.CellRendererText()
    if width is not None:
        cb.set_size_request(width, -1)
    cb.pack_start(cell, True)
    cb.add_attribute(cell, 'text', column)
    return cb
    
def ls_index(list_store, value, column):
    for i in range(len(list_store)):
        if list_store[i][column] == value:
            return i
    return None

def standard_filter(patterns, name):
    f = Gtk.FileFilter()
    for p in patterns:
        f.add_pattern(p)
    f.set_name(name)
    return f

def checkbox_changed_bool(checkbox, vpath):
    vpath.set(1 if checkbox.get_active() else 0)

def adjustment_changed_int(adjustment, vpath):
    vpath.set(int(adjustment.get_value()))

def adjustment_changed_float(adjustment, vpath):
    vpath.set(float(adjustment.get_value()))

def adjustment_changed_float_mapped(adjustment, vpath, mapper):
    vpath.set(mapper.map(adjustment.get_value()))

def combo_value_changed(combo, vpath, value_offset = 0):
    if combo.get_active() != -1:
        vpath.set(value_offset + combo.get_active())

def combo_value_changed_use_column(combo, vpath, column):
    if combo.get_active() != -1:
        vpath.set(combo.get_model()[combo.get_active()][column])

def tree_toggle_changed_bool(renderer, tree_path, model, opath, column):
    model[int(tree_path)][column] = not model[int(tree_path)][column]
    cbox.do_cmd(model.make_row_item(opath, tree_path), None, [1 if model[int(tree_path)][column] else 0])
        
def tree_combo_changed(renderer, tree_path, new_value, model, opath, column):
    new_value = renderer.get_property('model')[new_value][0]
    model[int(tree_path)][column] = new_value
    cbox.do_cmd(model.make_row_item(opath, tree_path), None, [new_value])
        
def standard_toggle_renderer(list_model, path, column):
    toggle = Gtk.CellRendererToggle()
    toggle.connect('toggled', tree_toggle_changed_bool, list_model, path, column)
    return toggle

def standard_combo_renderer(list_model, model, path, column):
    combo = Gtk.CellRendererCombo()
    combo.set_property('model', model)
    combo.set_property('editable', True)
    combo.set_property('has-entry', False)
    combo.set_property('text-column', 1)
    combo.set_property('mode', Gtk.CellRendererMode.EDITABLE)
    combo.connect('changed', tree_combo_changed, list_model, path, column)
    return combo

def add_display_row(t, row, label, path, values, item):
    t.attach(bold_label(label), 0, 1, row, row+1, Gtk.AttachOptions.SHRINK | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
    w = left_label(getattr(values, item))
    t.attach(w, 1, 2, row, row+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
    return w
    
def set_timer(widget, time, func, *args):
    refresh_id = glib.timeout_add(time, func, *args)
    widget.connect('destroy', lambda obj, id: glib.source_remove(id), refresh_id)

def create_menu(title, items):
    menuitem = Gtk.MenuItem.new_with_mnemonic(title)
    if items is not None:
        menu = Gtk.Menu()
        menuitem.set_submenu(menu)
        for label, meth in items:
            mit = Gtk.MenuItem.new_with_mnemonic(label)
            if meth is None:
                mit.set_sensitive(False)
            else:
                mit.connect('activate', meth)
            menu.append(mit)
    return menuitem

def note_to_name(note):
    if note < 0:
        return "N/A"
    n = note % 12
    return ("C C#D D#E F F#G G#A A#B "[n * 2 : n * 2 + 2]) + str((note // 12) - 2)

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

class TableRowWidget:
    def __init__(self, label, name, **kwargs):
        self.label = label
        self.name = name
        self.kwargs = kwargs
    def get_with_default(self, name, def_value):
        return self.kwargs[name] if name in self.kwargs else def_value
    def create_label(self):
        return bold_label(self.label)
    def has_attr(self, values):
        if type(values) is dict:
            return self.name in values
        return hasattr(values, self.name)
    def get_attr(self, values):
        if type(values) is dict:
            return values[self.name]
        return getattr(values, self.name)
    def get_value(self, values, vpath):
        if len(vpath.args) == 1:
            return self.get_attr(values)[vpath.args[0]]
        return self.get_attr(values)
    def add_row(self, table, row, vpath, values):
        table.attach(self.create_label(), 0, 1, row, row + 1, Gtk.AttachOptions.SHRINK | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
        widget, refresher = self.create_widget(vpath)
        table.attach(widget, 1, 2, row, row + 1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
        if values is not None:
            refresher(values)
        return refresher
    def update_sensitive(self, widget, values):
        sensitive = (values is not None) and (self.get_with_default('setter', 0) is not None) and self.has_attr(values)
        widget.set_sensitive(sensitive)
        return sensitive

class SliderRow(TableRowWidget):
    def __init__(self, label, name, minv, maxv, **kwargs):
        TableRowWidget.__init__(self, label, name, **kwargs)
        self.minv = minv
        self.maxv = maxv
    def create_widget(self, vpath):
        setter = self.get_with_default('setter', adjustment_changed_float)
        step = self.kwargs['step'] if 'step' in self.kwargs else 1
        adj = Gtk.Adjustment(self.minv, self.minv, self.maxv, step, 6, 0)
        slider = standard_hslider(adj)
        if 'digits' in self.kwargs:
            slider.set_digits(self.kwargs['digits'])
        if setter is not None:
            adj.connect("value_changed", setter, vpath.plus(self.name))
        else:
            slider.set_sensitive(False)
        def refresher(values):
            if self.update_sensitive(slider, values):
                adj.set_value(self.get_value(values, vpath))
        return (slider, refresher)

class IntSliderRow(SliderRow):
    def __init__(self, label, name, minv, maxv, **kwargs):
        SliderRow.__init__(self, label, name, minv, maxv, setter = adjustment_changed_int, **kwargs)
    def create_widget(self, vpath):
        (slider, refresher) = SliderRow.create_widget(self, vpath)
        slider.connect('change-value', self.on_change_value)
        slider.set_digits(0)
        return slider, refresher
    def on_change_value(self, range, scroll, value):
        range.set_value(int(value))
        return True
        

class MappedSliderRow(TableRowWidget):
    def __init__(self, label, name, mapper, **kwargs):
        TableRowWidget.__init__(self, label, name, **kwargs)
        self.mapper = mapper
    def create_widget(self, vpath):
        setter = self.get_with_default('setter', adjustment_changed_float_mapped)
        adj = Gtk.Adjustment(0, 0, 100, 1, 6, 0)
        slider = standard_mapped_hslider(adj, self.mapper)
        if setter is not None:
            adj.connect("value_changed", setter, vpath.plus(self.name), self.mapper)
        else:
            slider.set_sensitive(False)
        def refresher(values):
            if self.update_sensitive(slider, values):
                adj.set_value(self.mapper.unmap(self.get_value(values, vpath)))
        return (slider, refresher)

class CheckBoxRow(TableRowWidget):
    def create_widget(self, vpath):
        widget = Gtk.CheckButton(self.label)
        widget.connect("clicked", checkbox_changed_bool, vpath.plus(self.name))
        def refresher(values):
            if self.update_sensitive(widget, values):
                widget.set_active(self.get_value(values, vpath) > 0)
        return (widget, refresher)

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

class SelectObjectDialog(Gtk.Dialog):
    def __init__(self, parent):
        Gtk.Dialog.__init__(self, self.title, parent=parent, modal=True)
        self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK)
        self.set_default_response(Gtk.ResponseType.OK)
        model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING)
        
        self.update_model(model)
                
        scroll = Gtk.ScrolledWindow()
        scenes = Gtk.TreeView(model)
        scenes.insert_column_with_attributes(0, "Name", Gtk.CellRendererText(), text=0)
        scenes.insert_column_with_attributes(1, "Title", Gtk.CellRendererText(), text=3)
        scenes.insert_column_with_attributes(2, "Type", Gtk.CellRendererText(), text=1)
        scenes.get_column(0).set_property('min_width', 150)
        scenes.get_column(1).set_property('min_width', 300)
        scenes.get_column(2).set_property('min_width', 150)
        scroll.add(scenes)
        self.vbox.pack_start(scroll, False, False, 0)
        scenes.show()
        scroll.set_size_request(640, 500)
        scroll.show()
        self.scenes = scenes
        self.scenes.connect('cursor-changed', lambda w: self.update_default_button())
        self.scenes.connect('row-activated', lambda w, path, column: self.response(Gtk.ResponseType.OK))
        self.scenes.set_cursor((0,), scenes.get_column(0))
        self.scenes.grab_focus()
        self.update_default_button()

    def get_selected_object(self):
        return self.scenes.get_model()[self.scenes.get_cursor()[0].get_indices()[0]]
    def update_default_button(self):
        self.set_response_sensitive(Gtk.ResponseType.OK, self.scenes.get_cursor()[1] is not None)

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

class SaveConfigObjectDialog(Gtk.Dialog):
    def __init__(self, parent, title):
        Gtk.Dialog.__init__(self, title, parent, Gtk.DialogFlags.MODAL, 
            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK))
        self.set_default_response(Gtk.ResponseType.OK)
        
        l = Gtk.Label()
        l.set_text_with_mnemonic("_Name")
        e = Gtk.Entry()
        self.entry = e
        e.set_activates_default(True)
        e.connect('changed', self.on_entry_changed)
        row = Gtk.HBox()
        row.pack_start(l, False, False, 5)
        row.pack_start(e, True, True, 5)
        row.show_all()
        self.vbox.pack_start(row, True, True, 0)
        e.grab_focus()
        self.on_entry_changed(e)
    def get_name(self):
        return self.entry.get_text()
    def on_entry_changed(self, w):
        self.set_response_sensitive(Gtk.ResponseType.OK, w.get_text() != '')
