from Tkinter import *
from PIL import Image, ImageTk
import tkSimpleDialog

import urllib
import urllib2
import json

import time

sys.path.append("interface/HUTN")
sys.path.append("wrappers")
from modelverse import *

address = "http://127.0.0.1:8001"
taskname = str(random.random())
MAX_WIDTH = 500
MAX_HEIGHT = 500
MM_RENDERED = "MM_rendered_graphical"

def rendered_formula(model_name, mapper_name):
    return "__RENDERED_%s__%s" % (model_name, mapper_name)

init()
login("admin", "admin")

root = Tk()

class Window(object):
    def __init__(self):
        self.canvas = Canvas(root, width=MAX_WIDTH, height=MAX_HEIGHT, bg="white")

        self.selected_model = StringVar(root)
        self.selected_mapper = StringVar(root)

        mm_buttons = []
        available_models = model_list()
        available_mappers = []
        self.model_options = OptionMenu(root, self.selected_model, *[i[0] for i in available_models])
        self.mapper_options = OptionMenu(root, self.selected_mapper, "", *available_mappers)
        self.reset_optionmenu(self.mapper_options, [], self.selected_mapper)

        bound_functions = {
            "open": self.open_model,
            "render": self.render_model,
            "instantiate": self.instantiate_model,
            "verify": self.verify_model,
            }

        buttons = []
        buttons.append(self.model_options)
        buttons.append(self.mapper_options)

        self.current_element = None

        for k, v in bound_functions.items():
            buttons.append(Button(root, text=k, command=v))

        for i, b in enumerate(buttons):
            b.grid(row=0, column=i, sticky="WENS")

        self.mm_buttons = []

        self.CS_to_AS = {}
        self.AS_to_CS = {}
        self.AS_to_TK = {}
        self.TK_to_CS = {}

        self.canvas.grid(row=2,column=0,columnspan=len(buttons))

        self.canvas.bind("<Button-1>", self.left_clicked)
        self.canvas.bind("<B1-Motion>", self.left_drag)
        self.canvas.bind("<ButtonRelease-1>", self.left_released)
        self.canvas.bind("<Button-2>", self.middle_clicked)
        self.canvas.bind("<Button-3>", self.right_clicked)

    def find_AS(self, x, y):
        # Find the group at this location
        def in_bbox(coord, bbox):
            if bbox[0] <= coord[0] <= bbox[2] and bbox[1] <= coord[1] <= bbox[3]:
                return True

        for elem in self.canvas.find_all():
            if in_bbox((x, y), self.canvas.bbox(elem)):
                return self.CS_to_AS[self.TK_to_CS[elem]]

    def move_group(self, elem, coords):
        # Change coordinates of all enclosed elements!
        # elem is an AS id of the group
        m_x, m_y = coords
        o_x, o_y = self.previous_coords
        d_x = m_x - o_x
        d_y = m_y - o_y

        for tkinter_obj in self.AS_to_TK[elem]:
            obj_coords = self.canvas.coords(tkinter_obj)
            if len(obj_coords) == 2:
                c_x, c_y = obj_coords
                self.canvas.coords(tkinter_obj, (c_x + d_x, c_y + d_y))
            elif len(obj_coords) == 4:
                c_x, c_y, c_x2, c_y2 = obj_coords
                self.canvas.coords(tkinter_obj, (c_x + d_x, c_y + d_y, c_x2 + d_x, c_y2 + d_y))

    def left_clicked(self, evt):
        # Left click means we select an element for dragging
        x, y = evt.x, evt.y
        self.original_coords = x, y
        self.previous_coords = x, y
        self.current_element = self.find_AS(x, y)

    def left_drag(self, evt):
        # Left drag means we are dragging something, but only if something is selected
        if self.current_element is not None:
            x, y = evt.x, evt.y
            self.move_group(self.current_element, (x, y))
            self.previous_coords = x, y

    def left_released(self, evt):
        # Left click means we select an element for dragging
        if self.current_element is not None:
            x, y = evt.x, evt.y
            d_x = x - self.original_coords[0]
            d_y = y - self.original_coords[1]

            self.move_group(self.current_element, (x, y))
            attrs = read_attrs(rendered_formula(self.selected_model.get(), self.selected_mapper.get()), self.AS_to_CS[self.current_element])

            # Send new values to Modelverse!
            attr_assign(rendered_formula(self.selected_model.get(), self.selected_mapper.get()), self.AS_to_CS[self.current_element], "x", attrs["x"] + d_x)
            attr_assign(rendered_formula(self.selected_model.get(), self.selected_mapper.get()), self.AS_to_CS[self.current_element], "y", attrs["y"] + d_y)

    def middle_clicked(self, evt):
        # Middle clicked means we are modifying attributes of something
        x, y = evt.x, evt.y

        # Read available attrs and their values
        as_id = self.find_AS(x, y)
        attrs = read_attrs(self.selected_model.get(), as_id)
        attrs = {k: json.dumps(attrs[k]) for k in attrs}

        # Prompt for new values
        d = PromptDialog(root, attrs)
        if d.result is not None:
            # Change all attributes that have changed and send to Modelverse
            # TODO
            #TODO think about type checking
            for k, v in d.result.items():
                v = json.loads(v)
                if v is None:
                    attr_delete(self.selected_model.get(), as_id, k)
                else:
                    attr_assign(self.selected_model.get(), as_id, k, v)

    def visualize(self, model):
        cache = {}
        hierarchy = {}
        parent = {}

        self.canvas.delete("all")

        self.CS_to_AS = {}
        self.AS_to_CS = {}
        self.AS_to_TK = {}
        self.TK_to_CS = {}

        def findXY(element_id):
            x = cache[element_id]["x"]
            y = cache[element_id]["y"]

            while element_id in parent:
                # Has a parent (group), so increment the XY values and keep going
                element_id = parent[element_id]
                x += cache[element_id]["x"]
                y += cache[element_id]["y"]

            return x, y

        def findGroup(element_id):
            try:
                while cache[element_id]["__asid"] is None:
                    element_id = parent[element_id]
                return element_id
            except KeyError:
                # No configured __asid found in hierarchy
                print("ERROR")
                return None

        def findASID(element_id):
            return cache[findGroup(element_id)]["__asid"]

        for element in model:
            # Determine type of element
            cache[element["id"]] = element
            t = element["type"]

            if t == "contains":
                # Cache the containment links as well
                parent[element["__target"]] = element["__source"]
                hierarchy.setdefault(element["__source"], []).append(element["__target"])

        # Caches filled, so go over all elements, ignoring groups, and draw them relative to their enclosing groups
        for element in model:
            t = element["type"]
            if t in ["Rectangle", "Ellipse", "Line", "Text", "Figure"]:
                x, y = findXY(element["id"])
                as_ID = findASID(element["id"])
                cs_ID = element["id"]
                group_ID = findGroup(element["id"])
            elif t in ["Group", "contains", "renders"]:
                continue
            else:
                raise Exception(str(element))

            if t == "Rectangle":
                fillColour = element["fillColour"]
                width = element["width"]
                height = element["height"]
                lineWidth = element["lineWidth"]
                lineColour = element["lineColour"]
                tkinter_elem = self.canvas.create_rectangle(x, y, x+width, y+height, fill=fillColour, outline=lineColour)
            elif t == "Ellipse":
                fillColour = element["fillColour"]
                width = element["width"]
                height = element["height"]
                lineWidth = element["lineWidth"]
                lineColour = element["lineColour"]
                tkinter_elem = self.canvas.create_ellipse(x, y, x+width, y+height, fill=fillColour, outline=lineColour)
            elif t == "Line":
                targetX = element["targetX"]
                targetY = element["targetY"]
                lineWidth = element["lineWidth"]
                lineColour = element["lineColour"]
                tkinter_elem = self.canvas.create_line(x, y, targetX, targetY, fill=lineColour, width=lineWidth)
            elif t == "Text":
                lineWidth = element["lineWidth"]
                lineColour = element["lineColour"]
                text = element["text"]
                tkinter_elem = self.canvas.create_text(x, y, fill=lineColour, text=text)
            elif t == "Figure":
                # Fetch associated SVG figure
                raise NotImplementedError()

            self.TK_to_CS[tkinter_elem] = cs_ID
            self.CS_to_AS[cs_ID] = as_ID
            self.AS_to_CS[as_ID] = group_ID
            self.AS_to_TK.setdefault(as_ID, set([])).add(tkinter_elem)

    def reset_optionmenu(self, optionmenu, options, var):
        menu = optionmenu.children["menu"]
        menu.delete(0, "end")
        var.set("")
        for option in options:
            menu.add_command(label=option, command=lambda value=option: var.set(value))

    def read_available_mappers(self, instance_model):
        # Get instance model's type first, as transformations are defined on the type
        models = dict(model_list())
        type_model = models[instance_model]

        mappers = transformation_between(type_model, MM_RENDERED)
        return mappers

    def right_clicked(self, evt):
        element_id = instantiate(self.selected_model.get(), self.current_element)
        elements = self.render_model()
        for f in elements:
            if "__asid" in f:
                if f["__asid"] == element_id:
                    attr_assign(rendered_formula(self.selected_model.get(), self.selected_mapper.get()), f["id"], "x", evt.x)
                    attr_assign(rendered_formula(self.selected_model.get(), self.selected_mapper.get()), f["id"], "y", evt.y)
        self.render_model()

    def select_element(self, t):
        def set_elem():
            self.current_element = t
        return set_elem

    def render_model(self):
        import json
        rendered = json.loads(model_render(self.selected_model.get(), self.selected_mapper.get()))
        self.visualize(rendered)

        return rendered

    def open_model(self):
        available_mappers = self.read_available_mappers(self.selected_model.get())
        self.reset_optionmenu(self.mapper_options, available_mappers, self.selected_mapper)

        available_types = [i[0] for i in types_full(self.selected_model.get()) if i[1] == "Class"]

        for button in self.mm_buttons:
            button.destroy()

        self.mm_buttons = []
        for i, t in enumerate(available_types):
            self.mm_buttons.append(Button(root, text=t, command=self.select_element(t)))
            self.mm_buttons[-1].grid(row=1, column=i)

    def instantiate_model(self):
        d = PromptDialog(root, ["Name:"])
        if d.result is not None:
            name = str(d.result["Name:"])
            model_add(name, self.selected_model.get())
            self.reset_optionmenu(self.available_models, model_list())
            self.selected_model.set(name)
            self.open_model()

    def verify_model(self):
        print(verify(self.selected_model.get()))

class PromptDialog(tkSimpleDialog.Dialog):
    def __init__(self, master, query):
        self.query = query
        tkSimpleDialog.Dialog.__init__(self, master)

    def body(self, master):
        self.entries = {}
        for i, q in enumerate(self.query.items()):
            Label(master, text=q[0]).grid(row=i, column=0)
            self.entries[q[0]] = Entry(master)
            if q[1] is not None:
                self.entries[q[0]].insert(END, q[1])
            self.entries[q[0]].grid(row=i, column=1)
        return None

    def apply(self):
        self.result = {i: self.entries[i].get() for i in self.query}

window = Window()
root.mainloop()
