'''
Source: https://github.com/fietkiewicz/PointerBuilder
Description: Utility for pointers for NEURON simulations. See README.md for more details.
'''

import os
import csv
import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename
from functools import partial

column = 0 # Column index

def read_pointer_mod():
    filepath = askopenfilename(
        filetypes = [("Mod Files", "*.mod"), ("All files", "*.*")]
    )
    if not filepath:
        return
    with open(filepath, mode = "r", encoding = "utf-8") as mod_file:
        frame = tk.Toplevel(window)
        frame.title("Pointer Variables")
        for line in mod_file.readlines():
            if ("POINTER" in line):
                new_line = remove_spaces(line).strip()
                variables = new_line[7:].split(",")
                for i in range(len(variables)):
                    make_widget(frame, "button", i, 0, variables[i], setSticky = 'we', setCommand = partial(set_pointer_mod, filepath, variables[i]), setWidth = 20)
                break
        else:
            error_message = make_widget(frame, "label", 0, 0, "Error: No POINTER statement found.", setSticky = 'we', setWidth = 1000)
            error_message.configure(pady = 20, fg = "red")
            make_widget(frame, "button", 1, 0, "Try again", setSticky = 'we', setCommand = lambda: close_read_pointer_mod(frame), setWidth = 20)
            make_widget(frame, "button", 2, 0, "Close", setSticky = 'we', setCommand = lambda: frame.destroy(), setWidth = 20)

def set_pointer_mod(filepath, variable):
    ent_pointer_mod.delete(0, 'end') # Clear text
    file_name = os.path.basename(filepath)
    ent_pointer_mod.insert(0, os.path.splitext(file_name)[0])
    ent_pointer.delete(0, 'end') # Clear text
    ent_pointer.insert(0, variable)

def close_read_pointer_mod(frame):
    frame.destroy()
    read_pointer_mod()

def read_source_mod():
    filepath = askopenfilename(
        filetypes = [("Mod Files", "*.mod"), ("All files", "*.*")]
    )
    if not filepath:
        return
    with open(filepath, mode = "r", encoding = "utf-8") as mod_file:
        ent_source_mod.delete(0, 'end') # Clear text
        file_name = os.path.basename(filepath)
        ent_source_mod.insert(0, os.path.splitext(file_name)[0])

def make_py():
    filepath = asksaveasfilename(
        defaultextension=".py",
        filetypes=[("Python Script", "*.py"), ("All Files", "*.*")],
    )
    if not filepath:
        return
    if (typeSelect.get() == 0): # State variable
        commandText = ent_pointer_cell.get() + "(0.5)." + \
                      ent_pointer_mod.get() + "." + \
                      "_ref_" + ent_pointer.get() + " = " + \
                      ent_source_cell.get() + "(0.5)." + \
                      ent_source_mod.get() + "." + \
                      "_ref_" + ent_source.get()
    else: # Parameter
        commandText = ent_pointer_cell.get() + "(0.5)." + \
                      ent_pointer_mod.get() + "." + \
                      "_ref_" + ent_pointer.get() + " = " + \
                      "h._ref_" + ent_source.get() + "_" + \
                      ent_source_mod.get()
    with open(filepath, mode="w", encoding="utf-8") as py_file:
        py_file.write(commandText)

def view_command():
    '''
    Format: section(position).mechanism._ref_variable
    Examples:
    model(0.5).body._ref_V1pointer = model(0.5).brain._ref_V1
    model(0.5).brain._ref_L1pointer = h._ref_L1_body
    '''
    frame = tk.Toplevel(window)
    frame.title("Python pointer command")
    if (typeSelect.get() == 0): # State variable
        commandText = ent_pointer_cell.get() + "(0.5)." + \
                      ent_pointer_mod.get() + "." + \
                      "_ref_" + ent_pointer.get() + " = " + \
                      ent_source_cell.get() + "(0.5)." + \
                      ent_source_mod.get() + "." + \
                      "_ref_" + ent_source.get()
    else: # Parameter
        commandText = ent_pointer_cell.get() + "(0.5)." + \
                      ent_pointer_mod.get() + "." + \
                      "_ref_" + ent_pointer.get() + " = " + \
                      "h._ref_" + ent_source.get() + "_" + \
                      ent_source_mod.get()
    make_widget(frame, "label", 0, 0, "Copy the following pointer command:", setWidth = 70)
    make_widget(frame, "entry", 1, 0, commandText, setWidth = 70)

def save_settings():
    filepath = asksaveasfilename(
        defaultextension=".py",
        filetypes=[("CSV Files", "*.csv"), ("All Files", "*.*")],
    )
    if not filepath:
        return
    with open(filepath, "w", encoding = "utf-8") as settings_file:
        writer = csv.writer(settings_file)
        writer.writerow([
            ent_pointer_mod.get(),
            ent_pointer.get(),
            ent_pointer_cell.get(),
            ent_source_mod.get(),
            ent_source.get(),
            ent_source_cell.get(),
            str(typeSelect.get())
        ])

def load_settings():
    filepath = askopenfilename(
        filetypes = [("CSV Files", "*.csv"), ("All files", "*.*")]
    )
    if not filepath:
        return
    # Clear entry boxes
    ent_pointer_mod.delete(0, 'end')
    ent_pointer.delete(0, 'end')
    ent_pointer_cell.delete(0, 'end')
    ent_source_mod.delete(0, 'end')
    ent_source.delete(0, 'end')
    ent_source_cell.delete(0, 'end')
    with open(filepath, mode = "r", encoding = "utf-8") as settings_file:
        lines = settings_file.readlines()
        entries = lines[0].split(",")
        ent_pointer_mod.insert(0, entries[0])
        ent_pointer.insert(0, entries[1])
        ent_pointer_cell.insert(0, entries[2])
        ent_source_mod.insert(0, entries[3])
        ent_source.insert(0, entries[4])
        ent_source_cell.insert(0, entries[5])
        typeSelect.set(entries[6][0:1])

def remove_spaces(string):
    return string.replace(" ", "")

def do_nothing():
    # Do nothing. This is used as a default argument for make_widget.
    pass

def help_screen():
    new = tk.Toplevel(window)
    new.title("Pointer Builder Help")
    text = tk.Text(new, wrap = tk.WORD)
    text.pack()
    text.insert(tk.INSERT, "PointerBuilder.py is a tool that assists the user in using pointers for NEURON simulations. It creates a pointer assignment statement with the correct syntax. Additional support is available at this site: \n\nhttps://github.com/fietkiewicz/PointerBuilder")
    text.configure(state = tk.DISABLED)

def make_widget(frame, widgetType, setRow, setColumn, setText, setSticky = 'w', setCommand = do_nothing, setRadio = 0, setWidth = 18):
    fontSize = 20
    fontName = 'Arial'

    if (widgetType == "label"):
        obj = tk.Label(master = frame, text = setText, font = (fontName, fontSize))

    elif (widgetType == "entry"):
        obj = tk.Entry(master = frame, width = setWidth, font = (fontName, fontSize))
        obj.insert(0, setText)

    elif (widgetType == "button"):
        obj = tk.Button(frame, text = setText, command = setCommand, width = setWidth, font = (fontName, fontSize))

    elif (widgetType == "radio"):
        obj = tk.Radiobutton(frame, text = setText, variable = typeSelect, value = setRadio, font = (fontName, fontSize))

    # Grid settings for all widgets
    obj.grid(row = setRow, column = setColumn, pady = pad, sticky = setSticky)
    return obj


# GUI stuff
pad = 0 # Padding for grid
window = tk.Tk()
window.title("Pointer Builder")
window.columnconfigure(0, weight = 1)
window.rowconfigure(1, weight = 1)
window.rowconfigure(2, weight = 1)

framePad = 10
fontSize = 20
fontName = 'Arial'
frm_pointer = tk.LabelFrame(window, padx = framePad, pady = framePad, text = "Pointer Settings", font = (fontName, fontSize))
frm_pointer.grid(row = 0, column = 0, sticky = "nsew", padx = framePad)
frm_source = tk.LabelFrame(window, padx = framePad, pady = framePad, text = "State/Parameter Settings", font = (fontName, fontSize))
frm_source.grid(row = 1, column = 0, sticky = "nsew", padx = framePad)
frm_buttons = tk.LabelFrame(window, padx = framePad, text = "Actions", font = (fontName, fontSize))
frm_buttons.grid(row = 2, column = 0, sticky = "nsew", padx = framePad)

# Pointer panel
make_widget(frm_pointer, "label", 1, 0, "NMODL file:")
ent_pointer_mod = make_widget(frm_pointer, "entry", 2, 0, "NOT ENTERED", setSticky = 'we')
make_widget(frm_pointer, "button", 3, 0, "Read .mod file", setSticky = 'we', setCommand = read_pointer_mod, setWidth = 1) # Note: setWidth is set to 1 because the actual width somehow ends up being wider than entry box for same width (can't figure out why!).
make_widget(frm_pointer, "label", 1, 1, "Name of pointer:")
ent_pointer = make_widget(frm_pointer, "entry", 2, 1, "NOT ENTERED")
make_widget(frm_pointer, "label", 1, 2, "Section name:")
ent_pointer_cell = make_widget(frm_pointer, "entry", 2, 2, "NOT ENTERED")

# State/parameter panel
make_widget(frm_source, "label", 1, 0, "NMODL file:")
ent_source_mod = make_widget(frm_source, "entry", 2, 0, "NOT ENTERED", setSticky = 'we')
make_widget(frm_source, "button", 3, 0, "Read .mod file", setSticky = 'we', setCommand = read_source_mod, setWidth = 1) # Note: setWidth is set to 1 because the actual width somehow ends up being wider than entry box for same width (can't figure out why!).
make_widget(frm_source, "label", 1, 1, "Name of state/parameter:")
ent_source = make_widget(frm_source, "entry", 2, 1, "NOT ENTERED")
make_widget(frm_source, "label", 1, 2, "Section name:")
ent_source_cell = make_widget(frm_source, "entry", 2, 2, "NOT ENTERED")
typeSelect = tk.IntVar() # Value of raidobutton for selecting variabel type (state or parameter)
make_widget(frm_source, "label", 1, 3, "Type:")
make_widget(frm_source, "radio", 2, 3, "State variable", setRadio = 0)
make_widget(frm_source, "radio", 3, 3, "Parameter", setRadio = 1)

# Button panel
buttonWidth = 12
make_widget(frm_buttons, "button", 1, 0, "Python command", setSticky = 'we', setCommand = view_command, setWidth = buttonWidth)
make_widget(frm_buttons, "button", 1, 1, "Make .py file", setSticky = 'we', setCommand = make_py, setWidth = buttonWidth)
make_widget(frm_buttons, "button", 1, 2, "Save settings", setSticky = 'we', setCommand = save_settings, setWidth = buttonWidth)
make_widget(frm_buttons, "button", 1, 3, "Load settings", setSticky = 'we', setCommand = load_settings, setWidth = buttonWidth)
make_widget(frm_buttons, "button", 1, 4, "Help", setSticky = 'we', setCommand = help_screen, setWidth = buttonWidth)

window.mainloop()