import tkinter as tk
import tkinter.font as tkfont
import tkinter.filedialog as fd
import string
from collections import defaultdict, OrderedDict
import json
import os.path

HELP_TXT = """
Map Creator GUI - Tool made by Dogeek for

Click on the map to select a cell. You can then edit the cell info
to your liking.

Click on the save button in the Form once you are done editing the cell info.

Form attributes :

Position - current cell position, read only
Location Name - a unique name for this location.
Description - a description of the location shown to the player once he enters.
NPCs - A space-separated list of character names present in this cell.
Icon - An icon to represent this location on the map, must be a single character
Submaps - A space-separated list of submaps to enter from this location.
Exits - Tick in which directions the player can go from this location.
Save - Saves the current cell info.

Menubar :
Save - Saves the map file.
Load - Loads a map file.
Help - Show this window.

(c) Dogeek - 2019

def get_avg_charwidth(widget=None, text=None):
    if text is None:
        text = string.printable
    if widget is None:
        font = tkfont.Font(font='TkTextFont')
        font = tkfont.Font(font=widget['font'])
    return sum([font.measure(c) for c in text]) / len(text)

class HelpWindow(tk.Frame):
    def __init__(self, master=None):
        self.text = tk.Text(self, width=80, height=40)
        self.vsb = tk.Scrollbar(self, command=self.text.yview)
        self.text.pack(side=tk.LEFT, expand=True, fill=tk.BOTH)
        self.vsb.pack(side=tk.RIGHT, expand=True, fill=tk.Y)
        self.text.insert(tk.END, HELP_TXT)
        self.text.config(state=tk.DISABLED, xscrollcommand=self.vsb.set)

class DataSerializer:
    def __init__(self, filename='map.json'):
        self.filename = filename = defaultdict(dict)

    def save(self, filename=None):
        fname = filename or self.filename
        self.filename = fname
        with open(fname, 'w') as f:
            json.dump(, f, indent=4)

    def load(self, filename=None):
        fname = filename or self.filename
        self.filename = fname
        with open(self.filename) as f:
            data = json.load(f)

    def format_name(name):
        return name.replace(" ", "_").replace("'", "").lower()

class MapView(tk.Canvas):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for i in range(0, 513, 16):
            self.create_line(0, i, 512, i, fill='grey')
            self.create_line(i, 0, i, 512, fill='grey')
        self.cursor = self.create_rectangle((0, 0, 16, 16))
        self.selected_position = (0, 0)
        self.bind('<Button-1>', self.on_click)

    def on_click(self, event):
        x = self.canvasx(event.x)
        y = self.canvasy(event.y)

        x = int(x - x % 16)
        y = int(y - y % 16)
        self.selected_position = (x, y)
        self.coords(self.cursor, x, y, x + 16, y + 16)

    def map_position(self):
        x, y = self.selected_position
        return x // 16, y // 16

    def position_saved(self):
        x, y = self.selected_position
        self.create_rectangle(x, y, x + 16, y + 16, stipple='gray50', fill='grey')

class FormView(tk.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.variables = defaultdict(tk.StringVar)
        self.exits_var = [tk.IntVar() for i in range(4)]
        for var in self.exits_var:

        self.exits_frame = tk.Frame(self)
        self.form_entries = OrderedDict()
        self.form_labels = OrderedDict()
        self.form_exits = OrderedDict()
        self.form_entries_names = ('Location Name', 'Description', 'NPCs', 'Icon', "Submaps")
        self.exits_names = ('N', 'S', 'E', 'W')
        self.position_variable = tk.StringVar()
        self.position_variable.set('(0, 0)')
        self.position_label_lbl = tk.Label(self, text='Position : ')
        self.position_label = tk.Label(self, textvariable=self.position_variable)
        for entry in self.form_entries_names:
            self.form_labels[entry] = tk.Label(self, text=entry + ' :')
            if not entry == 'Description':
                self.form_entries[entry] = tk.Entry(self, textvariable=self.variables[entry], width=20)
                self.form_entries[entry] = tk.Text(self, width=20)
        for i, ext in enumerate(self.exits_names):
            self.form_exits[ext + '_label'] = tk.Label(self.exits_frame, text=ext + " :")
            self.form_exits[ext + '_checkbox'] = tk.Checkbutton(self.exits_frame, variable=self.exits_var[i])
        self.exits_label = tk.Label(self, text='Exits :')
        self.save_button = tk.Button(self, text='Save', command=self.save_btn)

    def set_position_label(self, x, y):
        self.position_variable.set(f'({x}, {y})')

    def setup_ui(self):
        self.position_label_lbl.grid(row=0, column=0, sticky='w')
        self.position_label.grid(row=0, column=1, sticky='w')
        for i, entry_lbl in enumerate(self.form_labels.values()):
            entry_lbl.grid(row=i + 1, column=0, sticky='w')
        for i, entry_entry in enumerate(self.form_entries.values()):
            entry_entry.grid(row=i + 1, column=1, sticky='we')
        for i, w in enumerate(self.form_exits.values()):
            w.grid(row=0, column=i + 1)
        x = len(self.form_labels.values()) + 1
        self.exits_label.grid(row=x, column=0, sticky='w')
        self.exits_frame.grid(row=x, column=1, sticky='w')
        self.save_button.grid(row=x + 1, column=0, sticky='w')

    def save_btn(self, *args):
        name = DataSerializer.format_name(self.variables['Location Name'].get())[name] = {}[name]['description'] = self.form_entries["Description"].get('1.0', tk.END)[name]['map_icon'] = self.variables["Icon"].get()[name]['npc'] = self.variables["NPCs"].get().split(' ')[name]['position'] = list(self.master.canvas.map_position)[name]['submaps'] = self.variables["Submaps"].get().split(' ')[name]['exits'] = [en for e, en in zip(self.exits_var, self.exits_names) if e.get() == 1]

    def load(self, x, y):
        name = ""
        for key, val in
            if val.get('position') == [x, y]:
                name = key

        if[name] != {}:
            self.form_entries['Description'].delete('1.0', tk.END)
            self.variables["Location Name"].set(name)
            self.variables["NPCs"].set(" ".join([name]['npc']))
            for e, en in zip(self.exits_var, self.exits_names):
                e.set(1 if en in[name]['exits'] else 0)
            self.form_entries['Description'].delete('1.0', tk.END)
            self.variables["Location Name"].set("")
            for e in self.exits_var:

class MapCreatorGUI(tk.Frame):
    def __init__(self, master, *args, **kwargs):
        kwargs['master'] = master
        super().__init__(*args, **kwargs)
        self.master = master
        self.help_window = None

        self.menubar = tk.Menu(self.master)
        self.menubar.add_command(label="Save", command=self._on_data_save)
        self.menubar.add_command(label="Load", command=self._on_data_load)
        self.menubar.add_command(label="Help", command=self._on_help)

        self.form_frame = FormView(self)
        self.canvas = MapView(self, width=512, height=512, background='white') = DataSerializer()

        master.bind('<Configure>', self.on_configure)

    def setup_ui(self):

    def on_configure(self, event):
        canvw = 512  # self.canvas['width']
        longest_label_name = sorted(self.form_frame.form_entries_names, key=len)[-1]
        margin_width = get_avg_charwidth(self.form_frame.form_labels[longest_label_name],
                                         longest_label_name) * len(longest_label_name)
        new_width = abs(int(self.winfo_width()) - int(canvw) - margin_width)
        cw = 8.8  # get_avg_charwidth()
        new_width_in_characters = int(new_width // cw)
        for entry in self.form_frame.form_entries_names:

    def _on_data_load(self, *args):
        fname = fd.askopenfilename()
        if not os.path.exists(fname):
        for v in [_ for _ in if _]:
            x, y = v['position']
            x *= 16
            y *= 16
            self.canvas.create_rectangle(x, y, x + 16, y + 16, stipple='gray50', fill='grey')

    def _on_data_save(self, *args):
        fname = fd.asksaveasfilename()

    def _on_help(self, *args):
        def _on_help_close():
            self.help_window = None

        if self.help_window is None:
            self.help_window = tk.Toplevel(self)
            self.help_window.resizable(False, False)
            gui = HelpWindow(self.help_window)
            gui.pack(expand=True, fill=tk.BOTH)
            self.help_window.protocol('WM_DELETE_WINDOW', _on_help_close)

    def loop(self):
        self.after(50, self.loop)

if __name__ == '__main__':
    root = tk.Tk()
    root.minsize(800, 525)
    root.resizable(True, False)
    root.title('RPG Text Map Creator')
    window = MapCreatorGUI(root)
    window.pack(expand=True, fill=tk.BOTH)