package de.duehl.basics.replacements;

import java.util.ArrayList;

/*
 * Copyright 2026 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.util.List;
import java.util.regex.Matcher;

import de.duehl.basics.text.NumberString;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse ermittelt aus einem Matcher, welcher auf einen Satz mit Platzhaltern angewendet
 * wird, die passenden Inhalte.
 *
 * Der Matcher fängt in Platzhaltern der Art <<town:7>> die 1-basierten Positionen, also hier im
 * Beispiel die "7".
 *
 * Mit dieser Klasse kann ich über die 1-basierte Gruppennummer (die Gruppe 0 ist beim Matcher der
 * gesamte gematchte Bereich) und die zugehörige Liste mit den Daten (im Beispiel die Orte) den
 * passenden Eintrag aus der Liste ermitteln.
 *
 * @version 1.01     2026-08-10
 * @author Christian Dühl
 */

public class PlaceholderResolver {

    /** Gibt an, ob während der Entwicklung der Regeln Testausgaben ausgegeben werden sollen. */
    private boolean debug;

    /**
     * Die Liste der abgefragten Gruppennummern.
     *
     * Mit ihrer Hilfe kann ich prüfen, ob welche ausgelassen oder doppelt verwendet werden.
     */
    private final List<Integer> usedGroupNumbers;

    /** Konstruktor. */
    public PlaceholderResolver() {
        debug = false;

        usedGroupNumbers = new ArrayList<>();
    }

    /** Legt fest, ob während der Entwicklung der Regeln Testausgaben ausgegeben werden sollen. */
    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    /**
     * Ermittelt den passenden Inhalt aus der übergebenen Liste.
     *
     * Die in der Methode gebildete "position" ist die gefangene Zahl aus dem regulären Ausdruck,
     * aus welcher die Position des Listenelements (1-basiert) extrahiert wird (aus "<<date:12>>"
     * wird die 12 ermittelt).
     *
     * @param <T>
     *            Der Typ der Listenelemente (meist String, kann aber auch eine eigene Klasse sein,
     *            z.B. bei Amtsgerichten wird das genutzt).
     * @param name
     *            Eine mitgegebene Bezeichnung für die Art der Listenelemente für Debug-Ausgaben
     *            (z.B. "Ort").
     * @param list
     *            Die Liste mit den Elementen, aus denen wir eines auswählen möchten.
     * @param matcher
     *            Der Matcher zu einem regulären Ausdruck mit Inhalten wie "<<date:12>>".
     * @param group
     *            Die Nummer der gematchten Gruppe (beginnend bei 1).
     * @return Der passende Wert in der Liste, wenn diese ausreichend viele Werte enthält. Falls
     *         nicht, wird eine Ausnahme geworfen.
     * @throws RuntimeException
     *             Wenn die Liste zu kurz für den gewünschten Index ist.
     */
    public <T> T resolve(String name, List<T> list, Matcher matcher, int group) {
        addGroupNumber(group);

        String position = matcher.group(group);
        int positionAsInt = NumberString.parseInt(position);
        int index = positionAsInt - 1;
        if (list.size() > index) {
            T element = list.get(index);
            if (debug) {
                Text.say(""
                        + Text.fillWithSpaces(name, 15)
                        + " : matcher.group("
                        + Text.fillWithSpacesAtFront(Integer.toString(group), 2)
                        + ") = position " + positionAsInt
                        + " -> index "
                        + index
                        + " -> \"" + element + "\"");
            }
            return element;
        }
        else {
            String message = "Die übergebene Liste hat nicht genug Elemente.\n"
                    + "\t" + "name : " + name + "\n"
                    + "\t" + "group : " + group + "\n"
                    + "\t" + "position : " + position + "\n"
                    + "\t" + "positionAsInt : " + positionAsInt + "\n"
                    + "\t" + "index : " + index + "\n"
                    + "\t" + "list : " + list + "\n";
            if (debug) {
                Text.say(message);
            }
            throw new RuntimeException(message);
        }
    }

    /**
     * Fügt die übergebene Gruppennummer zur Liste der gesehenen Gruppennummern hinzu und überprüft
     * dabei Lücken und doppelt verwendete Gruppennummern.
     */
    private void addGroupNumber(int group) {
        if (usedGroupNumbers.isEmpty()) {
            if (group == 1) {
                usedGroupNumbers.add(group);
            }
            else {
                throw new RuntimeException("Als erstes wird nicht die Gruppe mit der Nummer 1 "
                        + "abgefragt, sondern " + group + ".");
            }
        }
        else {
            if (usedGroupNumbers.contains(group)) {
                throw new RuntimeException(
                        "Die Gruppennummer " + group + " wird mehrfach abgefragt.");
            }
            else {
                int lastAddedGroup = usedGroupNumbers.get(usedGroupNumbers.size() - 1);
                if (group == lastAddedGroup + 1) {
                    usedGroupNumbers.add(group);
                }
                else {
                    throw new RuntimeException("Die Gruppennummer " + (lastAddedGroup + 1)
                            + " wurde vergessen abzufragen.");
                }
            }
        }
    }

    /** Teilt mit, dass man die Gruppe mit der übergebenen Gruppennummer anderweitig auswertet. */
    public void usingGroupNumber(int groupNumber) {
        addGroupNumber(groupNumber);
        if (debug) {
            Text.say(""
                    + "Anderweitig ausgewertete Gruppe: matcher.group(" + groupNumber + ")");
        }
    }

    /**
     * Ermittelt den passenden Inhalt aus der übergebenen Liste im Fall eines optionalen regulären
     * Ausdrucks mit mehreren Zweigen, die je ein gleichartiges Element wie z.B. ein Datum fangen
     * können. Von diesen ist dann nur maximal eines mit einer Zahl gefüllt, die anderen Inhalte
     * haben den Wert null.
     *
     * Die in der Methode gebildete "position" ist die gefangene Zahl aus dem regulären Ausdruck,
     * aus welcher die Position des Listenelements (1-basiert) extrahiert wird (aus "<<date:12>>"
     * wird die 12 ermittelt).
     *
     * @param <T>
     *            Der Typ der Listenelemente (meist String, kann aber auch eine eigene Klasse sein,
     *            z.B. bei Amtsgerichten wird das genutzt).
     * @param name
     *            Eine mitgegebene Bezeichnung für die Art der Listenelemente für Debug-Ausgaben
     *            (z.B. "Ort").
     * @param list
     *            Die Liste mit den Elementen, aus denen wir eines auswählen möchten.
     * @param matcher
     *            Der Matcher zu einem regulären Ausdruck mit Inhalten wie "<<date:12>>".
     * @param startGroup
     *            Die Nummer der der ersten gematchten Gruppe (beginnend bei 1).
     * @param numberOfGroups
     *            Die Anzahl der Gruppen, die zu diesem Ausdruck mit mehreren Zweigen gehören.
     * @param wrongValue
     *            Der Fehlerwert, der zurückgegeben wird, wenn in keinem der Zweige etwas gefangen
     *            wurde.
     * @return Der passende Wert in der Liste, wenn diese ausreichend viele Werte enthält. Falls
     *         nicht, wird eine Ausnahme geworfen.
     * @throws RuntimeException
     *             Wenn die Liste zu kurz für den gewünschten Index ist.
     */
    public <T> T resolveFromMultipleOptionalGroups(String name, List<T> list, Matcher matcher,
            int startGroup, int numberOfGroups, T wrongValue) {
        int filledGroupNumber = -1;
        List<Integer> groupNumbersToAddLater = new ArrayList<>();

        for (int index = 0; index < numberOfGroups; ++index) {
            int groupNumber = startGroup + index;
            String matched = matcher.group(groupNumber);
            if (null == matched) {
                if (filledGroupNumber == -1) { // vorher
                    addGroupNumber(groupNumber); // sonst "fehlen" die nachher ...
                    printSkippedGroupsOfMultiple(name, groupNumber, "davor");
                }
                else { // nachher
                    groupNumbersToAddLater.add(groupNumber);
                }
            }
            else {
                filledGroupNumber = groupNumber;
                // kein break; weil sonst Nummern fehlen!
            }
        }

        if (filledGroupNumber == -1) {
            return wrongValue;
        }
        else {
            T value = resolve(name, list, matcher, filledGroupNumber);
            for (int groupNumber : groupNumbersToAddLater) {
                addGroupNumber(groupNumber); // sonst "fehlen" die nachher ...
                printSkippedGroupsOfMultiple(name, groupNumber, "danach");
            }
            return value;
        }
    }

    private void printSkippedGroupsOfMultiple(String name, int group, String davorOderDanach) {
        if (debug) {
            Text.say(""
                    + Text.fillWithSpaces(name, 15)
                    + " : matcher.group("
                    + Text.fillWithSpacesAtFront(Integer.toString(group), 2)
                    + ") übersprungen da nicht gegriffener Match in Mehrfachauswahl ("
                    + davorOderDanach + ")");
        }
    }

}
