import tkinter as tk, os
from PIL import ImageTk, Image
from user import User
from structures import Data_Structures as ds
from tkinter import simpledialog, filedialog
from visualize import Visual

class App():

    def __init__(self):
        #set canvas properties
        self.width, self.height = 1100, 800
        self.root = tk.Tk()
        self.root.resizable(width=False, height=False)
        self.root.title('Data Structure Visualisation')

        self.canvas = tk.Canvas(self.root, width=1100, height=800, bg='#040204')

        windowWidth = self.root.winfo_reqwidth()
        windowHeight = self.root.winfo_reqheight()

        # Gets both half the screen width/height and window width/height
        self.positionRight = int(self.root.winfo_screenwidth() / 4 - windowWidth / 2)
        self.positionDown = int(self.root.winfo_screenheight() / 4 - windowHeight / 2)

        # Positions the window in the center of the page.
        self.root.geometry("+{}+{}".format(self.positionRight, self.positionDown))
        self.canvas.pack()

        self.anim_pic = []
        beg, end = "images/anim/frame (", ").png"
        for num in range(54):
            pic_path = beg + str(num+1) + end
            pic = ImageTk.PhotoImage(Image.open(pic_path))
            self.anim_pic.append(pic)
        self.anim_end = False
        self.anim = Anim(550, 400, self.anim_pic, self.canvas)
        self.timer()

        self.home_window()

    # remove button list - not used
    def empty_canvas(self):
        try:
            self.canvas.delete('all')
        except:
            pass

    def home_window(self, event = None):
        self.empty_canvas()

        self.anim = Anim(550,400, self.anim_pic, self.canvas)

        cover = ImageTk.PhotoImage(Image.open('images/applabel.png'))
        self.canvas.create_image(550,400, image=cover)

        log = ImageTk.PhotoImage(Image.open('images/login.png'))
        login_btn = self.canvas.create_image(750, 720, image=log)
        self.canvas.tag_bind(login_btn, '<Button-1>', self.log_window)

        sign = ImageTk.PhotoImage(Image.open('images/signin.png'))
        signin_btn = self.canvas.create_image(350, 720, image=sign)
        self.canvas.tag_bind(signin_btn, '<Button-1>', self.sign_window)

        self.root.mainloop()

    def log_window(self, event = None):
        self.empty_canvas()

        self.anim = Anim(550,400, self.anim_pic, self.canvas)

        cover = ImageTk.PhotoImage(Image.open('images/loglabel.png'))
        self.canvas.create_image(550, 400, image=cover)

        self.user = tk.Entry(self.root, font=("default", 25))
        self.user.insert(tk.END, string='User Name')
        self.password = tk.Entry(self.root, font=("default", 25))
        self.password.insert(tk.END, string='Password')
        self.canvas.create_window(550, 200, window=self.user)
        self.canvas.create_window(550, 600, window=self.password)

        exit_img = ImageTk.PhotoImage(Image.open('images/exit_sm.png'))
        ex_btn = self.canvas.create_image(750, 720, image=exit_img)
        self.canvas.tag_bind(ex_btn, '<Button-1>', self.home_window)

        create = ImageTk.PhotoImage(Image.open('images/login.png'))
        create_btn = self.canvas.create_image(350, 720, image=create)
        self.canvas.tag_bind(create_btn, '<Button-1>', self.verify_login)

        self.root.mainloop()

    def verify_login(self, event = None):
        valid_user, valid_pass = False, False
        user = User()
        users = user.get_users()

        if (self.user.get() in users):
            valid_user = True
            user.username = self.user.get()
            user_pass = user.get_password()

            if (self.password.get() == user_pass):
                valid_pass = True

            else:
                self.password.config(highlightthickness=2)
                self.password.config(highlightbackground="red", highlightcolor="red")

        else:
            self.user.config(highlightthickness=2)
            self.user.config(highlightbackground="red", highlightcolor="red")

        if (valid_user and valid_pass):
            user.password = user_pass
            self.logged_user = user

            self.user_main_window()

    def sign_window(self, event = None):
        self.empty_canvas()

        self.anim = Anim(550, 400, self.anim_pic, self.canvas)

        cover = ImageTk.PhotoImage(Image.open('images/loglabel.png'))
        self.canvas.create_image(550, 400, image=cover)

        self.user = tk.Entry(self.root, font=("default", 25))
        self.user.insert(tk.END, string='User Name')
        self.password1 = tk.Entry(self.root, font=("default", 25))
        self.password1.insert(tk.END, string='Password')
        self.password2 = tk.Entry(self.root, font=("default", 25))
        self.password2.insert(tk.END, string='Repeat pass')

        self.canvas.create_window(550, 200, window=self.user)
        self.canvas.create_window(250, 600, window=self.password1)
        self.canvas.create_window(850, 600, window=self.password2)

        exit_img = ImageTk.PhotoImage(Image.open('images/exit_sm.png'))
        ex_btn = self.canvas.create_image(750, 720, image=exit_img)
        self.canvas.tag_bind(ex_btn, '<Button-1>', self.home_window)

        create = ImageTk.PhotoImage(Image.open('images/createaccount.png'))
        create_btn = self.canvas.create_image(350, 720, image=create)
        self.canvas.tag_bind(create_btn, '<Button-1>', self.verify_sign)

        self.root.mainloop()

    def verify_sign(self, event = None):
        valid_user, valid_pass = False, False
        user = User()
        users = user.get_users()

        if (self.user.get() not in users):
            valid_user = True

        else:
            self.user.config(highlightthickness=2)
            self.user.config(highlightbackground="red", highlightcolor="red")

        pass1, pass2 = self.password1.get(), self.password2.get()
        if (pass1 == pass2):
            valid_pass = True

        else:
            self.password1.config(highlightthickness=2)
            self.password1.config(highlightbackground="red", highlightcolor="red")

            self.password2.config(highlightthickness=2)
            self.password2.config(highlightbackground="red", highlightcolor="red")

        if (valid_user and valid_pass):
            user.new_user(self.user.get(), pass1)
            user.username = self.user.get()
            user.password = self.password1.get()
            self.logged_user = user

            self.user_main_window()

    def user_main_window(self, event = None):
        self.empty_canvas()

        self.anim = Anim(550, 400, self.anim_pic, self.canvas)

        cover = ImageTk.PhotoImage(Image.open('images/userwindowlabel.png'))
        self.canvas.create_image(550, 400, image=cover)

        exit_img = ImageTk.PhotoImage(Image.open('images/exit_sm.png'))
        ex_btn = self.canvas.create_image(550, 750, image=exit_img)
        self.canvas.tag_bind(ex_btn, '<Button-1>', self.home_window)

        new_img = ImageTk.PhotoImage(Image.open('images/createaccount.png'))
        new_btn = self.canvas.create_image(350, 600, image=new_img)
        self.canvas.tag_bind(new_btn, '<Button-1>', self.new_structure_popup)

        open_img = ImageTk.PhotoImage(Image.open('images/open.png'))
        new_btn = self.canvas.create_image(750, 600, image=open_img)
        self.canvas.tag_bind(new_btn, '<Button-1>', self.open_structure)

        self.canvas.mainloop()

    def open_structure(self, event = None):
        dir = (os.getcwd() + "\\users\\" + self.logged_user.username + "\\structures").replace('\\', '/')
        filetype = (('DAT file', '*.dat'),)
        filename = filedialog.askopenfilename(title="Select saved structure", initialdir=dir, filetypes=filetype)

        if dir not in filename:
            popup = tk.Tk()
            msg = "Invalid file selected!"
            label = tk.Label(popup, text=msg)
            label.pack(side="top", fill="x", pady=60, padx=50)
            popup.geometry("+{}+{}".format(self.positionRight * 2, self.positionDown * 2))
            popup.mainloop()

        else:
            type, structure = self.logged_user.load_structure(filename)
            self.debug(type, structure)

    def new_structure_popup(self, event = None):
        self.popup = tk.Tk()
        label = tk.Label(self.popup, text = "Select a data structure type:")
        label.pack(side="top", fill="x", pady=10, padx=50)
        self.popup.geometry("+{}+{}".format(self.positionRight*2, self.positionDown*2))

        select_stack = tk.Button(self.popup, text="Stack", width=40, command=self.pre_stack_window)
        select_stack.pack()

        select_linkedlist = tk.Button(self.popup, text="Linked List", width=40, command=self.pre_linkedlist_window)
        select_linkedlist.pack()

        select_bintree = tk.Button(self.popup, text="Binary Tree", width=40, command=self.pre_bintree_window)
        select_bintree.pack()

        select_exit = tk.Button(self.popup, text="Exit", width=40, command=self.popup.destroy)
        select_exit.pack()

        self.popup.mainloop()

    def pre_stack_window(self, event = None):
        self.stack_item_count = 0
        self.stack_items = []
        self.data_string = "Item data"
        self.make_stack_window()

    def make_stack_window(self, event = None):
        self.empty_canvas()

        try:
            self.popup.destroy()
        except:
            pass

        cover = ImageTk.PhotoImage(Image.open('images/makecover.png'))
        self.canvas.create_image(550, 400, image=cover)

        exit_img = ImageTk.PhotoImage(Image.open('images/exit_sm.png'))
        ex_btn = self.canvas.create_image(950, 650, image=exit_img)
        self.canvas.tag_bind(ex_btn, '<Button-1>', self.user_main_window)

        save_img = ImageTk.PhotoImage(Image.open('images/save.png'))
        save_btn = self.canvas.create_image(950, 500, image=save_img)
        self.canvas.tag_bind(save_btn, '<Button-1>', self.save_stack)

        new_img = ImageTk.PhotoImage(Image.open('images/add.png'))
        new_btn = self.canvas.create_image(950, 350, image=new_img)
        self.canvas.tag_bind(new_btn, '<Button-1>', self.add_stack_item)

        self.data = tk.Entry(self.root, font=("default", 25))
        self.data.insert(tk.END, string=self.data_string)
        self.canvas.create_window(900, 150, window=self.data)

        self.draw_stack()
        self.canvas.mainloop()

    def save_stack(self, event = None):
        if (self.stack_item_count > 0):
            user = self.logged_user
            file_name = simpledialog.askstring(title="", prompt="Enter new file name:")

            user.save_structure("Stack", self.stack_items, file_name)
            self.user_main_window()

        else:
            popup = tk.Tk()
            msg = "Stack data structure empty!"
            label = tk.Label(popup, text=msg)
            label.pack(side="top", fill="x", pady=60, padx=50)
            popup.geometry("+{}+{}".format(self.positionRight * 2, self.positionDown * 2))
            popup.mainloop()

    def draw_stack(self):
        self.canvas.create_rectangle(100, 675, 700, 750, fill="#ff5c74")
        self.canvas.create_rectangle(375, 100, 425, 675, fill="#ff5c74")
        stack_rects = []
        stack_txts = []
        colors = ["red", "blue", "magenta", "forestgreen"]

        for count, value in enumerate(self.stack_items):
            x, y = 100 + (count + 1) * 27, 675 - (count + 1) * 70
            xfin, yfin = 800 - x, y + 55

            stack_rects.append(self.canvas.create_rectangle(x, y, xfin, yfin, fill=colors[0], width=3))
            self.canvas.tag_bind(stack_rects[-1], '<Double-Button-1>', self.remove_stack_item)

            xmid, ymid = 400, y + 27
            size = 35 - count*3
            fnt = 'arial ' + str(size)
            stack_txts.append(self.canvas.create_text(xmid, ymid, text=value, font=fnt))
            self.canvas.tag_bind(stack_txts[-1], '<Double-Button-1>', self.remove_stack_item)

            colors.append(colors.pop(0))

        self.canvas.pack()

    def add_stack_item(self, event = None):
        if (self.stack_item_count < 8):
            data = self.data.get()
            if data == "" or data == "Item data":
                # TODO make text "no data entered"
                self.data.config(highlightthickness=2)
                self.data.config(highlightbackground="red", highlightcolor="red")

            else:
                self.data.config(highlightthickness=1)
                self.data.config(highlightbackground="red", highlightcolor="black")
                self.stack_item_count += 1
                self.stack_items.append(data)

                self.data_string = ""
                self.make_stack_window()

        else:
            self.max_item_count()

    def remove_stack_item(self, event = None):
        pos = (event.y - 115)//70
        index = 7 - pos
        self.stack_items.pop(index)
        self.stack_item_count -= 1

        self.make_stack_window()

    def max_item_count(self):
        popup = tk.Tk()
        msg = "Max amount of items reached!"
        label = tk.Label(popup, text=msg)
        label.pack(side="top", fill="x", pady=60, padx=50)
        popup.mainloop()

    def pre_linkedlist_window(self, event = None):
        self.linkedlist_count = 0
        self.linkedlist_items = []
        self.data_string = "Item data"
        self.make_linkedlist_window()

    def make_linkedlist_window(self, event = None):
        self.empty_canvas()

        try:
            self.popup.destroy()
        except:
            pass

        cover = ImageTk.PhotoImage(Image.open('images/makecover.png'))
        self.canvas.create_image(550, 400, image=cover)

        exit_img = ImageTk.PhotoImage(Image.open('images/exit_sm.png'))
        ex_btn = self.canvas.create_image(950, 700, image=exit_img)
        self.canvas.tag_bind(ex_btn, '<Button-1>', self.user_main_window)

        save_img = ImageTk.PhotoImage(Image.open('images/save.png'))
        save_btn = self.canvas.create_image(750, 700, image=save_img)
        self.canvas.tag_bind(save_btn, '<Button-1>', self.save_linkedlist)

        new_img = ImageTk.PhotoImage(Image.open('images/add.png'))
        new_btn = self.canvas.create_image(950, 250, image=new_img)
        self.canvas.tag_bind(new_btn, '<Button-1>', self.add_linkedlist_item)

        self.data = tk.Entry(self.root, font=("default", 25))
        self.data.insert(tk.END, string=self.data_string)
        self.canvas.create_window(900, 150, window=self.data)

        self.draw_linkedlist()

        self.canvas.mainloop()

    def save_linkedlist(self, event=None):
        if (self.linkedlist_count > 0):
            user = self.logged_user
            file_name = simpledialog.askstring(title="", prompt="Enter new file name:")

            user.save_structure("LinkedList", self.linkedlist_items, file_name)
            self.user_main_window()

        else:
            popup = tk.Tk()
            msg = "LinkedList data structure empty!"
            label = tk.Label(popup, text=msg)
            label.pack(side="top", fill="x", pady=60, padx=50)
            popup.geometry("+{}+{}".format(self.positionRight * 2, self.positionDown * 2))
            popup.mainloop()

    def draw_linkedlist(self):
        self.canvas.create_text(120,340, text="Front", font='arial 16', fill="deepskyblue")
        if (self.linkedlist_count == 0):
            self.draw_linkedlist_box(80,360,"Empty", True)
        else:
            x, y = 80, 360
            for item in self.linkedlist_items[:-1]:
                self.draw_linkedlist_box(x, y, item)
                x += 120
            self.draw_linkedlist_box(x, y, self.linkedlist_items[-1], True)

        self.canvas.pack()

    def draw_linkedlist_box(self, x, y, data, end=False):
        curr = self.canvas.create_rectangle(x, y, x+80, y+80, width=3, fill="white", outline="lightgray")
        self.canvas.tag_bind(curr, '<Double-Button-1>', self.remove_linkedlist_item)
        if (not end):
            self.canvas.create_line(x+80, y+40, x+122, y+40, width=5, fill="red")
            self.canvas.create_line(x+100, y+10, x+120, y+40, width=5, fill="red")
            self.canvas.create_line(x + 100, y+70, x + 120, y+40, width=5, fill="red")
        txt = self.canvas.create_text(x+40,y+40, text=data, font='arial 16', fill="deepskyblue")
        self.canvas.tag_bind(txt, '<Double-Button-1>', self.remove_linkedlist_item)

    def add_linkedlist_item(self, event = None):
        if (self.linkedlist_count < 8):
            data = self.data.get()
            if data == "" or data == "Item data":
                self.data.config(highlightthickness=2)
                self.data.config(highlightbackground="red", highlightcolor="red")

            else:
                self.data.config(highlightthickness=1)
                self.data.config(highlightbackground="red", highlightcolor="black")
                self.linkedlist_count += 1
                self.linkedlist_items.append(data)

                self.data_string = ""
                self.make_linkedlist_window()

        else:
            self.max_item_count()

    def remove_linkedlist_item(self, event = None):
        if (self.linkedlist_count > 0):
            pos = (event.x - 80) // 120
            self.linkedlist_items.pop(pos)
            self.linkedlist_count -= 1
            self.make_linkedlist_window()

    def pre_bintree_window(self, event = None):
        self.tree_root = None
        self.tree = ds.Binary_Tree()

        self.shifts = [270,155,60]
        self.root_coord = (510, 100)
        self.make_bintree_window()

    def make_bintree_window(self, event = None):
        self.empty_canvas()
        try:
            self.popup.destroy()
        except:
            pass

        cover = ImageTk.PhotoImage(Image.open('images/makecoverbin.png'))
        self.canvas.create_image(550, 400, image=cover)

        exit_img = ImageTk.PhotoImage(Image.open('images/exit_sm.png'))
        ex_btn = self.canvas.create_image(950, 700, image=exit_img)
        self.canvas.tag_bind(ex_btn, '<Button-1>', self.user_main_window)

        save_img = ImageTk.PhotoImage(Image.open('images/save.png'))
        save_btn = self.canvas.create_image(700, 700, image=save_img)
        self.canvas.tag_bind(save_btn, '<Button-1>', self.save_bintree)

        self.uncreated_children = []
        self.draw_binarytree()

        self.data = tk.Entry(self.root, font='arial 25')
        self.data.insert(tk.END, string="Data")
        self.canvas.create_window(300, 700, window=self.data)

        self.canvas.mainloop()

    def save_bintree (self, event=None):
        if (self.tree_root != None):
            user = self.logged_user
            file_name = simpledialog.askstring(title="", prompt="Enter new file name:")
            tree = ds.Binary_Tree()
            tree.root = self.tree_root
            user.save_structure("Tree", tree, file_name)
            self.user_main_window()

        else:
            popup = tk.Tk()
            msg = "Tree data structure empty!"
            label = tk.Label(popup, text=msg)
            label.pack(side="top", fill="x", pady=60, padx=50)
            popup.geometry("+{}+{}".format(self.positionRight * 2, self.positionDown * 2))
            popup.mainloop()

    def draw_binarytree(self, event = None):
        self.canvas.create_text(550,80, text="Tree Root", font = 'arial 14', fill="white")

        if self.tree_root is not None:
            self.draw_tree_nodes(self.tree_root, create = True)
        else:
            self.draw_tree_child(None)

        self.canvas.pack()

    def draw_tree_nodes(self, node : ds.Node, parent : ds.Node = None, create = False):
        x, y = node.pos
        level = node.level
        data = node.data

        cur = self.canvas.create_oval(x, y, x+80, y+80, width=3, fill="white", outline="lightgray")
        txt = self.canvas.create_text(x+40, y+40, text=data, font='arial 15', fill="deepskyblue")
        self.canvas.tag_bind(cur, '<Double-Button-1>', self.remove_node)
        self.canvas.tag_bind(txt, '<Double-Button-1>', self.remove_node)

        if parent is not None:
            parX, parY = parent.pos
            self.canvas.create_line(x+40, y, parX+40, parY+80, width=3, fill = "white")


        if (level + 1 <= 4):
            if (node.left is not None):
                self.draw_tree_nodes(node.left, node, create)
            else:
                self.draw_tree_child(node, 0)

            if (node.right is not None):
                self.draw_tree_nodes(node.right, node, create)
            else:
                self.draw_tree_child(node, 1)

    #direct: left = 0, right = 1
    def draw_tree_child(self, parent : ds.Node, direct = None):
        if parent is not None:
            parX, parY = parent.pos
            y = parY + 150
            if (direct == 0):
                x = int(parX - self.shifts[parent.level-1])
            else:
                x = int(parX + self.shifts[parent.level-1])
        else:
            x, y = self.root_coord

        cur = self.canvas.create_oval(x, y, x+80, y+80, outline="#bababa", fill="#040204", width=2)
        self.canvas.tag_bind(cur, '<Double-Button-1>', self.add_binchild)
        self.canvas.tag_bind(cur, '<Enter>', self.enter_binchild)
        self.canvas.tag_bind(cur, '<Leave>', self.leave_binchild)

        self.uncreated_children.append(cur)

        if direct is not None:
            self.canvas.create_line(x+40, y, parX+40, parY+80, fill="gray")

    def remove_node(self, event = None):
        x, y = event.x, event.y
        tup = self.get_parent_node(x, y)
        node, dir = tup

        if (dir == 'T'):
            self.tree_root = None

        else:
            if (dir == 'L'):
                node.left = None
            if (dir == 'R'):
                node.right = None

        self.make_bintree_window()

    def add_binchild(self, event = None):
        x, y = event.x, event.y
        data = self.data.get()

        if (self.tree_root is None):
            self.tree_root = ds.Node(self.data.get(), 1, self.root_coord)
            self.tree.root = self.tree_root

        else:
            tup = self.get_parent_node(x, y)
            node, dir = tup
            if (dir == 'L'):
                pos = (node.pos[0] - self.shifts[node.level-1], node.pos[1] + 150)
                node.left = ds.Node(data, node.level + 1, pos)
            if (dir == 'R'):
                pos = (node.pos[0] + self.shifts[node.level-1], node.pos[1] + 150)
                node.right = ds.Node(data, node.level + 1, pos)

        self.make_bintree_window()

    def get_parent_node(self, posX, posY):
        path = self.get_tree_path(posX, posY)
        node = self.tree_root

        for dir in range(len(path)-1):
            if (path[dir] == 'L'):
                node = node.left

            if (path[dir] == 'R'):
                node = node.right

        if path == "":
            path = "T"
        return (node, path[-1])

    def get_tree_path(self, posX, posY):
        node = self.tree_root
        path = ""

        while True:
            if node is None:
                return path
            x, y = node.pos
            if (x <= posX <= x+80 and y <= posY <= y+80):
                return path

            else:
                if (x+40 > posX):
                    path += 'L'
                    node = node.left
                elif (x+40 < posX):
                    path += 'R'
                    node = node.right

    def enter_binchild(self, event = None):
        x, y = event.x, event.y
        self.enter_node = self.get_binchild(x, y)
        self.canvas.itemconfig(self.enter_node, outline="red")

    def leave_binchild(self, event = None):
        self.canvas.itemconfig(self.enter_node, outline="#bababa")
        self.enter_node = None

    def get_binchild(self, x, y):
        for oval in self.uncreated_children:
            sX, sY, eX, eY = self.canvas.coords(oval)
            if (sX <= x <= eX and sY <= y <= eY):
                return oval

    def debug(self, type, structure):
        self.visual = Visual(self, type, structure)

    def timer(self):
        if (self.anim_end is False):
            self.anim.dalsia_faza()
            self.time = self.canvas.after(25, self.timer)
        else:
            del self.time

    def make_anim(self):
        self.anim = Anim(550, 400, self.anim_pic, self.canvas)

class Anim:

    def __init__(self, x, y, zoz, canv):
        self.canvas = canv
        self.canvas.pack()
        self.id = self.canvas.create_image(x, y)
        self.zoz = zoz
        self.faza = 0

    def dalsia_faza(self):
        if (self.faza == 53): self.faza = 0
        else: self.faza = (self.faza + 1)
        self.canvas.itemconfig(self.id, image = self.zoz[self.faza])


App()