import re
import tkinter as tk
from tkinter import ttk

from thonny import get_workbench
from thonny.ui_utils import SafeScrollbar


class OutlineView(ttk.Frame):
    def __init__(self, master):
        ttk.Frame.__init__(self, master)
        self._init_widgets()
        
        self._tab_changed_binding = get_workbench().get_editor_notebook().bind("<<NotebookTabChanged>>", self._update_frame_contents ,True)
        get_workbench().bind("Save", self._update_frame_contents, True)
        get_workbench().bind("SaveAs", self._update_frame_contents, True)
        get_workbench().bind_class("Text", "<<NewLine>>", self._update_frame_contents, True)
        
        self._update_frame_contents()
    
    def destroy(self):
        try:
            # Not sure if editor notebook is still living
            get_workbench().get_editor_notebook().unbind("<<NotebookTabChanged>>", self._tab_changed_binding)
        except Exception:
            pass
        self.vert_scrollbar["command"] = None
        ttk.Frame.destroy(self)
    
    def _init_widgets(self):
        #init and place scrollbar
        self.vert_scrollbar = SafeScrollbar(self, orient=tk.VERTICAL)
        self.vert_scrollbar.grid(row=0, column=1, sticky=tk.NSEW)

        #init and place tree
        self.tree = ttk.Treeview(self, yscrollcommand=self.vert_scrollbar.set)
        self.tree.grid(row=0, column=0, sticky=tk.NSEW)
        self.vert_scrollbar['command'] = self.tree.yview

        #set single-cell frame
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)

        #init tree events
        self.tree.bind("<Double-Button-1>", self._on_double_click, "+")

        #configure the only tree column
        self.tree.column('#0', anchor=tk.W, stretch=True)
        # self.tree.heading('#0', text='Item (type @ line)', anchor=tk.W)
        self.tree['show'] = ('tree',)
        
        self._class_img = get_workbench().get_image("outline-class")
        self._method_img = get_workbench().get_image("outline-method")

    def _update_frame_contents(self, event=None):
        self._clear_tree()
        
        editor = get_workbench().get_editor_notebook().get_current_editor()
        if editor is None:
            return
        
        root = self._parse_source(editor.get_code_view().get_content())
        for child in root[2]:
            self._add_item_to_tree('', child)
    
    def _parse_source(self, source):
        #all nodes in format (parent, node_indent, node_children, name, type, linenumber)
        root_node = (None, 0, [], None, None, None) #name, type and linenumber not needed for root
        active_node = root_node

        lineno = 0
        for line in source.split('\n'):
            lineno += 1
            m = re.match(r'[ ]*[\w]{1}', line)
            if m:
                indent = len(m.group(0))
                while indent <= active_node[1]:
                    active_node = active_node[0]

                t = re.match(r'[ ]*(?P<type>(def|class){1})[ ]+(?P<name>[\w]+)', line)
                if t:
                    current = (active_node, indent, [], t.group('name'), t.group('type'), lineno)
                    active_node[2].append(current)
                    active_node = current
        
        return root_node


    #adds a single item to the tree, recursively calls itself to add any child nodes
    def _add_item_to_tree(self, parent, item):
        #create the text to be played for this item
        item_type = item[4]
        item_text = " " + item[3]
        
        if item_type == "class":
            image = self._class_img
        elif item_type == "def":
            image = self._method_img
        else:
            image = None
        
        #insert the item, set lineno as a 'hidden' value
        current = self.tree.insert(parent, 'end', text=item_text, values = item[5], image=image)

        for child in item[2]:
            self._add_item_to_tree(current, child)
        
    #clears the tree by deleting all items      
    def _clear_tree(self):
        for child_id in self.tree.get_children():
            self.tree.delete(child_id)

    #called when a double-click is performed on any items
    def _on_double_click(self, event):
        editor = get_workbench().get_editor_notebook().get_current_editor()
        if editor:
            code_view = editor.get_code_view() 
            lineno = self.tree.item(self.tree.focus())['values'][0]
            index = code_view.text.index(str(lineno) + '.0')
            code_view.text.see(index) #make sure that the double-clicked item is visible
            code_view.select_lines(lineno, lineno)
            
            get_workbench().event_generate("OutlineDoubleClick",
                item_text=self.tree.item(self.tree.focus(), option='text'))

def load_plugin() -> None: 
    get_workbench().add_view(OutlineView, "Outline", "ne")
