The model set is inside the py file import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import os
import re
import shutil

def extract_model_info(dfm_path):
    """
    Extract model information from a .dfm file.
    Modify this function based on how resolution or other info can be extracted from the .dfm file.
    For demonstration, we'll assume .dfm files are named as ModelName.dfm
    Example: Jackie_Chan.dfm
    """
    # Extract the model name without extension
    filename = os.path.basename(dfm_path)
    model_name = os.path.splitext(filename)[0]
    
    # TODO: Implement actual extraction logic if possible
    # For now, we'll simulate that resolution extraction fails
    size = None  # Simulate failure to find resolution
    
    return model_name, size

def prompt_for_resolutions(missing_models):
    """
    Prompt the user to input resolutions for models that are missing this information.
    Returns a dictionary mapping model names to their respective sizes.
    """
    def submit_resolutions():
        for model in missing_models:
            try:
                width = int(entries[model]['width'].get())
                height = int(entries[model]['height'].get())
                resolutions[model] = (width, height)
            except ValueError:
                messagebox.showerror("Invalid Input", f"Please enter valid integers for {model}.")
                return
        prompt_window.destroy()
    
    prompt_window = tk.Toplevel()
    prompt_window.title("Input Resolutions")
    
    prompt_window.geometry("400x300")
    prompt_window.grab_set()  # Make this window modal
    
    canvas = tk.Canvas(prompt_window)
    scrollbar = ttk.Scrollbar(prompt_window, orient="vertical", command=canvas.yview)
    scrollable_frame = ttk.Frame(canvas)
    
    scrollable_frame.bind(
        "<Configure>",
        lambda e: canvas.configure(
            scrollregion=canvas.bbox("all")
        )
    )
    
    canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
    canvas.configure(yscrollcommand=scrollbar.set)
    
    entries = {}
    for model in missing_models:
        frame = ttk.Frame(scrollable_frame, padding=10)
        frame.pack(fill=tk.X, expand=True)
        
        label = ttk.Label(frame, text=f"Resolution for '{model}':")
        label.pack(anchor=tk.W)
        
        size_frame = ttk.Frame(frame)
        size_frame.pack(fill=tk.X, pady=5)
        
        width_label = ttk.Label(size_frame, text="Width:")
        width_label.pack(side=tk.LEFT, padx=(0,5))
        width_entry = ttk.Entry(size_frame, width=10)
        width_entry.pack(side=tk.LEFT, padx=(0,15))
        
        height_label = ttk.Label(size_frame, text="Height:")
        height_label.pack(side=tk.LEFT, padx=(0,5))
        height_entry = ttk.Entry(size_frame, width=10)
        height_entry.pack(side=tk.LEFT)
        
        entries[model] = {'width': width_entry, 'height': height_entry}
    
    submit_button = ttk.Button(scrollable_frame, text="Submit", command=submit_resolutions)
    submit_button.pack(pady=10)
    
    prompt_window.mainloop()
    
    return resolutions

def update_model_set(script_path, dfm_folder):
    try:
        with open(script_path, 'r') as file:
            content = file.read()
        
        # Backup the original script
        backup_path = script_path + ".backup"
        shutil.copy(script_path, backup_path)
        
        # Find the existing MODEL_SET dictionary
        model_set_match = re.search(r"MODEL_SET\s*:\s*ModelSet\s*=\s*({.*?})", content, re.DOTALL)
        if not model_set_match:
            messagebox.showerror("Error", "MODEL_SET dictionary not found in the selected script.")
            return
        
        model_set_str = model_set_match.group(1)
        
        # Prepare to insert new models
        new_models = []
        missing_res_models = {}  # Models missing resolution
        
        dfm_files = [f for f in os.listdir(dfm_folder) if f.endswith('.dfm')]
        if not dfm_files:
            messagebox.showwarning("No Models Found", "No .dfm files found in the selected folder.")
            return
        
        for filename in dfm_files:
            dfm_path = os.path.join(dfm_folder, filename)
            model_name, size = extract_model_info(dfm_path)
            
            if size is None:
                missing_res_models[model_name] = dfm_path
        
        resolutions = {}
        if missing_res_models:
            missing_model_names = list(missing_res_models.keys())
            resolutions = prompt_for_resolutions(missing_model_names)
            # Update sizes based on user input
            for model_name, size in resolutions.items():
                if size is None:
                    messagebox.showerror("Error", f"Resolution for {model_name} was not provided.")
                    return
        else:
            resolutions = {}
        
        # Now process all models
        for filename in dfm_files:
            dfm_path = os.path.join(dfm_folder, filename)
            model_name, size = extract_model_info(dfm_path)
            
            if size is None:
                size = resolutions.get(model_name)
                if size is None:
                    messagebox.showerror("Error", f"No resolution provided for {model_name}.")
                    return
            
            # Fixed URLs as per Jackie Chan's model
            hash_url = 'https://huggingface.co/bluefoxcreation/DFM/resolve/main/Jackie_Chan.hash'
            dfm_url = 'https://github.com/iperov/DeepFaceLive/releases/download/JACKIE_CHAN/Jackie_Chan.dfm'
            
            # Construct paths relative to the script's directory
            script_dir = os.path.dirname(script_path)
            relative_hash_path = os.path.relpath(os.path.join(dfm_folder, 'Jackie_Chan.hash'), script_dir)
            relative_dfm_path = os.path.relpath(dfm_path, script_dir)
            
            # Lowercase model name for consistency
            model_key = model_name.lower()
            
            model_entry = f"""
        '{model_key}': {{
            'hashes': {{
                'deep_swapper': {{
                    'url': '{hash_url}',
                    'path': resolve_relative_path('{relative_hash_path}')
                }}
            }},
            'sources': {{
                'deep_swapper': {{
                    'url': '{dfm_url}',
                    'path': resolve_relative_path('{relative_dfm_path}')
                }}
            }},
            'template': 'arcface_128_v2',
            'size': {size}
        }}"""
            new_models.append(model_entry)
        
        # Combine all new models
        new_models_str = ",".join(new_models)
        
        # Insert new models before the closing brace of MODEL_SET
        updated_model_set_str = model_set_str.rstrip('}').rstrip() + "," + new_models_str + "\n}"
        
        # Replace the old MODEL_SET with the updated one
        updated_content = content.replace(model_set_str, updated_model_set_str)
        
        with open(script_path, 'w') as file:
            file.write(updated_content)
        
        messagebox.showinfo("Success", f"MODEL_SET updated successfully!\nBackup created at {backup_path}")
    
    except Exception as e:
        messagebox.showerror("Error", f"An error occurred: {str(e)}")

def browse_script():
    file_path = filedialog.askopenfilename(
        title="Select Python Script",
        filetypes=[("Python Files", "*.py")]
    )
    if file_path:
        script_entry.delete(0, tk.END)
        script_entry.insert(0, file_path)

def browse_folder():
    folder_path = filedialog.askdirectory(
        title="Select DFM Folder"
    )
    if folder_path:
        folder_entry.delete(0, tk.END)
        folder_entry.insert(0, folder_path)

def execute_update():
    script_path = script_entry.get()
    dfm_folder = folder_entry.get()
    
    if not os.path.isfile(script_path):
        messagebox.showerror("Error", "Please select a valid Python script.")
        return
    if not os.path.isdir(dfm_folder):
        messagebox.showerror("Error", "Please select a valid DFM folder.")
        return
    
    update_model_set(script_path, dfm_folder)

# Create the main window
root = tk.Tk()
root.title("FaceFusion DFM Installer")
root.geometry("700x300")
root.resizable(False, False)

# Create and place the widgets
frame = ttk.Frame(root, padding="20 20 20 20")
frame.pack(fill=tk.BOTH, expand=True)

# Python Script Selection
script_label = ttk.Label(frame, text="Select Python Script:")
script_label.grid(column=0, row=0, sticky=tk.W, pady=10)

script_entry = ttk.Entry(frame, width=60)
script_entry.grid(column=1, row=0, pady=10, padx=5, sticky=tk.W)

script_button = ttk.Button(frame, text="Browse", command=browse_script)
script_button.grid(column=2, row=0, padx=5, pady=10)

# DFM Folder Selection
folder_label = ttk.Label(frame, text="Select DFM Folder:")
folder_label.grid(column=0, row=1, sticky=tk.W, pady=10)

folder_entry = ttk.Entry(frame, width=60)
folder_entry.grid(column=1, row=1, pady=10, padx=5, sticky=tk.W)

folder_button = ttk.Button(frame, text="Browse", command=browse_folder)
folder_button.grid(column=2, row=1, padx=5, pady=10)

# Update Button
update_button = ttk.Button(frame, text="Update MODEL_SET", command=execute_update)
update_button.grid(column=1, row=2, pady=30)

# Add padding to all children
for child in frame.winfo_children():
    child.grid_configure(padx=10, pady=5)

# Start the GUI event loop
root.mainloop()