#!/usr/bin/env python

data = {}

def text_height(width, value):
    assert width > 0

    if len(value) < width:
        return 1
    else:
        return len(value) / width + 2

def load_config(filename):
    global data

    import json

    with open(filename) as f:
        data = json.load(f)

def is_special(key):
    return key.endswith("_doc") or key.endswith("_type") or key.endswith("_units") or key.endswith("_option") or key.endswith("_choices") or key == "long_name"

def path(event):
    return event.widget.focus().split(".")

def parameter_info(event):
    d = data.copy()
    p = path(event)

    name = p[-1]
    parent = p[:-1]

    for k in parent:
        d = d[k]

    result = {}

    result["name"]   = ".".join(p)
    result["value"]  = d[name]
    result["type"]   = d[name + "_type"]
    result["doc"]    = d[name + "_doc"]

    try:
        result["option"] = "-" + d[name + "_option"]
    except KeyError:
        result["option"] = "-" + result["name"]

    if result["type"] == "keyword":
        result["choices"] = d[name + "_choices"]

    if result["type"] not in ("keyword", "boolean", "string"):
        result["units"]  = d[name + "_units"]

    return result

def keyword_handler(info, window, first_row):
    value = tk.StringVar()
    value.set(info["value"])

    window.value = value

    ttk.Label(window, text="Value:").grid(row=first_row, column=0, sticky=tk.W, padx=10)

    j = first_row
    for v in info["choices"].split(","):
        ttk.Radiobutton(window, text=v, variable=value, value=v).grid(row=j, column=1, sticky=tk.W, padx=10)
        j += 1

    return j

def keyword_dialog(event):
    generic_dialog(event, keyword_handler)

def generic_dialog(event, func):

    info = parameter_info(event)

    treeview = event.widget
    item_id = treeview.focus()

    root = treeview.master.master

    window = tk.Toplevel(root)
    window.wm_title(info["name"])

    frame = ttk.Frame(window)
    frame.grid(padx=5, pady=5)

    j = 0

    ttk.Label(frame, text="Name:").grid(row=j, sticky=tk.W, padx=10)
    ttk.Label(frame, text=info["name"]).grid(row=j, column=1, sticky=tk.W, padx=10)
    j += 1

    ttk.Label(frame, text="Command-line option:").grid(row=j, sticky=tk.W, padx=10)
    ttk.Label(frame, text=info["option"]).grid(row=j, column=1, sticky=tk.W, padx=10)
    j += 1

    doc_text = info["doc"]
    ttk.Label(frame, text="Description:").grid(row=j, column=0, sticky=tk.W+tk.N, padx=10)
    doc = tk.Text(frame, width=60, height=text_height(60, doc_text), wrap="word")
    doc.insert(tk.END, doc_text)
    doc.config(state=tk.DISABLED)
    doc.grid(row=j, column=1, sticky=tk.W, padx=10)
    j += 1

    # spacer
    ttk.Frame(frame, height=5).grid(row=j, column=0, columnspan=2)
    j += 1

    j = func(info, frame, j)

    # the footer contains "OK" and "Cancel" buttons
    footer = ttk.Frame(frame)
    footer.grid(row=j, column=0, columnspan=2)

    def is_valid(T, value):
        if T == "scalar":
            try:
                dummy = float(value)
                return True
            except:
                return False
        elif T == "integer":
            try:
                dummy = int(value)
                return True
            except:
                return False
        elif T == "boolean":
            return value in ("True", "False")
        else:
            return True         # all strings are valid

    def set_item(name, value):
        print "setting %s to %s" % (name, value)

    def update_treeview(tree, name, value):
        print "setting %s to %s in the tree view" % (name, value)

    def ok_handler():
        if info["type"] in ("keyword", "boolean"):
            value = frame.value.get()
        else:
            value = frame.value.get(1.0, "end").strip()

        if is_valid(info["type"], value):
            if value != str(info["value"]):
                set_item(info["name"], value)
                update_treeview(treeview, info["name"], value)
            window.destroy()
        else:
            tkMessageBox.showerror("Invalid %s" % info["name"],
                                   "'%s' is not a valid %s" % (value, info["type"]),
                                   parent=window)

    ttk.Button(footer, text="OK", width=20,
               command=ok_handler).grid(pady=10, ipady=5, sticky=tk.W)
    ttk.Button(footer, text="Cancel", width=20,
               command=window.destroy).grid(row=0, column=1, pady=10, ipady=5, sticky=tk.W)

    window.transient(root)
    window.grab_set()
    root.wait_window(window)

def scalar_handler(info, window, first_row, integer):

    j = first_row

    ttk.Label(window, text="Units:").grid(row=j, column=0, sticky=tk.W, padx=10)
    ttk.Label(window, text=info["units"]).grid(row=j, column=1, sticky=tk.W, padx=10)
    j += 1

    ttk.Label(window, text="Value:").grid(row=j, column=0, sticky=tk.W+tk.N, padx=10)
    text = tk.Text(window, width=60, height=1)
    text.grid(row=j, column=1, sticky=tk.W, padx=10)
    value = info["value"]
    if integer:
        text.insert("end", str(int(value)))
    else:
        text.insert("end", str(value))
    j += 1

    window.value = text

    return j

def scalar_dialog(event):
    generic_dialog(event, lambda a, b, c : scalar_handler(a, b, c, False))

def integer_dialog(event):
    generic_dialog(event, lambda a, b, c : scalar_handler(a, b, c, True))

def string_handler(info, window, first_row):

    ttk.Label(window, text="Value:").grid(row=first_row, column=0, sticky=tk.W+tk.N, padx=10)

    value = info["value"]

    j = first_row
    text = tk.Text(window, width=60, height=text_height(60, value))
    text.grid(row=j, column=1, sticky=tk.W, padx=10)
    text.insert("end", value)
    j += 1

    window.value = text

    return j

def string_dialog(event):
    generic_dialog(event, string_handler)

def boolean_handler(info, window, first_row):

    value = tk.StringVar()
    value.set(str(info["value"]))

    ttk.Label(window, text="Value:").grid(row=first_row, column=0, sticky=tk.W, padx=10)

    window.value = value

    j = first_row
    for v in ["True", "False"]:
        ttk.Radiobutton(window, text=v, variable=value, value=v).grid(row=j, column=1, sticky=tk.W, padx=10)
        j += 1

    return j

def boolean_dialog(event):
    generic_dialog(event, boolean_handler)

class App(object):
    def __init__(self, master):
        self.master = master
        self.create_widgets(master)

    def create_widgets(self, master):
        frame = ttk.Frame(master, width=1000)
        frame.pack(side="left", fill="both", expand=True, padx=10, pady=10)

        tree = ttk.Treeview(frame)
        tree.tag_bind('string', '<Double-Button-1>', string_dialog)
        tree.tag_bind('keyword', '<Double-Button-1>', keyword_dialog)
        tree.tag_bind('scalar', '<Double-Button-1>', scalar_dialog)
        tree.tag_bind('integer', '<Double-Button-1>', integer_dialog)
        tree.tag_bind('boolean', '<Double-Button-1>', boolean_dialog)

        tree.pack(side="left", fill="both", expand=True)
        tree["height"] = 40

        tree['columns'] = ["value", "units", "description"]

        tree.column('description', minwidth=500)
        tree.column('units', minwidth=150, stretch=False)
        tree.column('value', minwidth=150, stretch=False)
        tree.column('#0', minwidth=200)

        tree.heading("value", text="Value")
        tree.heading("units", text="Units")
        tree.heading("description", text="Description")

        scrollbar = tk.Scrollbar(master, orient=tk.VERTICAL, command=tree.yview)
        scrollbar.pack(expand=True, fill="y")

        tree["yscrollcommand"] = scrollbar.set

        self.load_config(master)

        self.add_items("", tree, "", data)

    def add_items(self, id, tree, root, config):
        keys = config.keys()
        keys.sort()

        for k in keys:
            if id == "":
                parameter_name = k
            else:
                parameter_name = id + "." + k
            if type(config[k]) == dict:
                self.add_items(parameter_name, tree,
                               tree.insert(root, 'end', parameter_name, text=k,
                                           values=("---", "---", "---")),
                               config[k])
            else:
                if not is_special(k):
                    parameter_value = config[k]
                    parameter_type = config[k + "_type"]
                    if parameter_type == "integer":
                        parameter_value = int(parameter_value)
                    if parameter_type not in ("string", "keyword", "boolean"):
                        parameter_units = config[k + "_units"]
                    else:
                        parameter_units = "---"
                    doc = config[k + "_doc"]
                    tree.insert(root, 'end', parameter_name, text=k, values=(parameter_value, parameter_units, doc),
                                tags=parameter_type)

    def load_config(self, master):
        from argparse import ArgumentParser

        parser = ArgumentParser()
        parser.description = "PISM Configuration file editor"
        parser.add_argument("FILE", nargs=1)
        options = parser.parse_args()
        args = options.FILE

        if len(args) > 0:
            self.input_file = args[0]
        else:
            master.update()
            self.input_file = tkFileDialog.askopenfilename(parent=master,
                                                           filetypes = ["JSON .json"],
                                                           title='Choose an input file')

        if len(self.input_file) == 0:
            print "No input file selected. Exiting..."
            import sys
            sys.exit(0)

        load_config(self.input_file)

def create_nesting(config, name):
    p = name.split(".")

    d = config
    for j in p[:-1]:
        if j not in d.keys():
            d[j] = {}

        d = d[j]

def copy_parameter(source, destination, name):
    create_nesting(destination, name)

    p = name.split(".")

    s = source
    d = destination
    for j in p[:-1]:
        s = s[j]
        d = d[j]

    for k in s.keys():
        if k.startswith(p[-1] + "_"):
            d[k] = s[k]

if __name__ == "__main__":

    import Tkinter as tk
    import tkFileDialog, ttk, tkMessageBox

    root = tk.Tk()
    root.geometry("%dx%d" % (1200, 700))
    root.wm_title("PISM configuration editor")

    a = App(root)

    root.lift()
    root.mainloop()
