try:
    import json
    from tkinter import *
    from tkinter import filedialog
    from tkinter import ttk, Image, PhotoImage
    from tkinter.messagebox import *
    from pydantic import BaseModel
    from mzML import mzMLread
    import threading
    import os
    import sys
    import time
    import subprocess

except ModuleNotFoundError as e:
    raise e.msg
    
else:

    PATH = os.getcwd().replace(os.path.sep, "/")


    class Styles(BaseModel):
        generalFont = "Calibri"
        label = (generalFont, 12)
        labelTitle = (generalFont, 50)

    class AppData(BaseModel):
        SaveDirectory : str = ""
        UseCustomDirectory : bool = False
        FolderDirectory : str = ""

    appData = AppData()

    def resource_path(relative_path):
        """ Get absolute path to resource, works for dev and for PyInstaller """
        try:
            # PyInstaller creates a temp folder and stores path in _MEIPASS
            base_path = sys._MEIPASS
        except Exception:
            base_path = os.path.abspath(".")

        return os.path.join(base_path, relative_path)

    appStyles = Styles()
    filesToOpen = []
    command = f"""sudo open {os.getcwd().split("/Contents/Resources")[0]}"""

    def AskAdminRights():
        os.system(f"""osascript -e 'tell application "Terminal" to do script "{command}; exit"'""")

    if not os.getenv("SUDO_USER"):
        if askokcancel("The app needs to be ran with administrator rights.",
                    f"""The app needs to be ran with administrator rights. \nBy clicking "OK", you will be asked for running this command (System Preferences prompt can appear, just accept the mzMLtoMat app to access to your Terminal) : \n\n{command}\n\nThis will allow the application to convert your files correctly. Thanks !"""):
                    threading.Thread(target=AskAdminRights).start()
        exit(code=13)

    else:
        def launch(filesToConvert : list[str] = None, output : str = None, basePath : str = None, save : bool = False):
            MainWindow = Tk()
            MainWindow.title("mzMLtoMat")
            MainWindow.geometry("400x500")
            MainWindow.resizable(0,0)

            def LoadData():
                if output :
                    global filesToOpen
                    filesToOpen = filesToConvert
                    global savePath
                    if save :
                        global appData
                        appData = AppData(SaveDirectory=output, UseCustomDirectory=True, FolderDirectory=basePath)
                        CB_UseSaveDirectory.config(state=NORMAL)
                        IsChecked.set(appData.UseCustomDirectory)
                        Txt_CurrentDirectory.config(text=f"Current Save Directory : {output}")
                        with open(f"{PATH}/data/UI/appData.mlMat", "w+") as file:
                            file.write(str(appData.json(exclude={"FolderDirectory"})))
                            file.close()
                    else:
                        savePath = output
                        CB_UseSaveDirectory.config(state=NORMAL)
                        IsChecked.set(appData.UseCustomDirectory)
                        Txt_CurrentDirectory.config(text=f"Current Save Directory : {output}")
                    threading.Thread(target=StartConversion).start()
                    return
                try :
                    with open(f"{PATH}/data/UI/appData.mlMat", 'r+') as file:
                        data = file.readline()
                        if data:
                            appData = AppData().parse_obj(json.loads(data))
                            if appData.SaveDirectory and appData.UseCustomDirectory:
                                CB_UseSaveDirectory.config(state=NORMAL)
                                IsChecked.set(appData.UseCustomDirectory)
                                Txt_CurrentDirectory.config(text=f"Current Save Directory : {appData.SaveDirectory}")
                            elif appData.SaveDirectory:
                                CB_UseSaveDirectory.config(state=NORMAL)
                                BtnOutputFolder.config(state=DISABLED)
                                Txt_CurrentDirectory.config(text="Current Save Directory : data's folder")
                            else:
                                CB_UseSaveDirectory.config(state=DISABLED)
                                BtnOutputFolder.config(state=DISABLED)
                                BtnOutputFolder.config(state=DISABLED)
                                Txt_CurrentDirectory.config(text="Current Save Directory : data's folder")
                            file.close()
                        else:
                            with open(f"{PATH}/data/UI/appData.mlMat", 'w+') as file:
                                file.write(str(appData.json(exclude={"FolderDirectory"})))
                                file.close()
                                CB_UseSaveDirectory.config(state=DISABLED)
                                BtnOutputFolder.config(state=DISABLED)
                            
                except FileNotFoundError:
                    with open(f"{PATH}/data/UI/appData.mlMat", 'x') as file:
                        file.write(str(appData.json(exclude={"FolderDirectory"})))
                        CB_UseSaveDirectory.config(state=DISABLED)
                        BtnOutputFolder.config(state=DISABLED)
                        file.close()

            def AskFiles():
                global filesToOpen
                filesToOpen = filedialog.askopenfilenames(title="Select files to convert...",filetypes=[("mzML-DataFiles", ".xml")])
                if filesToOpen :
                    path = filesToOpen[0].rsplit("/", 1)[0]
                    appData.FolderDirectory = path
                    if not appData.UseCustomDirectory:
                        Txt_CurrentDirectory.config(text=f"Current Save directory : {appData.FolderDirectory}")
                    files = ""
                    for i in filesToOpen:
                        files = files+i.rsplit("/", 1)[1].split(".mzdata")[0]+"\n \n"
                    if askokcancel("Start Conversion ?", f"Theses files has been selected, start conversion ? \n \n{files}"):          
                        save = True
                        threading.Thread(target=StartConversion).start()
                        
                    else:
                        showinfo("Conversion Canceled", "Conversion Canceled")
                else :
                    showerror("No files provided !", "Please select files to convert")
            
            def Gifs():
                frame_index = 0     # Frame index
                while ChooseFileBtn.cget("state") != NORMAL:
                    try:
                        # Read a frame from GIF file
                        part = 'gif -index {}'.format(frame_index)
                        frame = PhotoImage(file=resource_path('Chargement.gif'), format=part)
                    except:
                        global last_frame
                        last_frame = frame_index - 1    # Save index for last frame
                        break               # Will break when GIF index is reached
                    framelist.append(frame)
                    frame_index  = frame_index+1        # Next frame index

            def animate(frame_number):
                if ChooseFileBtn.cget("state") != NORMAL:
                    if frame_number > last_frame:
                        frame_number = 0
                    LabelImage.config(image=framelist[frame_number]) 
                    MainWindow.after(50, animate, frame_number+1)
            
            def StartConversion():
                ChooseFileBtn.config(state=DISABLED)
                BtnClose.config(state=DISABLED)
                CB_UseSaveDirectory.config(state=DISABLED)
                ChooseSaveBtn.config(state=DISABLED)
                console.insert(-1, f"[Conversion] Conversion started for {len(filesToOpen)} item{'s' if len(filesToOpen) > 1 else ''}...")
                MainWindow.focus()
                global PbConversion
                PbConversion = ttk.Progressbar(MainWindow, maximum=1, orient=HORIZONTAL, length=300)
                PbConversion.pack(before=console)
                threading.Thread(target=StartTimer).start()
                FramePercent = Frame(MainWindow)
                FramePercent.pack(before=PbConversion, pady=5)
                TextPercent = Label(FramePercent, font=appStyles.label, text="0%")
                TextPercent.grid(row=0, column=0)
                global LabelImage
                LabelImage = Label(FramePercent)
                Gifs()
                animate(0)
                LabelImage.grid(row=0, column=1)
                for i in filesToOpen :
                    currentFile = i.rsplit("/", 1)[1].split(".mzdata")[0]
                    console.insert(-1, f"[Conversion] Started Converting file {currentFile}...")
                    console.update()
                    if save :
                        mzMLread(i, formated=True, customDirectory=appData.SaveDirectory if appData.SaveDirectory and appData.UseCustomDirectory else "")
                    else:
                        mzMLread(i, formated=True, customDirectory=savePath)        
                    toGo = filesToOpen.index(i)+1
                    toGo = len(filesToOpen)-toGo
                    if toGo != 0:
                        console.insert(-1, f"[Conversion] Finished Converting file {currentFile}, {toGo} files to go.")
                        percent = ((filesToOpen.index(i)+1)/len(filesToOpen))
                        PbConversion.config(value=percent)
                        TextPercent.config(text=f"{round(percent*100)}%")
                        console.update()
                    else:
                        console.insert(-1, "[Conversion] Conversion finished !")
                        PbConversion.pack_forget()
                        FramePercent.pack_forget()
                console.insert(-1, f"[Conversion] Executed in {timer} seconds.")
                ChooseFileBtn.config(state=NORMAL)
                BtnClose.config(state=NORMAL)
                if appData.SaveDirectory:
                    CB_UseSaveDirectory.config(state=NORMAL)
                ChooseSaveBtn.config(state=NORMAL)
                BtnOutputFolder.config(state=NORMAL)
                time.sleep(1)
                showinfo("Conversion finished", f"Successfully converted {len(filesToOpen)} files !")
                
            def StartTimer():
                global timer
                timer = "0:00"
                timeLabel = Label(MainWindow, text=timer)
                timeLabel.pack(after=PbConversion)
                while ChooseFileBtn.cget("state") != NORMAL:
                    for i in range(60):
                        if ChooseFileBtn.cget("state") != NORMAL:
                            time.sleep(1)
                            if i+1 < 10:
                                timer = timer.replace(timer.split(":")[1], "0"+str(i+1))
                            else:
                                timer = timer.replace(timer.split(":")[1], str(i+1))
                            timeLabel.config(text=timer)
                        else:
                            break
                    timer = timer.replace(timer.split(":")[0], str(int(timer.split(":")[0])+1))
                    timer = timer.replace(timer.split(":")[1], "00")
                    timeLabel.config(text=timer)
                timeLabel.pack_forget()


            def GetSaveDirectory():
                newDirectory = filedialog.askdirectory(mustexist=True, title="Choose a save Directory...")
                if not newDirectory:
                    showwarning("Directory Invalid", "Please select a valid directory.")
                else:
                    with open(f"{PATH}/data/UI/appData.mlMat", "w+") as file:
                        appData.SaveDirectory = newDirectory
                        CB_UseSaveDirectory.config(state=NORMAL)
                        file.write(str(appData.json(exclude={"FolderDirectory"})))
                        file.close()
                    showinfo("Save directory successfully changed !", "Save directory successfully changed !")

            def SwitchCustomDirectory():
                if IsChecked.get():
                    appData.UseCustomDirectory = True
                    Txt_CurrentDirectory.config(text=f"Current Save Directory : {appData.SaveDirectory}")
                    BtnOutputFolder.config(state=NORMAL)
                    with open(f"{PATH}/data/UI/appData.mlMat", "w+") as file:
                        file.write(str(appData.json(exclude={"FolderDirectory"})))
                        file.close()
                else:
                    appData.UseCustomDirectory = False
                    if appData.FolderDirectory:
                        Txt_CurrentDirectory.config(text=f"Current Save Directory : {appData.FolderDirectory}")
                        BtnOutputFolder.config(state=NORMAL)
                    else:
                        Txt_CurrentDirectory.config(text="Current Save Directory : data's folder")
                        BtnOutputFolder.config(state=DISABLED)
                    with open(f"{PATH}/data/UI/appData.mlMat", "w+") as file:
                        file.write(str(appData.json(exclude={"FolderDirectory"})))
                        file.close()

            def OpenOutputFolder():
                if save:
                    file_to_show = appData.SaveDirectory if appData.UseCustomDirectory else appData.FolderDirectory
                else:
                    file_to_show = savePath
                print(file_to_show)
                if not file_to_show.endswith("/"):
                    file_to_show = file_to_show+"/"
                    subprocess.call(["open", "-R", file_to_show])
                else:
                    subprocess.call(["open", "-R", file_to_show])

            Label(MainWindow, text="mzMLtoMat", anchor=CENTER, font=appStyles.labelTitle).pack()
            Label(MainWindow, text="Welcome into .mzdata.xml to .mat files app converter !", font=appStyles.label, wraplength=300).pack(pady=10)
            FrameButtons = Frame(MainWindow)
            FrameButtons.pack(pady=10, anchor=CENTER)
            ChooseFileBtn = Button(FrameButtons, text="Choose files to convert", command=AskFiles)
            ChooseFileBtn.grid(row=0, column=0)
            ChooseSaveBtn = Button(FrameButtons, text="Choose Save Directory...", command=GetSaveDirectory)
            ChooseSaveBtn.grid(row=0, column=1)
            IsChecked = BooleanVar()
            CB_UseSaveDirectory = Checkbutton(MainWindow, text="Use Custom Directory", variable=IsChecked, command=SwitchCustomDirectory)
            CB_UseSaveDirectory.pack()
            Txt_CurrentDirectory = Label(MainWindow, text="Current Save Directory : data's folder", wraplength=300)
            Txt_CurrentDirectory.pack()
            console = Listbox(MainWindow)
            console.pack(pady=10, padx=20, fill=X)
            BtnOutputFolder = Button(MainWindow, text="Open Output folder", command=OpenOutputFolder)
            BtnOutputFolder.pack(after=FrameButtons)
            BtnClose = Button(MainWindow, text="CLOSE", anchor=CENTER, command=MainWindow.destroy)
            BtnClose.pack(pady=5)
            framelist = []      # List to hold all the frames
            LoadData()

            MainWindow.mainloop()