package de.duehl.basics.replacements;

/*
 * Copyright 2024 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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.replacements.data.FoundPlaceholder;

/**
 * Diese Klasse sortiert in einem Text, in dem bestimmte Teile durch Platzhalter der Form
 * <<begriff:1>> ersetzt wurden die Reihenfolge dieser Platzhalter.
 *
 * Entsprechend wird auch die Liste der zugehörigen Fundstücke umsortiert.
 *
 * Ebenfalls wird der Text angepasst hinsichtlich der Positionsnummern in den Platzhaltern.
 *
 * @version 1.01     2024-02-08
 * @author Christian Dühl
 */

public class ReplacementSorter<T> {

    /**
     * Der Text, in dem bestimmte Teile durch Platzhalter der Form <<begriff:1>> ersetzt wurden.
     *
     * Dieser wird hier modifiziert, wenn sich die Reihenfolge ändert.
     */
    private String text;

    /**
     * Der Tag dessen Platzhalter sortiert werden, etwa "begriff" zu den Platzhaltern der Form
     * <<begriff:1>>.
     */
    private final String tag;

    /**
     * Die Liste der gefundenen Informationen (meist Strings, manchmal aber auch daraus gewonnene
     * Informationen wie solche zu einem Amtsgericht).
     *
     * Diese wird hier ggf. umsortiert.
     */
    private final List<T> foundDataList;

    /** Die Liste der Platzhalter im Text zum Tag. */
    private List<FoundPlaceholder> foundPlaceholdersWithTag;

    /**
     * Standard-Konstruktor.
     *
     * @param text
     *            Der Text in dem bestimmte Teile durch Platzhalter der Form <<begriff:1>> ersetzt
     *            wurden.
     * @param tag
     *            Der Tag dessen Platzhalter sortiert werden, etwa "begriff" zu den Platzhaltern
     *            der Form <<begriff:1>>.
     * @param foundDataList
     *            Die Liste der gefundenen Informationen (meist Strings, manchmal aber auch daraus
     *            gewonnene Informationen wie solche zu einem Amtsgericht).
     */
    public ReplacementSorter(String text, String tag, List<T> foundDataList) {
        this.text = text;
        this.tag = tag;
        this.foundDataList = foundDataList;
    }

    /** Führt die Sortierung durch. */
    public void sort() {
        findReplacementsOfTag();
        if (sizesAreEqual() && !foundDataList.isEmpty()) {
            reallySort();
        }
    }

    private void findReplacementsOfTag() {
        ReplacementsAnalyser analyser = new ReplacementsAnalyser(text);
        analyser.analyse();
        Map<String, List<FoundPlaceholder>> map = analyser.getPlaceHoldersByName();
        if (map.containsKey(tag)) {
            foundPlaceholdersWithTag = map.get(tag);
        }
        else {
            foundPlaceholdersWithTag = new ArrayList<>();
        }
    }

    private boolean sizesAreEqual() {
        return foundDataList.size() == foundPlaceholdersWithTag.size();
    }

    private void reallySort() {
        List<Integer> positions = createPositions();
        List<Integer> wantedPositions = createWantedPositions(positions);
        checkPositionsAndWantedPositions(positions, wantedPositions);
        sortFoundDataList(positions, wantedPositions);
        changeText(positions, wantedPositions);
    }

    private List<Integer> createPositions() {
        List<Integer> positions = new ArrayList<>();

        for (FoundPlaceholder placeholder : foundPlaceholdersWithTag) {
            int position = placeholder.getPosition();
            positions.add(position);
        }

        return positions;
    }

    private List<Integer> createWantedPositions(List<Integer> positions) {
        List<Integer> wantedPositions = new ArrayList<>();
        wantedPositions.addAll(positions);
        Collections.sort(wantedPositions);
        return wantedPositions;
    }

    /**
     * Prüft vor dem Aufruf von sortFoundDataList() die Positionen.
     *
     * @param positions
     *         Die Positionsnummern der Platzhalter in der Reihenfolge des Vorkommens im Text
     *         (im Beispiel: 3, 1, 2).
     * @param wantedPositions
     *         Die gewünschten Positionsnummern der Platzhalter (im Beispiel: 1, 2, 3).
     */
    private void checkPositionsAndWantedPositions(List<Integer> positions,
            List<Integer> wantedPositions) {
        for (int wantedPosition : wantedPositions) {
            if (wantedPosition < 1 || wantedPosition > positions.size()) {
                throw new RuntimeException("Die gewünschte Position ist unzulässig.\n"
                        + "\t" + "gewünschte Position: " + wantedPosition + "\n"
                        + "\t" + "kleinste zulässige Position: " + 1 + "\n"
                        + "\t" + "größte zulässige Position: " + positions.size() + "\n"
                        + "\t" + "text: " + text + "\n"
                        + "\t" + "tag: " + tag + "\n"
                        + "Liste mit den Positionen:\n"
                        + CollectionsHelper.listListNice(positions)
                        + "Liste mit den gewünschten Positionen:\n"
                        + CollectionsHelper.listListNice(wantedPositions)
                        + "Liste mit den gefundenen Informationen:\n"
                        + CollectionsHelper.listListNice(foundDataList)
                        + "Liste mit den Platzhaltern im Text zum Tag:\n"
                        + CollectionsHelper.listListNice(foundPlaceholdersWithTag)
                        );
            }
        }
    }

    /**
     * Sortiert die Liste.
     *
     * IST:
     * ~~~~
     *
     * Gedachter ursprünglicher Text:
     *     Die Birne liegt auf dem Tisch.
     *     Der Apfel hängt am Apfelbaum.
     *
     * Satz wie er eingegeben wird:
     *     Die <<obst:3>> liegt auf dem Tisch.
     *     Der <<obst:1>> hängt am <<obst:2>>baum.
     *
     * Platzhalter:
     *     <<obst:3>>, <<obst:1>>, <<obst:2>>
     *     Birne,      Apfel,      Apfel
     *
     * Daraus ermittelte Positionen:
     *     3, 1, 2
     *
     * Liste: Apfel, Apfel, Birne
     *
     * ZIEL:
     * ~~~~
     *
     * Satz wie er sein soll:
     *     Die <<obst:1>> liegt auf dem Tisch.
     *     Der <<obst:2>> hängt am <<obst:3>>baum.
     *
     * Platzhalter:
     *     <<obst:1>>, <<obst:2>>, <<obst:3>>
     *     Birne,      Apfel,      Apfel
     *
     * Zugehörige gewünschte Positionen:
     *     1, 2, 3
     *
     * Liste: Birne, Apfel, Apfel
     *
     * WEG:
     * ~~~~
     * Wir stellen erstmal die Reihenfolge 1, 2, 0 her, also die Indices, die die aktuellen
     * Listenelemente aus foundDataList dann haben sollen:
     *
     *     Zu jeder gewünschten Position
     *         wantedPosition: 1, 2, 3
     *     ermitteln wir die Position
     *         position      : 3, 1, 2
     *     und daraus die Indices
     *         index         : 2, 0, 1
     *
     * Diese Indices beziehen sich dann auf foundDataList.
     *
     * @param positions
     *         Die Positionsnummern der Platzhalter in der Reihenfolge des Vorkommens im Text
     *         (im Beispiel: 3, 1, 2).
     * @param wantedPositions
     *         Die gewünschten Positionsnummern der Platzhalter (im Beispiel: 1, 2, 3).
     */
    private void sortFoundDataList(List<Integer> positions, List<Integer> wantedPositions) {
        List<T> sortedFoundDataList = new ArrayList<>();

        for (int wantedPosition : wantedPositions) {
            int position = positions.get(wantedPosition - 1);
            int index = position - 1;
            T data = foundDataList.get(index);
            sortedFoundDataList.add(data);
        }

        foundDataList.clear();
        foundDataList.addAll(sortedFoundDataList);
    }

    private void changeText(List<Integer> positions, List<Integer> wantedPositions) {
        int replacementNumberAdditional = wantedPositions.get(wantedPositions.size() - 1) + 10;

        List<Integer> replacementPositions = new ArrayList<>();
        for (int position : positions) {
            int replacementPosition = position + replacementNumberAdditional;
            replacementPositions.add(replacementPosition);
        }

        replacePlaceholdersInText(positions, replacementPositions);
        replacePlaceholdersInText(replacementPositions, wantedPositions);
    }

    private void replacePlaceholdersInText(List<Integer> sourcePositions,
            List<Integer> targetPositions) {
        for (int index = 0; index < sourcePositions.size(); ++index) {
            int sourcePosition = sourcePositions.get(index);
            int targetPosition = targetPositions.get(index);
            String sourcePlaceholder = "<<" + tag + ":" + sourcePosition + ">>";
            String targetPlaceholder = "<<" + tag + ":" + targetPosition + ">>";
            text = text.replace(sourcePlaceholder, targetPlaceholder);
        }
    }

    /**
     * Getter für den Text, in dem bestimmte Teile durch Platzhalter der Form <<begriff:1>> ersetzt
     * wurden.
     *
     * Dieser wurde hier ggf. modifiziert, wenn sich die Reihenfolge der Platzhalter geändert hat.
     */
    public String getText() {
        return text;
    }

    /**
     * Erstellt einen Sortierer für Fälle in denen man die gefundenen Textstellen nicht aufhebt.
     *
     * @param text
     *            Der Text in dem bestimmte Teile durch Platzhalter der Form <<begriff:1>> ersetzt
     *            wurden.
     * @param tag
     *            Der Tag dessen Platzhalter sortiert werden, etwa "begriff" zu den Platzhaltern
     *            der Form <<begriff:1>>.
     */
    public static ReplacementSorter<String> createSorterWithoutFoundDataList(String text,
            String tag) {
        return new ReplacementSorter<>(text, tag, createDummyfoundDataList(text, tag));
    }

    private static List<String> createDummyfoundDataList(String text, String tag) {
        ReplacementsAnalyser analyser = new ReplacementsAnalyser(text);
        analyser.analyse();
        Map<String, List<FoundPlaceholder>> map = analyser.getPlaceHoldersByName();
        int size = 0;
        if (map.containsKey(tag)) {
            List<FoundPlaceholder> list = map.get(tag);
            size = list.size();
        }

        List<String> dummyfoundDataList = new ArrayList<>();
        for (int index = 0; index < size; ++index) {
            dummyfoundDataList.add("dummy");
        }

        return dummyfoundDataList;
    }

}
