#!/usr/bin/env python3
"""
gerar_dimeros_gui.py

GUI (Tkinter) para gerar dimeros M1_Mi.xyz a partir de uma pasta contendo
monomeros M1.xyz ... MN.xyz, seguindo o mesmo esquema usado em
make_bsse_dimers.py / BSSECorrection_M30.cmp:

    - M1 e sempre o fragmento "real"
    - Mi (i = 2..N) e marcado como fragmento "ghost" (':' apos o simbolo)
    - As coordenadas originais NUNCA sao alteradas
    - Os dimeros sao escritos em uma pasta de SAIDA, separada da pasta
      de entrada, com o nome M1_Mi.xyz (mesma convencao usada dentro de
      BSSECorrection_M30.cmp, ex: molecule = "M1_M2.xyz")

Uso:
    python gerar_dimeros_gui.py

Requer apenas a biblioteca padrao do Python (tkinter incluso).
"""

import threading
from pathlib import Path
import tkinter as tk
from tkinter import filedialog, messagebox, ttk


# ----------------------------------------------------------------------
# Logica principal (independente da GUI, testavel isoladamente)
# ----------------------------------------------------------------------

def read_xyz(path: Path):
    """Le um arquivo .xyz e devolve (n_atomos, lista_de_linhas_de_atomo)."""
    lines = path.read_text(encoding="utf-8").splitlines()
    if len(lines) < 2:
        raise ValueError(f"{path.name}: arquivo XYZ invalido (menos de 2 linhas)")
    try:
        n = int(lines[0].strip())
    except ValueError:
        raise ValueError(f"{path.name}: primeira linha nao e um numero de atomos")
    atoms = [x.strip() for x in lines[2:] if x.strip()]
    if len(atoms) < n:
        raise ValueError(f"{path.name}: esperado {n} atomos, encontrados {len(atoms)}")
    return n, atoms[:n]


def ghost_line(line: str) -> str:
    """Marca a linha do atomo como 'ghost' (adiciona ':' ao simbolo), se ja nao estiver marcada."""
    p = line.split()
    if len(p) < 4:
        raise ValueError(f"Linha de atomo invalida: {line}")
    if p[0].endswith(":"):
        return line
    p[0] += ":"
    return " ".join(p)


def make_dimer_file(m1_path: Path, mi_path: Path, i: int, output_dir: Path) -> Path:
    """Cria M1_Mi.xyz em output_dir: M1 real + Mi ghost. Coordenadas preservadas."""
    _, a1 = read_xyz(m1_path)
    _, ai = read_xyz(mi_path)

    atoms = a1 + [ghost_line(x) for x in ai]

    out_path = output_dir / f"M1_M{i}.xyz"
    with out_path.open("w", encoding="utf-8") as f:
        f.write(f"{len(atoms)}\n")
        f.write(f"M1-M{i} | M1 real + M{i} ghost | coordinates preserved\n")
        f.write("\n".join(atoms) + "\n")
    return out_path


def check_inputs(input_dir: Path, n_monomers: int):
    """Verifica se M1.xyz ... Mn.xyz existem na pasta de entrada.
    Devolve lista de nomes de arquivos faltando (vazia se tudo OK)."""
    missing = []
    for idx in range(1, n_monomers + 1):
        p = input_dir / f"M{idx}.xyz"
        if not p.exists():
            missing.append(p.name)
    return missing


def generate_all(input_dir: Path, output_dir: Path, n_monomers: int, log=print):
    """Gera M1_M2.xyz ... M1_Mn.xyz. Devolve (ok, falhas) como listas de nomes."""
    if n_monomers < 2:
        raise ValueError("O numero de monomeros deve ser >= 2 (e preciso ao menos M1 e M2).")

    m1_path = input_dir / "M1.xyz"
    if not m1_path.exists():
        raise FileNotFoundError(f"M1.xyz nao encontrado em {input_dir}")

    output_dir.mkdir(parents=True, exist_ok=True)

    ok, failed = [], []
    for i in range(2, n_monomers + 1):
        mi_path = input_dir / f"M{i}.xyz"
        if not mi_path.exists():
            log(f"  [FALTANDO] M{i}.xyz nao encontrado -- pulado.")
            failed.append(f"M{i}.xyz")
            continue
        try:
            out = make_dimer_file(m1_path, mi_path, i, output_dir)
            log(f"  [OK] {out.name} criado.")
            ok.append(out.name)
        except Exception as e:
            log(f"  [ERRO] M1_M{i}.xyz: {e}")
            failed.append(f"M{i}.xyz")
    return ok, failed


def count_monomers_in_folder(input_dir: Path) -> int:
    """Detecta automaticamente quantos monomeros M1..Mn existem em sequencia continua."""
    n = 0
    while (input_dir / f"M{n + 1}.xyz").exists():
        n += 1
    return n


# ----------------------------------------------------------------------
# GUI
# ----------------------------------------------------------------------

class DimerGeneratorApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Gerador de Dimeros BSSE (M1-centrado)")
        self.geometry("640x540")
        self.minsize(560, 460)

        self.input_dir = tk.StringVar()
        self.output_dir = tk.StringVar()
        self.n_monomers = tk.StringVar(value="17")

        self._build_widgets()

    def _build_widgets(self):
        pad = {"padx": 10, "pady": 6}

        frame_in = ttk.LabelFrame(self, text="1) Pasta com os monomeros (M1.xyz ... Mn.xyz)")
        frame_in.pack(fill="x", **pad)
        ttk.Entry(frame_in, textvariable=self.input_dir).pack(
            side="left", fill="x", expand=True, padx=(8, 4), pady=8
        )
        ttk.Button(frame_in, text="Selecionar...", command=self.select_input_dir).pack(
            side="left", padx=(0, 8), pady=8
        )

        frame_n = ttk.LabelFrame(self, text="2) Numero de monomeros na pasta")
        frame_n.pack(fill="x", **pad)
        ttk.Entry(frame_n, textvariable=self.n_monomers, width=10).pack(
            side="left", padx=(8, 4), pady=8
        )
        ttk.Button(frame_n, text="Detectar automaticamente", command=self.detect_n).pack(
            side="left", padx=4, pady=8
        )
        ttk.Label(frame_n, text="(serao gerados M1_M2.xyz ... M1_M<n>.xyz)").pack(
            side="left", padx=8
        )

        frame_out = ttk.LabelFrame(self, text="3) Pasta de saida para os dimeros")
        frame_out.pack(fill="x", **pad)
        ttk.Entry(frame_out, textvariable=self.output_dir).pack(
            side="left", fill="x", expand=True, padx=(8, 4), pady=8
        )
        ttk.Button(frame_out, text="Selecionar...", command=self.select_output_dir).pack(
            side="left", padx=(0, 8), pady=8
        )

        self.run_button = ttk.Button(self, text="Gerar dimeros", command=self.run_generation)
        self.run_button.pack(pady=10)

        frame_log = ttk.LabelFrame(self, text="Log")
        frame_log.pack(fill="both", expand=True, **pad)
        self.log_text = tk.Text(frame_log, height=15, wrap="word", state="disabled")
        self.log_text.pack(fill="both", expand=True, padx=8, pady=8)

    def log(self, msg):
        self.log_text.configure(state="normal")
        self.log_text.insert("end", msg + "\n")
        self.log_text.see("end")
        self.log_text.configure(state="disabled")
        self.update_idletasks()

    def select_input_dir(self):
        d = filedialog.askdirectory(title="Selecione a pasta com os monomeros")
        if d:
            self.input_dir.set(d)

    def select_output_dir(self):
        d = filedialog.askdirectory(title="Selecione a pasta de saida para os dimeros")
        if d:
            self.output_dir.set(d)

    def detect_n(self):
        if not self.input_dir.get():
            messagebox.showwarning("Aviso", "Selecione primeiro a pasta de entrada.")
            return
        n = count_monomers_in_folder(Path(self.input_dir.get()))
        if n == 0:
            messagebox.showwarning("Aviso", "Nenhum M1.xyz encontrado na pasta selecionada.")
            return
        self.n_monomers.set(str(n))
        self.log(f"Detectados {n} monomeros continuos (M1..M{n}).")

    def run_generation(self):
        in_dir_str = self.input_dir.get().strip()
        out_dir_str = self.output_dir.get().strip()
        n_str = self.n_monomers.get().strip()

        if not in_dir_str:
            messagebox.showerror("Erro", "Selecione a pasta de entrada.")
            return
        if not out_dir_str:
            messagebox.showerror("Erro", "Selecione a pasta de saida.")
            return
        try:
            n = int(n_str)
        except ValueError:
            messagebox.showerror("Erro", "Numero de monomeros invalido.")
            return

        input_dir = Path(in_dir_str)
        output_dir = Path(out_dir_str)

        if input_dir.resolve() == output_dir.resolve():
            messagebox.showerror("Erro", "A pasta de saida deve ser diferente da pasta de entrada.")
            return

        missing = check_inputs(input_dir, n)
        if missing:
            proceed = messagebox.askyesno(
                "Arquivos faltando",
                "Os seguintes arquivos nao foram encontrados:\n" + ", ".join(missing) +
                "\n\nDeseja continuar mesmo assim (os dimeros correspondentes serao pulados)?"
            )
            if not proceed:
                return

        self.run_button.configure(state="disabled")
        self.log(f"\n--- Gerando dimeros M1_M2.xyz ... M1_M{n}.xyz ---")
        self.log(f"Entrada: {input_dir}")
        self.log(f"Saida:   {output_dir}\n")

        def worker():
            try:
                ok, failed = generate_all(input_dir, output_dir, n, log=self.log)
                self.log(f"\nConcluido: {len(ok)} dimero(s) criado(s), {len(failed)} falha(s)/pulado(s).")
                if ok:
                    messagebox.showinfo("Concluido", f"{len(ok)} dimero(s) gerado(s) em:\n{output_dir}")
                else:
                    messagebox.showwarning("Concluido", "Nenhum dimero foi gerado. Veja o log.")
            except Exception as e:
                self.log(f"\n[ERRO FATAL] {e}")
                messagebox.showerror("Erro", str(e))
            finally:
                self.run_button.configure(state="normal")

        threading.Thread(target=worker, daemon=True).start()


if __name__ == "__main__":
    app = DimerGeneratorApp()
    app.mainloop()
