package de.duehl.basics.text.xml;

import java.util.ArrayList;
import java.util.List;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse setzt Tags der übergebenen Art, die sich über mehrere Zeilen erstrecken, zu einer
 * Zeile zusammen.
 *
 * Beispiel:
 *
 *     <Z-Titel>über die Gesellschafterversammlung <nZ/>der
 *         "AS Cleopatra" Schifffahrtsgesellschaft mbH & Co. KG
 *         <nZ/>Hamburg
 *         <nZ/>am 07.04.2022</Z-Titel>
 *
 * @version 1.01     2023-11-21
 * @author Christian Dühl
 */

public class MultiLineTagJoiner {

    /** Die Zeilen des XML-Textes, der hier bearbeitet wird. */
    private final List<String> lines;

    /** Der Tag welcher zusammengesetzt werden soll. */
    private final String tag;

    /** Der öffnende Tag mit Parametern (in der Form "<tag "). */
    private final String openingTag1;

    /** Der öffnende Tag ohne Parameter (in der Form "<tag>"). */
    private final String openingTag2;

    /** Der schließende Tag. */
    private final String closingTag;

    /**
     * Die Liste der weiteren Formen, mit der der Tag in der einzeiligen Form beendet werden kann.
     */
    private final List<String> alternativEnds;

    /** Gibt an, ob in den Zeilen noch weiter ersetzt werden soll. */
    private boolean replacing;

    /**
     * Konstruktor.
     *
     * @param lines
     *            Die Zeilen des XML-Textes, der hier bearbeitet wird.
     * @param tag
     *            Der Tag welcher zusammengesetzt werden soll.
     */
    public MultiLineTagJoiner(List<String> lines, String tag) {
        this.lines = lines;
        this.tag = tag;
        openingTag1 = "<" + tag + " ";
        openingTag2 = "<" + tag + ">";
        closingTag = "</" + tag + ">";
        alternativEnds = new ArrayList<>();
    }

    /**
     * Fügt eine weitere Form hinzu, mit der der Tag in der einzeiligen Form beendet werden kann.
     */
    public void addAlternativEnd(String alternativEnd) {
        alternativEnds.add(alternativEnd);
    }

    /** Führt das Zusammensetzen der mehrzeiligen Tags durch. */
    public void join() {
        replacing = true;

        while (replacing) {
            replaceLoop();
        }
    }

    private void replaceLoop() {
        replacing = false;

        int indexOfLineWithOpeningAndNotClosingTag =
                determineIndexOfLineWithOpeningAndNotClosingTag();
        if (indexOfLineWithOpeningAndNotClosingTag == -1) {
            return;
        }

        int indexOfLineWithClosingTag = determineIndexOfLineWithClosingTag(
                indexOfLineWithOpeningAndNotClosingTag);
        if (indexOfLineWithClosingTag == -1) {
            throw new RuntimeException("Nach einer Zeile mit einem öffnenden " + tag + "-Tag wurde "
                    + "keine mit dem schließenden " + tag + "-Tag gefunden.\n"
                    + "Zeilen:\n"
                    + CollectionsHelper.listListNice(lines));
        }

        joinZTitleLines(indexOfLineWithOpeningAndNotClosingTag, indexOfLineWithClosingTag);
        replacing = true;
    }

    /**
     * Ermittelt den ersten Index eine Zeile mit einem öffnenden Tag, die keinen
     * schließenden Tag enthält.
     */
    private int determineIndexOfLineWithOpeningAndNotClosingTag() {
        for (int index = 0; index < lines.size(); ++index) {
            String line = lines.get(index);
            String strippedLine = line.strip();
            if (
                    (
                            strippedLine.startsWith(openingTag1)
                            ||
                            strippedLine.startsWith(openingTag2)
                    )
                    && !strippedLineEndsWithTag(strippedLine)) {
                return index;
            }
        }

        return -1;
    }

    private boolean strippedLineEndsWithTag(String strippedLine) {
        if (strippedLine.endsWith(closingTag)) {
            return true;
        }

        for (String alternativEnd : alternativEnds) {
            if (strippedLine.endsWith(alternativEnd)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Ermittelt nach dem Index der Zeile mit einem öffnenden Tag, die keinen schließenden
     * Tag enthält den Index der ersten folgenden Zeile mit einem schließenden Tag.
     *
     * @param indexOfLineWithOpeningAndNotClosingTag
     *            Der Index eine Zeile mit einem öffnenden Tag, die keinen schließenden
     *            Tag enthält.
     * @return Index der ersten folgenden Zeile mit einem schließenden Tag.
     */
    private int determineIndexOfLineWithClosingTag(
            int indexOfLineWithOpeningAndNotClosingTag) {
        int endLineIndex = indexOfLineWithOpeningAndNotClosingTag;

        boolean searchingForLineWithClosingTag = true;
        while (searchingForLineWithClosingTag) {
            ++endLineIndex;
            if (endLineIndex >= lines.size()) {
                searchingForLineWithClosingTag = false;
                return -1;
            }
            String perhapsEndLine = lines.get(endLineIndex);
            String strippedPerhapsEndLine = perhapsEndLine.strip();
            if (strippedPerhapsEndLine.endsWith(closingTag)) {
                searchingForLineWithClosingTag = false;
            }
        }

        return endLineIndex;
    }

    private void joinZTitleLines(int indexOfLineWithOpeningTag, int indexOfLineWithClosingTag) {
        List<String> linesToJoin = new ArrayList<>();

        String firstLine = lines.get(indexOfLineWithOpeningTag);
        String strippedFirstLine = firstLine.strip();
        int numberOfFrontSpaces = Text.findIndexOfFirstNonSpace(firstLine);
        String frontSpaces = Text.multipleString(" ", numberOfFrontSpaces);
        linesToJoin.add(frontSpaces + strippedFirstLine);

        for (int index = indexOfLineWithOpeningTag + 1;
                index <= indexOfLineWithClosingTag;
                ++index) {
                String anotherLine = lines.get(index);
                String strippedAnotherLine = anotherLine.strip();
                linesToJoin.add(strippedAnotherLine);
        }

        for (int removeIndex = indexOfLineWithClosingTag;
                removeIndex >= indexOfLineWithOpeningTag;
                --removeIndex) {
            lines.remove(removeIndex);
        }

        String newLine = Text.joinWithBlank(linesToJoin);

        lines.add(indexOfLineWithOpeningTag, newLine);
    }

}
