from pathlib import Path
import re

from openpyxl import load_workbook


# ============================================================
# Dateien und Verzeichnisse
# ============================================================

SCRIPT_DIR = Path(__file__).resolve().parent

NEU_EINTRAGEN = SCRIPT_DIR / "NEU_EINTRAGEN.txt"

NEU_EINTRAGEN.touch(exist_ok=True)


# ============================================================
# Antwort-Schaltung
# ============================================================

def frage_ja_nein(frage, default=True):

    zusatz = "[J/n]" if default else "[j/N]"

    while True:

        antwort = input(f"{frage} {zusatz} ").strip().lower()

        if antwort == "":
            return default

        if antwort in ("j", "ja", "y", "yes"):
            return True

        if antwort in ("n", "nein", "no"):
            return False

        print("Bitte j oder n eingeben.")


# ============================================================
# Konfiguration
# ============================================================

print()
print("========== Konfiguration ==========")

XLSX_FILE = Path(input("Pfad zur XLSX-Datei: ").strip())

if not XLSX_FILE.is_absolute():
    XLSX_FILE = SCRIPT_DIR / XLSX_FILE


TEX_DIR = Path(input("Pfad zum TEX-Verzeichnis: ").strip())

if not TEX_DIR.is_absolute():
    TEX_DIR = SCRIPT_DIR / TEX_DIR


if not XLSX_FILE.exists():
    raise FileNotFoundError(XLSX_FILE)

if not TEX_DIR.exists():
    raise FileNotFoundError(TEX_DIR)

print("Excel-Datei gefunden.")
print("TEX-Verzeichnis gefunden.")


# ============================================================
# Zuordnungstabelle laden
# ============================================================

name_map = {}


def latex_to_unicode(text):

    ersetzungen = {
        r"\"a": "ä",
        r"\"o": "ö",
        r"\"u": "ü",
        r"\"A": "Ä",
        r"\"O": "Ö",
        r"\"U": "Ü",
        r"\"e": "ë",
        r"\"i": "ï",

        r"\'a": "á",
        r"\'e": "é",
        r"\'i": "í",
        r"\'o": "ó",
        r"\'u": "ú",

        r"\`a": "à",
        r"\`e": "è",
        r"\`i": "ì",
        r"\`o": "ò",
        r"\`u": "ù",

        r"\ss": "ß",
    }

    for latex, uni in ersetzungen.items():
        text = text.replace(latex, uni)

    return text


def get_neuer_trigger(col2, col3):

    col2 = (col2 or "").strip()
    col3 = (col3 or "").strip()

    if col2:
        return col2

    if col3:
        return col3

    raise ValueError(
        "Weder Spalte 2 noch Spalte 3 gefüllt"
    )


# ============================================================
# Excel-Datei einlesen
# ============================================================

wb = load_workbook(
    XLSX_FILE,
    data_only=True
)

ws = wb.active


for rownum, row in enumerate(
    ws.iter_rows(values_only=True),
    start=1
):

    col1 = str(row[0] or "").strip()
    col2 = str(row[1] or "").strip()
    col3 = str(row[2] or "").strip()

    if not col1:
        continue

    if bool(col2) == bool(col3):

        raise ValueError(
            f"Zeile {rownum}: "
            f"Genau eine von Spalte 2 oder 3 "
            f"muss gefüllt sein."
        )

    neuer_trigger = get_neuer_trigger(
        col2,
        col3
    )

    # --------------------------------------------------------
    # Fall:
    # AlterTrigger|Nachname, Vorname
    # --------------------------------------------------------

    if "|" in col1:

        alter_trigger, person = col1.split(
            "|",
            1
        )

        alter_trigger = alter_trigger.strip()

        nachname = person.split(
            ",",
            1
        )[0].strip()

        name_map[alter_trigger] = neuer_trigger
        


    # --------------------------------------------------------
    # Fall:
    # AlterTrigger@Name
    # --------------------------------------------------------

    elif "@" in col1:

        alter_trigger, zielname = col1.split(
            "@",
            1
        )

        alter_trigger = alter_trigger.strip()
        zielname = zielname.strip()

        name_map[alter_trigger] = neuer_trigger

        indexname = zielname.split(
            ",",
            1
        )[0].strip()

        name_map[indexname] = neuer_trigger

        name_map[
            latex_to_unicode(indexname)
        ] = neuer_trigger


    # --------------------------------------------------------
    # Fall:
    # Nachname, Vorname
    # --------------------------------------------------------

    else:

        nachname = col1.split(
            ",",
            1
        )[0].strip()

        if not col2:

            if nachname != neuer_trigger:

                raise ValueError(
                    f"Zeile {rownum}: "
                    f"Nachname '{nachname}' "
                    f"stimmt nicht mit Spalte 3 "
                    f"'{neuer_trigger}' überein."
                )

        name_map[nachname] = neuer_trigger

        name_map[
            latex_to_unicode(nachname)
        ] = neuer_trigger


# ============================================================
# Zusätzliche Unicode-Schreibweisen
# ============================================================

for trigger, neuer_trigger in list(
    name_map.items()
):

    unicode_trigger = latex_to_unicode(
        trigger
    )

    if unicode_trigger not in name_map:

        name_map[
            unicode_trigger
        ] = neuer_trigger


print(
    f"{len(name_map)} Name-Trigger geladen"
)


# ============================================================
# Regex für \name
#
# Unterstützt:
#
#   \name{Name}
#   \name[...]{Name}
#
# Optional kann anschließend noch
# \person{...} stehen.
# ============================================================

NAME_RE = re.compile(
    r'\\name(?P<optional>\[[^\]]*\])?\{(?P<name>[^{}]+)\}'
    r'(?:\\person\{[^{}]+\})?'
)


# ============================================================
# Statistik
# ============================================================

stats = {
    "dateien_verarbeitet": 0,
    "dateien_uebersprungen": 0,
    "namen_gefunden": 0,
    "namen_zu_namep": 0,
    "namen_mit_person": 0,
    "namen_unbekannt": 0,
}


# ============================================================
# Fehlende Einträge
# ============================================================

missing_names = {}

new_entries = set()

try:

    with open(
        NEU_EINTRAGEN,
        encoding="utf-8"
    ) as f:

        existing_entries = {
            line.strip()
            for line in f
            if line.strip()
            and not line.startswith("#")
        }

except FileNotFoundError:

    existing_entries = set()


current_file = ""


# ============================================================
# \name bearbeiten
# ============================================================

def replace_name(match):

    stats["namen_gefunden"] += 1

    # --------------------------------------------------------
    # Bestandteile des gefundenen Befehls
    # --------------------------------------------------------

    name = match.group("name").strip()

    optional = match.group("optional") or ""


    # --------------------------------------------------------
    # Name nicht in der Excel-Zuordnung gefunden
    # --------------------------------------------------------

    if name not in name_map:

        stats["namen_unbekannt"] += 1

        missing_names.setdefault(
            name,
            set()
        ).add(
            current_file
        )

        if name not in existing_entries:

            new_entries.add(name)
        

        return match.group(0)


    # --------------------------------------------------------
    # Neuer Trigger aus Excel
    # --------------------------------------------------------

    neuer_trigger = name_map[name]


    # --------------------------------------------------------
    # Name bleibt gleich:
    #
    # \name{Awodey}
    # ->
    # \namep{Awodey}
    #
    # \name[Kollege]{Awodey}
    # ->
    # \namep[Kollege]{Awodey}
    # --------------------------------------------------------

    if neuer_trigger == name:

        stats["namen_zu_namep"] += 1

        return (
            f'\\namep'
            f'{optional}'
            f'{{{name}}}'
        )


    # --------------------------------------------------------
    # Neuer Trigger:
    #
    # \name{WeberEH}
    # ->
    # \name{WeberEH}\person{Erhard Wilhelm Weber}
    #
    # \name[Kollege]{WeberEH}
    # ->
    # \name[Kollege]{WeberEH}\person{Erhard Wilhelm Weber}
    # --------------------------------------------------------

    stats["namen_mit_person"] += 1

    return (
        f'\\name'
        f'{optional}'
        f'{{{name}}}'
        f'\\person{{{neuer_trigger}}}'
    )


# ============================================================
# TEX-Dateien bearbeiten
# ============================================================

for texfile in Path(TEX_DIR).rglob("*.tex"):

    print(
        "Bearbeitet:",
        texfile
    )

    try:

        text = texfile.read_text(
            encoding="utf-8"
        )

    except UnicodeDecodeError:

        stats["dateien_uebersprungen"] += 1

        print(
            "Nicht UTF-8:",
            texfile
        )

        continue


    current_file = str(
        texfile
    )

    stats["dateien_verarbeitet"] += 1



    text = NAME_RE.sub(
        replace_name,
        text
    )


    # --------------------------------------------------------
    # Datei direkt überschreiben
    # --------------------------------------------------------

    texfile.write_text(
        text,
        encoding="utf-8"
    )


# ============================================================
# Zusammenfassung
# ============================================================

print()
print("Fertig.")

print()
print("========================")
print("FEHLENDE EINTRÄGE")
print("========================")


if missing_names:

    for name in sorted(
        missing_names
    ):

        print()
        print(name)

        for fn in sorted(
            missing_names[name]
        ):

            print(
                f"    {fn}"
            )


# ============================================================
# Neue Einträge in NEU_EINTRAGEN.txt speichern
# ============================================================

if new_entries:

    all_entries = (
        existing_entries
        | new_entries
    )

    with open(
        NEU_EINTRAGEN,
        "w",
        encoding="utf-8"
    ) as f:

        for entry in sorted(
            all_entries
        ):

            f.write(
                entry + "\n"
            )



# ============================================================
# Statistik
# ============================================================

print()
print("============================================================")
print("STATISTIK")
print("============================================================")

print(
    f"Verarbeitete Dateien:               "
    f"{stats['dateien_verarbeitet']}"
)

print(
    f"Übersprungene Dateien:              "
    f"{stats['dateien_uebersprungen']}"
)

print(
    f"Gefundene \\name-Befehle:            "
    f"{stats['namen_gefunden']}"
)

print(
    f"Davon zu \\namep geändert:           "
    f"{stats['namen_zu_namep']}"
)

print(
    f"Davon mit \\person ergänzt:          "
    f"{stats['namen_mit_person']}"
)

print(
    f"Davon unbekannte Namen:             "
    f"{stats['namen_unbekannt']}"
)

print(
    f"Neue Einträge in NEU_EINTRAGEN.txt: "
    f"{max(0, len(new_entries) - 1)}"
)