from datetime import datetime
import tkinter as tk
from tkinter import filedialog, messagebox, BooleanVar
import subprocess
import xml.etree.ElementTree as ET
import xml.dom.minidom
import os

def update_command_preview():
    target_name = target_entry.get().strip()
    patch_count = patch_entry.get().strip()
    extra_neutrals = neutrals_entry.get().strip()
    gray_patches = gray_entry.get().strip()
    use_precond = precond_var.get()
    precond_path = precond_path_var.get().strip()

    cmd = ["targen", "-v", "-d2", "-G"]
    if patch_count:
        cmd.append(f"-f{patch_count}")
    if extra_neutrals:
        cmd.append(f"-e{extra_neutrals}")
    if gray_var.get() and gray_patches:
        cmd.append(f"-g{gray_patches}")
    if use_precond and precond_path:
        cmd.extend(["-c", precond_path, "-N0.75"])
    if target_name:
        cmd.append(target_name)

    preview_text = format_cmd_for_display(cmd)
    command_preview.delete("1.0", tk.END)
    command_preview.insert(tk.END, preview_text)
    
def format_cmd_for_display(cmd_list):
    return " ".join(f'"{arg}"' if ' ' in arg else arg for arg in cmd_list)
    
def toggle_gray_entry():
    state = tk.NORMAL if gray_var.get() else tk.DISABLED
    gray_entry.config(state=state)
    update_command_preview()


def select_precond_file():
    path = filedialog.askopenfilename(title="Select ICC/ICM Profile",
                                      filetypes=[("ICC/ICM files", "*.icc *.icm")])
    if path:
        precond_path_var.set(path)
        precond_checkbox.config(state=tk.NORMAL)
        precond_var.set(True)
        update_command_preview()

def select_working_folder():
    folder = filedialog.askdirectory(title="Select Working Folder")
    if folder:
        working_folder_var.set(folder)
        update_command_preview()

def run_targen(target_path, patch_count, extra_neutrals, gray_patches, use_precond, precond_path, include_gray):
    cmd = ["targen", "-v", "-d2", "-G"]
    cmd.append(f"-f{patch_count}")
    cmd.append(f"-e{extra_neutrals}")
    if include_gray and gray_patches:
        cmd.append(f"-g{gray_patches}")

    if use_precond and precond_path:
        cmd.extend(["-c", precond_path, "-N0.75"])
    cmd.append(target_path)


    output_text.delete("1.0", tk.END)
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    output_text.insert(tk.END, f"Run started at {timestamp}\n")
    output_text.see(tk.END)

    output_text.insert(tk.END, "Running command:\n" + format_cmd_for_display(cmd) + "\n\n")
    output_text.see(tk.END)



    try:
        result = subprocess.run(cmd, capture_output=True, text=True)
        output_text.insert(tk.END, result.stdout)
        if result.stderr:
            output_text.insert(tk.END, "\nErrors:\n" + result.stderr)
        if result.returncode != 0:
            messagebox.showerror("Error", "targen failed. See output window for details.")
            return False
    except Exception as e:
        output_text.insert(tk.END, f"\nException: {e}")
        messagebox.showerror("Error", f"targen execution failed:\n{e}")
        return False

    return True

def parse_ti1(filename):
    rgb_data = []
    in_data = False
    with open(filename, 'r') as f:
        for line in f:
            line = line.strip()
            if line == "BEGIN_DATA":
                in_data = True
                continue
            if line == "END_DATA":
                break
            if in_data:
                parts = line.split()
                if len(parts) >= 4:
                    try:
                        r = float(parts[1])
                        g = float(parts[2])
                        b = float(parts[3])
                        rgb_data.append((r, g, b))
                    except ValueError:
                        continue
    return rgb_data

def scale_rgb(r, g, b):
    return round(r * 2.55), round(g * 2.55), round(b * 2.55)

def build_pxf(rgb_list, output_file):
    ET.register_namespace('cc', "http://colorexchangeformat.com/CxF3-core")
    ns = "http://colorexchangeformat.com/CxF3-core"
    root = ET.Element(f'{{{ns}}}CxF')

    file_info = ET.SubElement(root, f'{{{ns}}}FileInformation')
    ET.SubElement(file_info, f'{{{ns}}}Creator').text = "ArgyllCMS to i1Profiler Converter"
    ET.SubElement(file_info, f'{{{ns}}}Description').text = "Converted CXF3 file"

    resources = ET.SubElement(root, f'{{{ns}}}Resources')
    obj_collection = ET.SubElement(resources, f'{{{ns}}}ObjectCollection')

    for i, (r, g, b) in enumerate(rgb_list, start=1):
        r255, g255, b255 = scale_rgb(r, g, b)
        obj = ET.SubElement(obj_collection, f'{{{ns}}}Object',
                            ObjectType="Target", Name=f"Target{i}", Id=f"c{i}")
        ET.SubElement(obj, f'{{{ns}}}CreationDate').text = "2025-10-12T00:00:00+00:00"
        dev_values = ET.SubElement(obj, f'{{{ns}}}DeviceColorValues')
        color_rgb = ET.SubElement(dev_values, f'{{{ns}}}ColorRGB',
                                  ColorSpecification="Unknown")
        ET.SubElement(color_rgb, f'{{{ns}}}R').text = str(r255)
        ET.SubElement(color_rgb, f'{{{ns}}}G').text = str(g255)
        ET.SubElement(color_rgb, f'{{{ns}}}B').text = str(b255)

    rough_string = ET.tostring(root, encoding='utf-8')
    reparsed = xml.dom.minidom.parseString(rough_string)
    pretty_xml = reparsed.toprettyxml(indent="  ", newl="\n")

    with open(output_file, 'w', newline='\n', encoding='utf-8') as f:
        f.write(pretty_xml)

def generate_and_convert():
    target_name = target_entry.get().strip()
    patch_count = patch_entry.get().strip()
    extra_neutrals = neutrals_entry.get().strip()
    gray_patches = gray_entry.get().strip()
    use_precond = precond_var.get()
    precond_path = precond_path_var.get()
    working_folder = working_folder_var.get()

    if not target_name:
        messagebox.showerror("Error", "Please enter a target name.")
        return
    if not working_folder:
        messagebox.showerror("Error", "Please select a working folder.")
        return

    try:
        patch_count = int(patch_count)
        extra_neutrals = int(extra_neutrals)
        gray_patches = int(gray_patches) if gray_patches else None
    except ValueError:
        messagebox.showerror("Error", "Patch counts must be integers.")
        return


    target_path = os.path.join(working_folder, target_name)
    ti1_file = target_path + ".ti1"
    pxf_file = target_path + ".pxf"

    
    if use_precond and not precond_path:
        messagebox.showwarning("Warning", "Preconditioning is enabled but no ICC/ICM file is selected.\nIt will be ignored.")

    success = run_targen(target_path, patch_count, extra_neutrals, gray_patches, use_precond, precond_path, gray_var.get())
    if not success:
        return

    if not os.path.exists(ti1_file):
        messagebox.showerror("Error", f"TI1 file not found:\n{ti1_file}")
        return

    rgb_values = parse_ti1(ti1_file)
    build_pxf(rgb_values, pxf_file)
    output_text.insert(tk.END, f"\nPXF file saved with {len(rgb_values)} patches:\n{pxf_file}\n")
    output_text.see(tk.END)
    messagebox.showinfo("Success", f"PXF file saved:\n{pxf_file}")

# GUI setup
root = tk.Tk()
root.title("ArgyllCMS Patch Generator + PXF Converter")
root.geometry("460x700")

def bind_updates(widget):
    widget.bind("<KeyRelease>", lambda event: update_command_preview())
    widget.bind("<FocusOut>", lambda event: update_command_preview())

tk.Label(root, text="Target name (no extension):").pack()
target_entry = tk.Entry(root)
target_entry.insert(0, "mytarget")
target_entry.pack()
bind_updates(target_entry)

tk.Label(root, text="Number of patches:").pack()
patch_entry = tk.Entry(root)
patch_entry.insert(0, "400")
patch_entry.pack()
bind_updates(patch_entry)

tk.Label(root, text="Extra white/black patches:").pack()
neutrals_entry = tk.Entry(root)
neutrals_entry.insert(0, "4")
neutrals_entry.pack()
bind_updates(neutrals_entry)

gray_var = BooleanVar(value=True)
gray_checkbox = tk.Checkbutton(root, text="Include grayscale patches", variable=gray_var,
                               command=toggle_gray_entry)
gray_checkbox.pack()
gray_entry = tk.Entry(root)
gray_entry.insert(0, "51")
gray_entry.pack()
bind_updates(gray_entry)


tk.Label(root, text="").pack(pady=4)
               
precond_var = BooleanVar()
precond_checkbox = tk.Checkbutton(root, text="Use preconditioning profile", variable=precond_var,
                                  command=update_command_preview, state=tk.DISABLED)
precond_checkbox.pack()
               

precond_path_var = tk.StringVar()
tk.Button(root, text="Select ICC/ICM file", command=select_precond_file).pack()
tk.Label(root, textvariable=precond_path_var, wraplength=360).pack()

tk.Label(root, text="").pack(pady=4)  # Adds vertical space

working_folder_var = tk.StringVar(value=os.path.dirname(os.path.abspath(__file__)))
tk.Button(root, text="Select Working Folder", command=select_working_folder).pack()
tk.Label(root, textvariable=working_folder_var, wraplength=360).pack()

tk.Button(root, text="Generate TI1 and Convert to PXF", command=generate_and_convert).pack(pady=20)

tk.Label(root, text="Generated targen command:").pack()
command_preview = tk.Text(root, height=3, width=60, wrap=tk.WORD)
command_preview.pack()
command_preview.configure(state="normal")
update_command_preview()

# Scrollable output window
output_frame = tk.Frame(root)
output_frame.pack(fill=tk.BOTH, expand=True)

output_scrollbar = tk.Scrollbar(output_frame)
output_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

output_text = tk.Text(output_frame, height=12, wrap=tk.WORD, yscrollcommand=output_scrollbar.set)
output_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
output_scrollbar.config(command=output_text.yview)

# Launch the GUI
root.mainloop()
