import os
import logging
import tempfile

from dataclasses import dataclass

from direct.gui import DirectGuiGlobals as DGG
from direct.gui.DirectFrame import DirectFrame
from direct.gui.DirectDialog import YesNoDialog

from DirectFolderBrowser.DirectFolderBrowser import DirectFolderBrowser
from Panda3DNodeEditor.Tools.JSONTools import JSONTools

@dataclass
class CodePart:
    imports: str = ""
    variable: str = ""
    init_snippet: str = ""
    snippet: str = ""
    write_snippet: bool = True

class PythonExporter:
    def __init__(self, nodes, connections):
        self.nodes = nodes
        self.connections = connections
        self.indent = "    "
        self.indent_count = 2
        self.node_code_dict = {}
        self.browser = DirectFolderBrowser(self.save, True, defaultFilename="newModule.py")

    def save(self, doSave):
        if doSave:
            self.dlg_overwrite = None
            self.dlg_overwrite_shadow = None
            path = self.browser.get()
            path = os.path.expanduser(path)
            path = os.path.expandvars(path)
            if os.path.exists(path):
                self.dlg_overwrite = YesNoDialog(
                    text="File already Exist.\nOverwrite?",
                    relief=DGG.RIDGE,
                    frameColor=(1,1,1,1),
                    frameSize=(-0.5,0.5,-0.3,0.2),
                    sortOrder=1,
                    button_relief=DGG.FLAT,
                    button_frameColor=(0.8, 0.8, 0.8, 1),
                    command=self.__execute_save,
                    extraArgs=[path],
                    scale=300,
                    pos=(base.getSize()[0]/2, 0, -base.getSize()[1]/2),
                    parent=base.pixel2d)
                self.dlg_overwrite_shadow = DirectFrame(
                    pos=(base.getSize()[0]/2 + 10, 0, -base.getSize()[1]/2 - 10),
                    sortOrder=0,
                    frameColor=(0,0,0,0.5),
                    frameSize=self.dlg_overwrite.bounds,
                    scale=300,
                    parent=base.pixel2d)
            else:
                self.__execute_save(True, path)
            base.messenger.send("setLastPath", [path])
        self.browser.destroy()
        del self.browser

    def __execute_save(self, overwrite, path):
        if self.dlg_overwrite is not None: self.dlg_overwrite.destroy()
        if self.dlg_overwrite_shadow is not None: self.dlg_overwrite_shadow.destroy()
        if not overwrite: return

        leaf_nodes = []
        for node in self.nodes:
            if node.isLeaveNode():
                leaf_nodes.append(node)

        for leaf in leaf_nodes:
            self.__create_code(leaf)

        classname = os.path.basename(path)[:-3]
        # ensure uppercase first letter
        classname = classname[0].upper() + classname[1:]

        code = "# This code was generated by FRAME\n"
        code += "\n"
        code += "from direct.showbase.DirectObject import DirectObject\n"

        # import block
        for node, code_part in self.node_code_dict.items():
            imports = code_part.imports
            if imports == "":
                continue
            code += imports
            code += "\n"

        code += "\n"

        # class setup and init
        code += f"class {classname}(DirectObject):\n"
        code += "    def __init__(self):\n"
        code += "        DirectObject.__init__(self)\n"
        code += "\n"
        indent = self.indent*self.indent_count
        for node, code_part in self.node_code_dict.items():
            snippet = code_part.init_snippet
            if snippet == "":
                continue
            code += indent + snippet
            code += "\n"

        # the remaining code
        for node, code_part in self.node_code_dict.items():
            if not code_part.write_snippet:
                continue
            snippet = code_part.snippet
            if snippet == "":
                continue
            code += indent + snippet
            code += "\n"

        with open(path, 'w') as outfile:
            outfile.write(code)

    def __create_code(self, node):
        code_part = None
        if node.name == "Call script func":
            code_part = self.__create_script_call(node)
        elif node.name == "Catch Event":
            code_part = self.__create_catch_event(node)
        elif node.name == "Script":
            code_part = self.__create_load_script(node)

        if code_part is not None:
            self.node_code_dict[node] = code_part
        return code_part

    def __get_or_create_code_part(self, node):
        if node in self.node_code_dict:
            return self.node_code_dict[node]
        else:
            return self.__create_code(node)

    def __create_script_call(self, node):
        code_part = CodePart()
        script_node = self.__find_node_in_connection(node, "Script")
        script_code_part = None
        if script_node is not None:
            script_code_part = self.__get_or_create_code_part(script_node)
        if script_code_part is None:
            if node.inputList[0].getValue() != "":
                code_part.snippet = f"{node.inputList[0].getValue()}.{node.inputList[1].getValue()}()"
            else:
                code_part.snippet = f"self.{node.inputList[1].getValue()}()"
        else:
            code_part.snippet = f"{script_code_part.variable}.{node.inputList[1].getValue()}()"
        code_part.write_snippet = False
        return code_part

    def __create_catch_event(self, node):
        code_part = CodePart()
        call_node = self.__find_node_in_connection(node, "Call script func")
        sub_code_part = self.__get_or_create_code_part(call_node)
        if sub_code_part is None:
            return None
        code_part.snippet = f"self.accept(\"{node.inputList[0].getValue()}\", {sub_code_part.snippet[:-2]}, [])"
        return code_part

    def __create_load_script(self, node):
        code_part = CodePart()
        modules = node.inputList[0].getValue()
        classname = modules.split(".")[-1]
        classname = classname[0].upper() + classname[1:]
        code_part.imports = f"from {modules} import {classname}"
        var_name = classname[0].lower() + classname[1:]
        code_part.init_snippet = f"self.{var_name} = {classname}()"
        code_part.variable = f"self.{var_name}"
        return code_part

    def __find_node_in_connection(self, connected_with_node, node_type):
        for connector in self.connections:
            if connector.socketA.node is connected_with_node \
            or connector.socketB.node is connected_with_node:
                if connector.socketA.node is connected_with_node:
                    if connector.socketB.node.name == node_type:
                        return connector.socketB.node
                else:
                    if connector.socketA.node.name == node_type:
                        return connector.socketA.node
        return None
