import os
import json
import shutil
import re
import glob
from VIStk.Structures._VINFO import *
from tkinter import *
from pathlib import Path
import sys
import os
from notifypy import Notify

class Screen(VINFO):
    """A VIS screen object
    """
    def __init__(self,name:str,script:str,release:bool=False,icon:str=None,exists:bool=True,desc:str=None,tabbed:bool=False):
        super().__init__()
        self.name:str=name
        """The Name of the `Screen`"""
        self.script:str=script
        """The Name of the python script the screen executes"""
        self.release:bool=release
        """`True` if `Screen` Should be Released as Its Own Binary"""
        self.icon:str=icon
        """The Name of the Icon for the Screen"""
        self.path = self.p_screens+"/"+self.name
        """Path to the Screen `Screens/` Folder"""
        self.m_path = self.p_modules+"/"+self.name
        """Path to the Screen `modules/` Folder"""

        if not exists:
            with open(self.p_sinfo,"r") as f:
                info = json.load(f)

            info[self.title]["Screens"][self.name] = {"script":script,"release":release}
            if not icon == None:
                info[self.title]["Screens"][self.name]["icon"] = icon

            if not desc == None:
                info[self.title]["Screens"][self.name]["desc"] = desc
            else:
                info[self.title]["Screens"][self.name]["desc"] = "A VIS Created Executable"

            info[self.title]["Screens"][self.name]["version"] = str(Version("1.0.0"))#always making first major version of screen

            info[self.title]["Screens"][self.name]["current"] = None#always making first major version of screen

            info[self.title]["Screens"][self.name]["tabbed"] = tabbed

            with open(self.p_sinfo,"w") as f:
                json.dump(info,f,indent=4)

            shutil.copyfile(self.p_templates+"/screen.txt",self.p_project+"/"+script)
            os.mkdir(self.p_screens+"/"+self.name)
            os.mkdir(self.p_modules+"/"+self.name)

            with open(self.p_project+"/"+script, "r") as f:
                template = f.read()

            template = template.replace("<title>",self.name)
            if self.icon is None:
                template = template.replace("<icon>",info[self.title]["defaults"]["icon"])
            else:
                template = template.replace("<icon>",self.icon)

            with open(self.p_project+"/"+script, "w") as f:
                f.write(template)

        with open(self.p_sinfo,"r") as f:
            info = json.load(f)

        self.desc = info[self.title]["Screens"][self.name]["desc"]
        """Screen Description"""
        self.s_version = Version(info[self.title]["Screens"][self.name]["version"])
        """Screen `Version`"""
        self.current = info[self.title]["Screens"][self.name]["current"]#remove later
        self.tabbed: bool = info[self.title]["Screens"][self.name].get("tabbed", False)
        """Whether this screen opens as a tab inside the Host window"""
      
    def addElement(self,element:str) -> int:
        if validName(element):
            if not os.path.exists(self.path+"/f_"+element+".py"):
                shutil.copyfile(self.p_templates+"/f_element.txt",self.path+"/f_"+element+".py")
                print(f"Created element f_{element}.py in {self.path}")
                self.patch(element)
            if not os.path.exists(self.m_path+"/m_"+element+".py"):
                with open(self.m_path+"/m_"+element+".py", "w"): pass
                print(f"Created module m_{element}.py in {self.m_path}")
            return 1
        else:
            return 0
    
    def patch(self,element:str) -> int:
        """Patches up the template after its copied
        """
        if os.path.exists(self.path+"/f_"+element+".py"):
            with open(self.path+"/f_"+element+".py","r") as f:
                text = f.read()
            text = text.replace("<frame>","f_"+element)
            with open(self.path+"/f_"+element+".py","w") as f:
                f.write(text)
            print(f"patched f_{element}.py")
            return 1
        else:
            print(f"Could not patch, element does not exist.")
            return 0
    
    @staticmethod
    def _replace_section(text: str, section_name: str, content: str) -> str:
        """Replace the body of a template section.

        Finds ``#%<section_name>`` (possibly indented) and replaces everything
        up to the next ``#%`` header with ``content``.  Content lines are
        re-indented to match the section header's indentation, so this works
        correctly for sections inside functions as well as at module level.
        """
        pattern = rf"([ \t]*#%{re.escape(section_name)}\n).*?(?=\n?[ \t]*#%)"

        def replacer(m: re.Match) -> str:
            header = m.group(1)
            indent = re.match(r"[ \t]*", header).group()
            if content and indent:
                lines = content.splitlines(keepends=True)
                indented = "".join(
                    indent + line if line.strip() else line for line in lines
                )
                return header + indented
            return header + content

        return re.sub(pattern, replacer, text, flags=re.DOTALL)

    def stitch(self) -> int:
        """Connects screen elements to a screen
        """
        with open(self.p_project+"/"+self.script,"r") as f: text = f.read()
        stitched = []

        #Elements — each f_element defines build(parent); call it inside setup()
        elements = glob.glob(self.path+'/f_*')#get all elements
        element_lines = []
        for elem_path in elements:
            module_path = elem_path.replace("\\", "/")
            module_path = module_path.replace(self.path+"/", "Screens."+self.name+".")[:-3]
            stitched.append(module_path)
            pkg, mod_name = module_path.rsplit(".", 1)
            element_lines.append(f"from {pkg} import {mod_name}")
            element_lines.append(f"{mod_name}.build(parent)")
        elements_str = ("\n".join(element_lines) + "\n") if element_lines else ""
        text = self._replace_section(text, "Screen Elements", elements_str)

        #Modules
        modules = glob.glob(self.m_path+'/m_*')#get all modules
        for i in range(0,len(modules),1):#iterate into module format
            modules[i] = modules[i].replace("\\","/")
            modules[i] = modules[i].replace(self.m_path+"/","modules."+self.name+".")[:-3]
            stitched.append(modules[i])
        modules_str = ("from " + " import *\nfrom ".join(modules) + " import *\n") if modules else ""
        text = self._replace_section(text, "Screen Modules", modules_str)

        #write out
        with open(self.p_project+"/"+self.script,"w") as f:
            f.write(text)
        print("Stitched: ")
        for i in stitched:
            print(f"\t{i} to {self.name}")

    def addMenu(self,menu:str) -> int:
        pass #will be command line menu creation tool

    def load(self, *args):
        """Loads this screen.

        If a Host is running (port file present), sends the screen name via IPC
        so the Host handles routing (tab or subprocess).  Falls back to
        ``os.execl`` when no Host is detected.
        """
        if send_to_host(self.title, self.name):
            return
        os.execl(sys.executable, *(sys.executable, Path(getPath() + "/" + self.script), *args))

    def close(self) -> bool:
        """Ask the Host to close this screen (tab or Toplevel).

        Returns ``True`` if the request was delivered, ``False`` if no Host
        is running (in standalone mode there is nothing to close via IPC).
        """
        return send_to_host(self.title, f"__VIS_CLOSE__:{self.name}")

    def getModules(self, script:str=None) -> list[str]:
        """Gets a list of all modules in the screens folder"""
        if script is None: script = self.script
        path = self.p_project+"/"+script
        with open(path,"r") as file:
            modules=[]
            for line in file:
                splitline = line.split(" ")
                if splitline[0] == "from" or splitline[0] == "import":
                    if splitline[1].split(".")[0] in ["Screens", "modules"]:
                        modulename = splitline[1].replace("\n","")
                        modules.append(modulename)
                        modulepath = modulename.replace(".","/")+".py"
                        for i in self.getModules(modulepath):
                            if not i in modules:
                                modules.append(i)
        return modules
    
    def isolate(self):
        """Disabled releasing of other screens temporarily by settings them to None"""
        with open(self.p_sinfo,"r") as f:
            info = json.load(f)
            
        for i in info[self.title]["Screens"]:
            if i == self.name:
                if info[self.title]["Screens"][i]["release"] is True:
                    pass
                else:
                    print("Screen is not setup to release.")
            else:
                if info[self.title]["Screens"][i]["release"] is True:
                    info[self.title]["Screens"][i]["release"] = None

        with open(self.p_sinfo,"w") as f:
            json.dump(info,f,indent=4)

    def sendNotification(self, message:str):
        """Sends a notification for this application"""
        notification = Notify()
        notification.title=self.name
        notification.application_name=self.title
        notification.message=message
        notification.send()

    def __str__(self)->str:
        return self.name
    