"""CalendarTreeview class
Ttk treeview class with added functions.
"""
import tkinter as tk
from tkinter import ttk
from customtkinter import *
from bson.errors import InvalidId
from bson.objectid import ObjectId
from pollenisatorgui.core.application.dialogs.ChildDialogAskText import ChildDialogAskText
from pollenisatorgui.core.components.apiclient import APIClient
from pollenisatorgui.core.components.datamanager import DataManager
from pollenisatorgui.core.components.settings import Settings
from pollenisatorgui.core.forms.formpanel import FormPanel
from pollenisatorgui.core.models.interval import Interval
from pollenisatorgui.core.models.tags import Tag
from pollenisatorgui.core.models.ip import Ip
from pollenisatorgui.core.models.port import Port
from pollenisatorgui.core.models.scope import Scope
from pollenisatorgui.core.models.checkinstance import CheckInstance
from pollenisatorgui.core.models.wave import Wave
from pollenisatorgui.core.models.command import Command
from pollenisatorgui.core.views.intervalview import IntervalView
from pollenisatorgui.core.views.ipview import IpView
from pollenisatorgui.core.views.multipleipview import MultipleIpView
from pollenisatorgui.core.views.multiplescopeview import MultipleScopeView
from pollenisatorgui.core.views.multiselectionview import MultiSelectionView
from pollenisatorgui.core.views.portview import PortView
from pollenisatorgui.core.views.scopeview import ScopeView
from pollenisatorgui.core.views.toolview import ToolView
from pollenisatorgui.core.views.checkinstanceview import CheckInstanceView
from pollenisatorgui.core.views.waveview import WaveView
from pollenisatorgui.core.views.defectview import DefectView
from pollenisatorgui.core.views.commandview import CommandView
from pollenisatorgui.core.views.checkitemview import CheckItemView
from pollenisatorgui.core.controllers.wavecontroller import WaveController
from pollenisatorgui.core.controllers.checkinstancecontroller import CheckInstanceController
from pollenisatorgui.core.controllers.portcontroller import PortController
from pollenisatorgui.core.controllers.scopecontroller import ScopeController
from pollenisatorgui.core.controllers.toolcontroller import ToolController
from pollenisatorgui.core.controllers.defectcontroller import DefectController
from pollenisatorgui.core.controllers.ipcontroller import IpController
from pollenisatorgui.core.controllers.intervalcontroller import IntervalController
from pollenisatorgui.core.controllers.commandcontroller import CommandController
from pollenisatorgui.core.controllers.checkitemcontroller import CheckItemController
from pollenisatorgui.core.application.dialogs.ChildDialogProgress import ChildDialogProgress
from pollenisatorgui.core.application.dialogs.ChildDialogQuestion import ChildDialogQuestion
from pollenisatorgui.core.application.dialogs.ChildDialogCustomCommand import ChildDialogCustomCommand
from pollenisatorgui.core.application.dialogs.ChildDialogExportSelection import ChildDialogExportSelection
from pollenisatorgui.core.application.treeviews.PollenisatorTreeview import PollenisatorTreeview
import pollenisatorgui.core.components.utils as utils
from PIL import Image
from pollenisatorgui.core.components.logger_config import logger


class PentestTreeview(PollenisatorTreeview):
    """Inherit PollenisatorTreeview.
    Ttk treeview class with added functions to handle the main view objects.
    """

    def __init__(self, appli, parentFrame):
        """
        Args:
            appli: a reference to the main Application object.
            parentFrame: the parent tkinter window object.
        """
        super().__init__(appli, parentFrame)
        self._hidden = []  # Hidden objects references
        self._hidden_lkp = {}  # Hidden objects ids, avoid time consuming list comprehension
        self.tagsMenu = None  # Menu bar for tag changing
        self.tagsVars = None  # list of tag values
        self.contextualMenu = None  #  contextual menu to open on right click
        self.waves_node = None  # the wave parent node on the treeview
        self.ips_node = None  # the IPs parent node on the treeview
        self.openedViewFrameId = None  # id of opened objects in the view frame.
        self.commands_node = None  # parent of all commands nodes
        self.lazylimit = 10000
        self.lazyload = False
        self.first_load = True
        DataManager.getInstance().attach(self)

    def initUI(self):
        """Initialize the user interface widgets and binds them."""
        self._initContextualsMenus()
        self.heading('#0', text='Object', anchor=tk.W)
        self.column('#0', stretch=tk.YES, minwidth=300, width=300)
        self.bind("<Button-3>", self.doPopup)
        self.bind("<Button-2>", self.doPopupTag)
        self.bind("<<TreeviewSelect>>", self.onTreeviewSelect)
        self.bind("<<TreeviewOpen>>", self.onTreeviewOpen)
        self.bind("<Double-Button-1>", self.onTreeviewDoubleClick)
        self.bind('<Delete>', self.deleteSelected)
        self.bind('h', self.hideSelection)
        self.bind("<Shift-Down>", self.openNextSameTypeNode)
        self.bind("<Shift-Up>", self.openPrevSameTypeNode)
        self.filterFrame = CTkFrame(self.parentFrame, height=0)
        self.filterFrameInterior = CTkFrame(self.filterFrame, height=0)
        self.initFiltersFrame(self.filterFrameInterior)
        self.filterFrame.grid(row=2, column=0, columnspan=2)

        self.filter_open_image = CTkImage(Image.open(utils.getIcon("up-arrow.png")))
        self.filter_button = CTkButton(self.parentFrame, text="", image=self.filter_open_image,command=self.filter_toogle)
        self.filter_open = True
        self.filter_toogle(self.filter_open)

    def reopen(self,event=None):
        self.onTreeviewSelect()

    def _place_button_filter(self):
        self.filter_button.place(in_= self, rely=1.01, relx=0.5, anchor=tk.S, bordermode="inside")

    def filter_toogle(self, status=None):
        if status is None:
            self.filter_open = not self.filter_open
        else:
            self.filter_open = status
        if self.filter_open:
            self.filter_image = CTkImage(Image.open(utils.getIcon("down-arrow.png")))
            self.filterFrame.configure(height=self.filterFrameInterior.winfo_reqheight())
            self.filterFrameInterior.pack(fill=tk.X, padx=5, pady =10)
        else:
            self.filterFrameInterior.pack_forget()
            self.filter_image = CTkImage(Image.open(utils.getIcon("up-arrow.png")))
            self.filterFrame.configure(height=0)
        self.filter_button.configure(image=self.filter_image)
        

    def initFiltersFrame(self, frame):
        """Populate the filter frame with cool widgets"""
        settings = Settings()
        settings.reloadLocalSettings()
        form = FormPanel( pady=0, padx=0, fill="y")
        show_only_todo = settings.is_show_only_todo()
        hide_oos = settings.is_hide_oos()
        self.check_hide_oos = form.addFormSwitch("OOS", "Hide Out Of Scope hosts", hide_oos, command=self.hideOOSSwap, pady=0, padx=0, side="top", anchor="w")
        self.check_show_only_todo = form.addFormSwitch("Todos", "Show only todos", show_only_todo, command=self.showTodoSwap, pady=0, padx=0, side="top", anchor="w")
        show_only_manual = settings.is_show_only_manual()
        self.check_show_only_manual = form.addFormSwitch("Manuals", "Show only manual checks", show_only_manual, command=self.showManualSwap, pady=0, padx=0, side="top", anchor="w")
        form.constructView(frame)
        
    def hideOOSSwap(self, event=None):
        val = self.check_hide_oos.getValue()
        settings = Settings()
        settings.local_settings["hide_oos"] = val
        settings.saveLocalSettings()
        if val == 0:
            self.unfilter("filter_oos")
        else:
            self.filter_oos()

    def checklistViewSwap(self, event=None):
        self.refresh()
        self.filter_empty_nodes()

    def showTodoSwap(self, event=None):
        val = self.check_show_only_todo.getValue() 
        settings = Settings()
        settings.local_settings["show_only_todo"] = val
        settings.saveLocalSettings()
        if val == 0:
            self.unfilter("filter_empty")
            self.unfilter("filter_todo")
        else:
            self.filter_todo()

    def filter_empty_nodes(self):
        for key, values in self.views.items():
            view = values["view"]
            if not isinstance(view, CheckItemView):
                continue
            iid = view.controller.getDbId()
            try:
                if len(self.get_children(str(iid))) == 0:
                    view.hide("filter_empty")
            except tk.TclError:
                pass

    def filter_todo(self):
        for values in self.views.values():
            view = values["view"]
            if not isinstance(view, CheckInstanceView):
                continue
            status = view.controller.getStatus()
            if status == "":
                status = view.controller.model.status
            if status == "":
                status = "todo"
            if status != "todo":
                view.hide("filter_todo")
        self.filter_empty_nodes()
    
    def filter_oos(self):
        for key, values in self.views.items():
            view = values["view"]
            if not isinstance(view, IpView):
                continue
            tags = self.item(key)["tags"]
            if "OOS" in tags:
                view.hide("filter_oos")
    
    def showManualSwap(self, event=None):
        val = self.check_show_only_manual.getValue() 
        settings = Settings()
        settings.local_settings["show_only_manual"] = val
        settings.saveLocalSettings()
        if val == 0:
            self.unfilter("filter_empty")
            self.unfilter("filter_manual")
        else:
            self.filter_manual()
        
    def filter_manual(self):
        for values in self.views.values():
            view = values["view"]
            if not isinstance(view, CheckInstanceView) and not isinstance(view, CheckItemView):
                continue
            if view.controller.isAuto():
                view.hide("filter_manual")
        self.filter_empty_nodes()
    
    def unfilter(self, reason):
        self.unhide(reason)


    def getRows(self, startNode=''):
        """Returns all child nodes of the given startNode iid as a list.
        Args:
            - startNode: node to recursively get children. Default to '' which is rhe treeview root.
        Returns:
            - List of all children iid of given node.
        """
        myRows = []
        children = self.get_children(startNode)
        for child in children:
            myRows.append(child)
            myRows += self.getRows(child)
        return myRows

    def openPrevSameTypeNode(self, _event=None):
        """Open the first node of the same type above the currently selected object on the tree view.
        Args:
            - _event: not used but mandatory
        Return:
            return the string "break" to stop processing the event
        """
        fromNode = self.selection()
        if fromNode:
            fromNode = fromNode[0]
            rows = self.getRows()
            pos = rows.index(fromNode)
            if pos-1 > 0:
                view_o = self.getViewFromId(fromNode)
                classToFind = view_o.__class__
                for row_nth in range(pos-1, -1, -1):
                    row = rows[row_nth]
                    cmp_view_o = self.getViewFromId(row)
                    if classToFind == cmp_view_o.__class__:
                        self.see(row)
                        self.focus(row)
                        self.selection_set(row)
                        return "break"
        return "break"

    def openNextSameTypeNode(self, _event=None):
        """Open the first node of the same type below the currently selected object on the tree view.
        Args:
            - _event: not used but mandatory
        Return:
            return the string "break" to stop processing the event
        """
        fromNode = self.selection()
        if fromNode:
            fromNode = fromNode[0]
            rows = self.getRows()
            pos = rows.index(fromNode)
            if pos+1 < len(rows):
                view_o = self.getViewFromId(fromNode)
                classToFind = view_o.__class__
                for row in rows[pos+1:]:
                    cmp_view_o = self.getViewFromId(row)
                    if classToFind == cmp_view_o.__class__:
                        self.see(row)
                        self.focus(row)
                        self.selection_set(row)
                        return "break"
        return "break"

    def doPopup(self, event):
        """Called when a right click is received by the tree view.
        Open the contextual menu at the clicked position.
        Args:
            - event: sent automatically though an event on treeview
        """
        if self.contextualMenu is not None:
           self.popupFocusOut()
        self.contextualMenu.selection = self.identify(
            "item", event.x, event.y)
        view = self.getViewFromId(str(self.contextualMenu.selection))
        additonal_commands = {}
        apiclient = APIClient.getInstance()
        datamanager = DataManager.getInstance()
        if view is None:
            item = self.contextualMenu.selection
            if str(item) == "waves":
                view = WaveView(self, self.appli.viewframe,
                                       self.appli, WaveController(Wave()))
            elif str(item) == "mycommands":
                user = apiclient.getUser()
                view = CommandView(
                            self, self.appli.viewframe, self.appli, CommandController(Command({"indb": apiclient.getCurrentPentest(), "owners": [user]})))
            elif str(item) == "commands":
                view = CommandView(
                        self, self.appli.viewframe, self.appli, CommandController(Command({"indb": apiclient.getCurrentPentest()})))
            elif str(item) == "ips":
                view = MultipleIpView(
                        self, self.appli.viewframe, self.appli, IpController(Ip()))
            elif "intervals" in str(item):
                wave = datamanager.get("waves",ObjectId(IntervalView.treeviewListIdToDb(item)))
                view = IntervalView(self, self.appli.viewframe, self.appli, IntervalController(
                        Interval().initialize(wave.wave)))
            elif "scopes" in str(item):
                wave = datamanager.get("waves",ObjectId(ScopeView.treeviewListIdToDb(item)))
                view = MultipleScopeView(
                            self, self.appli.viewframe, self.appli, ScopeController(Scope().initialize(wave.wave)))
        if view is not None:
            additonal_commands |= view.getAdditionalContextualCommands()
            
        if self.appli.searchMode:
            additonal_commands["Show in full tree"] = self.showInTreeview

        self._initContextualsMenus(additonal_commands)
        
        super().doPopup(event)

    def doPopupTag(self, event):
        """Called when a middle click is received by the tree view.
        Open the tag menu at the clicked position.
        Args:
            - event: sent automatically though an event on treeview
        """
        self.tagsMenu.selection = self.identify(
            "item", event.x, event.y)
        # display the popup menu
        try:
            self.tagsMenu.tk_popup(event.x_root, event.y_root)
        finally:
            # make sure to release the grab (Tk 8.0a1 only)
            self.tagsMenu.grab_release()
        self.tagsMenu.focus_set()
        self.tagsMenu.bind('<FocusOut>', self.popupFocusOutTag)

    def popupFocusOutTag(self, _=None):
        """Called when the tag contextual menu is unfocused.
        Close the tag contextual menu.
        """
        self.tagsMenu.unpost()

    def attackInTerminal(self, check_view):
        self.appli.open_terminal(str(check_view.controller.getDbId()), check_view.controller.target_repr)

    def insertViewById(self, node_id):
        view = self.views.get(node_id)
        if view is not None:
            view["view"].addInTreeview(insert_parents=True, addChildren=False)

    def showInTreeview(self, _=None):
        """Unfilter the treeview and focus the node stored in the contextualMenu.selection variable0
        Also select it.
        """
        node = str(self.contextualMenu.selection)
        self.appli.endSearch()
        try:
            self.delete("search")
        except tk.TclError:
            pass
        view = self.views.get(node)["view"]
        if view:
            self.unhideAll()
            view.addInTreeview(insert_parents=True)
            self.see(node)
            self.focus(node)
            self.selection_set(node)

    def tagClicked(self, name):
        """Callback for an event. If the function was called directly it would not work.
        Args:
            - name: the tag name clicked
        """
        return lambda: self.setTagFromMenubar(name)

    def _initContextualsMenus(self, additional_commands=None):
        """
        Create the contextual menu
        """
        additional_commands = {} if additional_commands is None else additional_commands
        self.contextualMenu = utils.craftMenuWithStyle(self.parentFrame)
        for command_name, command_func in additional_commands.items():
            self.contextualMenu.add_command(
                label=command_name, command=command_func)
        self.contextualMenu.add_command(
            label="Export selection", command=self.exportSelection)
        self.contextualMenu.add_separator()
        for module in self.appli.modules:
            if callable(getattr(module["object"], "_initContextualsMenus", None)):
                menu = module["object"]._initContextualsMenus(self.parentFrame)
                self.contextualMenu.add_cascade(
                    label=module["name"].strip(), menu=menu)
        self.tagsMenu = utils.craftMenuWithStyle(self.parentFrame)
        tags = Settings.getTags()
        listOfLambdas = [self.tagClicked(tag) for tag in list(tags.keys())]
        for i, val in enumerate(tags):
            self.tagsMenu.add_command(
                label=val, command=listOfLambdas[i])
        #self.contextualMenu.add_cascade(label="Tags", menu=self.tagsMenu)
        self.contextualMenu.add_command(
            label="Sort children", command=self.sort)
        self.contextualMenu.add_command(
            label="Expand", command=self.expand)
        self.contextualMenu.add_command(
            label="Collapse", command=self.collapse)
        self.contextualMenu.add_command(
            label="Hide", command=self.hideAndUpdate)
        self.contextualMenu.add_command(
            label="Unhide children", command=lambda: self.unhideNodeChildren("*"))
        self.contextualMenu.add_separator()
        self.contextualMenu.add_command(
            label="Debug", command=self.showDebug)
        self.contextualMenu.add_command(
            label="Close", command=self.closeMenu)
        return self.contextualMenu

    def setTagFromMenubar(self, name):
        """
        Change the tags of every selected object in the treeview to the one selected in the tag contextual menu.
        Args:
            - name: the tag name clicked
        """
        for selected in self.selection():
            view_o = self.getViewFromId(selected)
            view_o.controller.setTags([name])

    def closeMenu(self, _event=None):
        """
        Close the contextual menu. Does nothing, just an empty callback
        Args:
            - _event: not used but mandatory
        """
        # Do nothing and close
        return

    def modelToView(self, collection, model):
        view = None
        if not collection.endswith("s"):
            collection += "s"
        if collection == "tools":
            view = ToolView(self, self.appli.viewframe,
                            self.appli, ToolController(model))
        elif collection == "waves":
            view = WaveView(self, self.appli.viewframe,
                            self.appli, WaveController(model))
        elif collection == "scopes":
            view = ScopeView(self, self.appli.viewframe,
                                self.appli, ScopeController(model))
            
        elif collection == "ports":
            view = PortView(self, self.appli.viewframe,
                            self.appli, PortController(model))
        elif collection == "ips":
            view = IpView(self, self.appli.viewframe,
                            self.appli, IpController(model))
        elif collection == "intervals":
            view = IntervalView(self, self.appli.viewframe,
                                self.appli, IntervalController(model))
        elif collection == "defects":
            view = DefectView(self, self.appli.viewframe,
                                self.appli, DefectController(model))
        elif collection == "commands":
            view = CommandView(self, self.appli.viewframe,
                                self.appli, CommandController(model))
        elif collection == "checkinstances":
                check_i = model
                view = CheckInstanceView(self, self.appli.viewframe,
                                         self.appli, CheckInstanceController(check_i))
        elif collection == "checkitems":
                check_i = model
                view = CheckItemView(self, self.appli.viewframe,
                                         self.appli, CheckItemController(check_i))
                                        
                # parent_view = CheckItemView(self, self.appli.viewframe,
                #                          self.appli, CheckItemController(check_i.check_m))
                # parent_view.addInTreeview()
        return view

    def update_received(self, dataManager, notif, obj, old_obj):
        apiclient = APIClient.getInstance()
        if not apiclient.getCurrentPentest() != "":
            return
        if apiclient.getCurrentPentest() != notif["db"]:
            return
        collection = notif["collection"]
        # Delete
        if notif["action"] == "delete":
            try:
                self.delete(ObjectId(notif["iid"]))
            except tk.TclError:
                pass  # item was not inserted in the treeview
            if str(notif["iid"]) == self.openedViewFrameId:
                self.event_generate("<F6>") # event generation is handled in main thread (tkinter is not thread safe, so refrshing of view must be done through events)
        # Insert
        if collection == "tags" and notif["action"] == "insert":
            iid = obj.item_id
            try:
                self.item(str(iid), tags=obj.tags)
            except tk.TclError:
                pass
            return
        if notif["action"] == "insert":
            view = None
            if obj is None:
                return
            model = obj
            view = self.modelToView(collection, model)
            try:
                if view is not None:
                    view.addInTreeview()
                    view.insertReceived()
            except tk.TclError:
                pass
        elif notif["action"] == "insert_many":
            view = None
            if not obj:
                return
            for model in obj:
                view = self.modelToView(collection, model)
                try:
                    if view is not None:
                        view.addInTreeview(insert_parents=True)
                        view.insertReceived()
                except tk.TclError:
                    pass
        elif notif["action"] == "update_many":
            if not obj:
                return
            for model in obj:
                view = self.getViewFromId(model.getId())
                if view is not None:
                    tags = view.controller.getTags()
                    try:
                        self.item(str(model.getId()), tags=tags, image=view.getIcon())
                        if "hidden" in tags:
                            self.hide("tags")
                    except tk.TclError:
                        if view is not None:
                            view.addInTreeview(insert_parents=True)
                    if view is not None:
                        view.updateReceived(obj, old_obj)
                    if model.getId() == self.openedViewFrameId:
                        self.event_generate("<F6>")
        if notif["action"] == "update":
            try:
                if obj is None:
                    return
                self.updateView(str(notif["iid"]), obj)
                view = self.getViewFromId(str(notif["iid"]))
                if view is not None:
                    item = self.item(str(notif["iid"]))
                    tags = view.controller.getTags()
                    self.item(str(notif["iid"]), tags=tags, image=view.getIcon())
                    if "hidden" in tags:
                        self.hide("tags")
            except tk.TclError:
                if view is not None:
                    view.addInTreeview()
            
            if view is not None:
                view.updateReceived(obj, old_obj)
            if str(notif["iid"]) == self.openedViewFrameId:
                self.event_generate("<F6>")

    def exportSelection(self, _event=None):
        """
        Popup a window to help a user to export some data from treeview.
        Args:
            _event: not used but mandatory
        """
        values = set()
        for selected in self.selection():
            view_o = self.getViewFromId(selected)
            if view_o is not None:
                data = view_o.controller.getData()
                for key in data.keys():
                    if key == "infos":
                        for info_keys in data["infos"]:
                            values.add("infos."+info_keys)
                    else:
                        values.add(key)
        dialog = ChildDialogExportSelection(self, values)
        self.wait_window(dialog.app)
        if isinstance(dialog.rvalue, list):
            fields_to_export = dialog.rvalue
            f = tk.filedialog.asksaveasfilename(parent=self, defaultextension=".csv")
            # asksaveasfile return `None` if dialog closed with "cancel".
            if f is None or len(f) == 0:
                return
            csv_filename = str(f)
            with open(csv_filename, mode='w') as f:
                f.write(", ".join(fields_to_export)+"\n")
                for selected in self.selection():
                    view_o = self.getViewFromId(selected)
                    if view_o is not None:
                        line = []
                        modelData = view_o.controller.getData()
                        for field in fields_to_export:
                            if field.startswith("infos."):
                                line.append(
                                    str(modelData.get("infos", {}).get(field[6:], "")))
                            else:
                                line.append(str(modelData.get(field, "")))
                        f.write(", ".join(line)+"\n")
            dialog = ChildDialogQuestion(
                self, "Export completed", "Your export just finished : "+csv_filename+".\n Do you want to open this folder ?")
            self.wait_window(dialog.app)
            if dialog.rvalue == "Yes":
                utils.openPathForUser(csv_filename, folder_only=True)

    def onTreeviewOpen(self, event=None):
        """Called when an item is opened, lazy load its children.
        """
        if not self.lazyload:
            return
        opened = event.widget.focus()
        if opened == "" or opened is None:
            return
        objView = self.getViewFromId(str(opened))
        if objView is not None:
            objView.opened()
        return 
    
    def onTreeviewSelect(self, event=None):
        """Called when a line is selected on the treeview
        Open the selected object view on the view frame.
        Args:
            _event: not used but mandatory
        """
        selection = self.selection()
        if len(selection) == 1:
            item = super().onTreeviewSelect(event)
            if isinstance(item, str):
                if item.startswith("multi|"):
                    objView = self.getViewFromId(str(item))
                    self.openModifyWindowOf(item)
                #self._openInsertWindow(item)
            else:
                objView = self.getViewFromId(str(item))
                objView.opened()
                self.item(selection, open=True)
                self.openModifyWindowOf(item)
        elif len(selection) > 1:
            prev_type = None
            all_the_same = True
            for item in selection:
                items = {}
                try:
                    item_iid = ObjectId(item)
                    objView = self.getViewFromId(str(item_iid))
                    if objView is not None:
                        items[str(item_iid)] = objView
                        curr_type = objView.__class__
                        if prev_type is not None and prev_type != curr_type:
                            all_the_same = False
                            break
                        prev_type = curr_type
                except InvalidId:
                    break
            if all_the_same and prev_type.multiview_class is not None:
                multiView = prev_type.multiview_class(
                    self, self.appli.viewframe, self.appli)
            else:
            # Multi select:
                multiView = MultiSelectionView(
                    self, self.appli.viewframe, self.appli)
            for widget in self.appli.viewframe.winfo_children():
                widget.destroy()
            multiView.form.clear()
            multiView.openModifyWindow()

    def onTreeviewDoubleClick(self,  event=None):
        selection = self.selection()
        if len(selection) == 1:
            item = super().onTreeviewSelect(event)
            objView = self.getViewFromId(str(item))
            if objView is not None:
                if hasattr(objView, "onDoubleClick") and callable(objView.onDoubleClick):
                    objView.onDoubleClick()

    def _openInsertWindow(self, item):
        apiclient = APIClient.getInstance()
        self.saveState(apiclient.getCurrentPentest())
        if str(item) == "waves":
            objView = WaveView(self, self.appli.viewframe,
                                       self.appli, WaveController(Wave()))
            objView.openInsertWindow()
        elif str(item) == "mycommands":
            user = apiclient.getUser()
            objView = CommandView(
                        self, self.appli.viewframe, self.appli, CommandController(Command({"indb": apiclient.getCurrentPentest(), "owners": [user]})))
            objView.openInsertWindow()
        elif str(item) == "commands":
            objView = CommandView(
                        self, self.appli.viewframe, self.appli, CommandController(Command({"indb": apiclient.getCurrentPentest()})))
            objView.openInsertWindow()
        elif str(item) == "ips":
            objView = MultipleIpView(
                        self, self.appli.viewframe, self.appli, IpController(Ip()))
            objView.openInsertWindow()
        elif "intervals" in str(item):
            wave = Wave.fetchObject(
                        {"_id": ObjectId(IntervalView.treeviewListIdToDb(item))})
            objView = IntervalView(self, self.appli.viewframe, self.appli, IntervalController(
                        Interval().initialize(wave.wave)))
            objView.openInsertWindow()
        elif "scopes" in str(item):
            wave = Wave.fetchObject(
                        {"_id": ObjectId(ScopeView.treeviewListIdToDb(item))})
            objView = MultipleScopeView(
                        self, self.appli.viewframe, self.appli, ScopeController(Scope().initialize(wave.wave)))
            objView.openInsertWindow()

    def load(self, searchModel=None):
        """
        Load the treeview with database information
        Args:
            searchModel: (DEPRECATED) a search object default to None
        """
        for widget in self.appli.viewframe.winfo_children():
            widget.destroy()
        # Reattach hidden as get_children won't get them otherwise
        self.unhideAll()
        # Clear the tree
        apiclient = APIClient.getInstance()
        self.heading("#0", text=apiclient.getCurrentPentestName())
        self.delete(*self.get_children())
        
        self._hidden = []
        self._hidden_lkp = {}
        if searchModel is None or searchModel.query == "":
            self._load()
        else:
            viewsFound = searchModel.getViews(
                self, self.appli.viewframe, self.appli)
            for viewFound in viewsFound:
                viewFound.addInTreeview('', addChildren=False)
        self.configureTags()
        self.loadState(apiclient.getCurrentPentest())

    # def changeSize(self, event):
    #     self._place_button_filter()


    def refresh(self, force=True):
        """Alias to load function"""
        self.after(0, self._place_button_filter)
        if force or self.first_load:
            self.first_load = False
            self.unfilterAll()
            #DataManager.getInstance().load(collections=["ip", "port","wave","scope","interval", "command"],forceReload=force)
            
            #DataManager.getInstance().load(collections=["ip", "wave","scope","interval", "command"],forceReload=force)
            self.load()
        
    def _load(self):
        """ 
        Load the treeview with database information
        """
        self.appli.settings.reloadSettings()

        checklistview = self.appli.settings.is_checklist_view()

        apiclient = APIClient.getInstance()
        nbObjects = apiclient.count("checkinstances")
        if nbObjects > self.lazylimit and not self.lazyload:
            self.lazyload = True
            tk.messagebox.showwarning(
                "Warning Many Objects detected", "Big pentest detected. Lazy loading will be used. (required data will be downloaded on request)")
        dialog = ChildDialogProgress(self.appli, "Loading "+str(
            apiclient.getCurrentPentestName()), "Opening "+str(apiclient.getCurrentPentestName()) + ". Please wait for a few seconds.", speed=10, progress_mode="determinate")
        self.unhideAll()
        for child in self.get_children():
            self.delete(child)   
        self._hidden = []
        self._hidden_lkp = {}
        dataManager = DataManager.getInstance()
        self.appli.statusbar.reset()
        dialog.update(msg="Insertings cheatsheets")
        # self.commands_node = self.insert(
        #     self.commands_node, "end", "mycommands", text="Commands", image=CommandView.getClassIcon())        # Loading every category separatly is faster than recursivly.
        # This is due to cursor.next function calls in pymongo
        # Adding wave objects
        # LOAD BIG PENTEST OBJECT
        dialog.update(msg="Insertings ips")
        # Adding ip objects
        try:
            self.ips_node = self.insert("", "end", str(
                "ips"), text="IPs", image=IpView.getClassIcon())
        except tk.TclError as e:
            print(e)
        dialog.update(msg="Insertings checkinstances node")
        try:
            self.commands_node = self.insert(
            "", "end", "commands", text="Commands", image=CommandView.getClassIcon())
        except tk.TclError as e:
            pass
        
        if checklistview:
            checks = list(dataManager.get("checkitem", '*'))
            apiclient = APIClient.getInstance()
            checks.sort(key=lambda x: x.priority)
            for check in checks:
                count = apiclient.count("checkinstances", {"check_iid": str(check._id)})
                if count > 0:
                    check_vw = CheckItemView(
                        self, self.appli.viewframe, self.appli, CheckItemController(check))
                    check_vw.addInTreeview(None, with_category=True, addChildren=False, count_children=count)
            if not self.lazyload:
                tags = dataManager.find("tags", {"item_type": "checkinstances"}, fetch_on_none=True)
                tag_lkp = {}
                for tag in tags:
                    tag_lkp[str(tag.item_id)] = tag
                checkinstances = dataManager.get("checkinstance", '*')
                check_lkp = {str(checkinstance.getId()): checkinstance for checkinstance in checkinstances}
                targets_repr = apiclient.getCheckInstanceRepr(list(check_lkp.keys()))
                for checkinstance in checkinstances:
                    check_o = CheckInstanceController(checkinstance)
                    check_o.target_repr = targets_repr.get(str(checkinstance.target_iid), "<Removed>")
                    checkinstance_vw = CheckInstanceView(
                        self, self.appli.viewframe, self.appli, check_o)
                    checkinstance_vw.addInTreeview(None, addChildren=False, tags=tag_lkp.get(str(checkinstance.getId()), []))
        
        else:
            try:
                self.waves_node = self.insert("", "end" , str(
                    "waves"), text="Scope options", image=WaveView.getClassIcon())
            
            except tk.TclError as e:
                pass
            
            
            
                #dialog.update()
            dialog.update(msg="Insertings commands")
            for command in list(dataManager.get("command", '*')):
                command_vw = CommandView(
                    self, self.appli.viewframe, self.appli, CommandController(command))
                command_vw.addInTreeview()
                #dialog.update()
            dialog.update(msg="Insertings waves ")
            waves = list(dataManager.get("wave", '*'))
            for wave in waves:
                wave_o = WaveController(wave)
                wave_vw = WaveView(
                    self, self.appli.viewframe, self.appli, wave_o)
                wave_vw.addInTreeview(self.waves_node, addChildren=False)
                
            # dialog.update()
            intervals = list(dataManager.get("interval", '*'))
            for interval in intervals:
                interval_o = IntervalController(interval)
                interval_vw = IntervalView(
                    self, self.appli.viewframe, self.appli, interval_o)
                interval_vw.addInTreeview(None, addChildren=False)
                #dialog.update()
            scopes = list(dataManager.get("scope", '*'))
            for scope in scopes:
                scope_o = ScopeController(scope)
                scope_vw = ScopeView(
                    self, self.appli.viewframe, self.appli, scope_o)
                scope_vw.addInTreeview(None, addChildren=False)
            dialog.update(msg="Insertings ips")
            
            if not checklistview:
                ips = list(dataManager.get("ip", '*'))
                for ip in ips:
                    ip_o = IpController(ip)
                    ip_vw = IpView(self, self.appli.viewframe, self.appli, ip_o)
                    ip_vw.addInTreeview(self.ips_node, addChildren=False)
                
            if not self.lazyload:
                dialog.update(msg="Insertings ports")
                    #dialog.update()
                # Adding port objects
                ports = list(dataManager.get("port", '*'))
                for port in ports:
                    port_o = PortController(port)
                    port_vw = PortView(
                        self, self.appli.viewframe, self.appli, port_o)
                    port_vw.addInTreeview(None, addChildren=False)
        # LOAD MODULE PENTEST OBJECTS        
        for module in self.appli.modules:
            if hasattr(module["object"], "onTreeviewLoad"):
                module["object"].onTreeviewLoad(checklistview, self.lazyload)
        # ADD DEFECT, CHECKINSTANCE OBJECTS and TOOL IF NOT LAZYLOAD AND NOT CHECKLISTVIEW
        if not checklistview:
            if not self.lazyload:
                #dialog.update()
                # Adding defect objects
                dialog.update(msg="Insertings defects")
                defects = list(dataManager.get("defect", '*'))
                for defect in defects:
                    defect_o = DefectController(defect)
                    defect_vw = DefectView(
                        self, self.appli.viewframe, self.appli, defect_o)
                    defect_vw.addInTreeview(None)
                dialog.update(msg="Insertings check instances")
                apiclient = APIClient.getInstance()

                checks = list(dataManager.get("checkinstance", '*'))
                check_lkp = {check.getId(): check for check in checks}
                targets_repr = apiclient.getCheckInstanceRepr(list(check_lkp.keys()))
                for check in checks:
                    check_o = CheckInstanceController(check)
                    check_o.target_repr = targets_repr.get(str(check.target_iid), "<Removed>")
                    check_vw = CheckInstanceView(
                        self, self.appli.viewframe, self.appli, check_o)
                    check_vw.addInTreeview(None, refresh_status=False)
                dialog.update(msg="Insertings tools")
                tools = list(dataManager.get("tools",'*'))
                for tool in tools:
                    tool_o = ToolController(tool)
                    tool_vw = ToolView(
                        self, self.appli.viewframe, self.appli, tool_o)
                    tool_vw.addInTreeview(None)
        dialog.update(msg="Preparing treeview...")
        if not checklistview:
            # Adding Tools objects
            self.open("scopes")
            if not self.lazyload:
                self.open("ips")
            self.sort(self.ips_node)
        else:
            try:
                self.hide(self.ips_node, "checklist_view")
                self.hide(self.commands_node, "checklist_view")
                self.hide(self.waves_node, "checklist_view")
            except tk.TclError as e:
                pass
        dialog.destroy()


    def openModifyWindowOf(self, dbId):
        """
        Retrieve the View of the database id given and open the modifying form for its model.
        Args:
            dbId: the Mongo Id to open the modification form on.
        """
        # self.resetTags()
        objView = self.getViewFromId(str(dbId))
        if objView is not None:
            for widget in self.appli.viewframe.winfo_children():
                widget.destroy()
            objView.form.clear()
            self.openedViewFrameId = str(dbId)
            objView.openModifyWindow(treevw="pentest")
           

    # def unhide(self, node=None, reason=""):
    #     """Unhide children of given node.
    #     Args:
    #         - node: the node which we want to unhide the children.
    #                 If this value is None, use the contextualMenu.selection value
    #                 Default to None.
    #     """
    #     nodeToUnhideChildren = str(
    #         self.contextualMenu.selection) if node is None else node

    #     for hidden in self._hidden[::-1]:
    #         itemId = hidden[0]
    #         parentId = '' if hidden[1] is None else hidden[1]
    #         if str(parentId) == str(nodeToUnhideChildren):
    #             view_o = self.getViewFromId(str(itemId))
    #             if view_o is not None:
    #                 view_o.controller.delTag("hidden")
    #             try:
    #                 self.reattach(itemId, parentId, 0)
    #             except tk.TclError:
    #                 pass  # Le noeud a ete supprime entre temps

    def hideAndUpdate(self):
        """Hide object with contextualMenu attached in the treeview and store this effect in its tags."""
        self.hide(None, "manual_hide", True)

    def showDebug(self):
        """Hide object with contextualMenu attached in the treeview and store this effect in its tags."""

        node = str(self.contextualMenu.selection)
        view_o = self.getViewFromId(node)
        msg = str(view_o)+"\n"+str(view_o.controller.getDbId())+"\n"
        data = view_o.controller.getData()
        msg += str(data)
        dialog = ChildDialogAskText(self, "Debug", default=msg)
        self.wait_window(dialog.app)

    def hideSelection(self, _event=None):
        """ Hide selected objects in the treeview and store this effect in their tags."""
        selectedNodes = self.selection()
        for node in selectedNodes:
            self.hide(node, "manual_hide", True)

    def hide(self, node=None, reason="manual_hide", updateTags=False):
        """Hide given node object in the treeview and can store this effect in its tag.
        Args:
            - node: node to hide. If none is given, the contextualMenu.selection value will be used.
                    Default to None.
            - updateTags: mark the object as hidden in its tags. Default to False
        """
        if node is None and not hasattr(self.contextualMenu, "selection"):
            return
        nodeToHide = str(
            self.contextualMenu.selection) if node is None else node
        view_o = self.getViewFromId(nodeToHide)
        if view_o is not None:
            parent = view_o.getParentNode()
        else:
            parent = self.parent(node)
        hidd = self._hidden_lkp.get(str(nodeToHide), None)
        if hidd is not None:
            self._hidden[hidd[0]][2].append(reason)
        else:
            self._hidden.append([nodeToHide, parent, [reason]])
            self._hidden_lkp[str(nodeToHide)] = (len(self._hidden)-1, parent)
            
            
        
        
        try:
            if updateTags and view_o is not None:
                view_o.controller.addTag("hidden")
            self.detach(nodeToHide)
        except tk.TclError:
            pass

    def parent(self, itemiid):
        data = self._hidden_lkp.get(str(itemiid))
        if data is None:
            return super().parent(itemiid)
        parentId = '' if data[1] is None else data[1]
        return parentId
        # for hidden in self._hidden[::-1]:
        #     itemId = hidden[0]
        #     parentId = '' if hidden[1] is None else hidden[1]
        #     if str(itemId) == str(itemiid):
        #         return parentId
        #return super().parent(itemiid)

    def showItem(self, item):
        self.see(str(item))
        self.selection_set(str(item))
        self.focus(str(item))
