package de.duehl.basics.text;

/*
 * Copyright 2022 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.List;

/**
 * Diese Klasse zerlegt Text so, dass er in eine bestimmte Zeilenbreite passt.
 *
 * @version 1.01     2022-03-11
 * @author Christian Dühl
 */

public class TextBreaker {

    /** Gewünschte Zeilenlänge. */
    private final int lineLength;

    /** Anzahl einzufügender Leerzeichen am Anfang der Zeile. */
    private final int numberOfFrontSpaces;

    /**
     * Gibt an, ob die Leerzeichen, an denen umgebrochen wird, am Ende der Zeilen behalten werden
     * (default: nein).
     */
    private boolean keepSpacesAtBreaks;

    /** Gibt an, ob die erste Ausgabezeile behandelt wird. */
    private boolean firstOutputLine;

    /**
     * Konstruktor.
     *
     * @param lineLength
     *            Gewünschte Zeilenlänge.
     */
    public TextBreaker(int lineLength) {
        this(lineLength, 0);
    }

    /**
     * Konstruktor.
     *
     * @param lineLength
     *            Gewünschte Zeilenlänge.
     * @param numberOfFrontSpaces
     *            Anzahl einzufügender Leerzeichen am Anfang der neuen Zeilen.
     */
    public TextBreaker(int lineLength, int numberOfFrontSpaces) {
        if (numberOfFrontSpaces >= lineLength) {
            throw new IllegalArgumentException("Zu wenig Platz für den umzubrechenden Text!");
        }

        this.lineLength = lineLength - numberOfFrontSpaces;
        this.numberOfFrontSpaces = numberOfFrontSpaces;
        keepSpacesAtBreaks = false;
    }

    /**
     * Legt fest, dass die Leerzeichen, an denen umgebrochen wird, am Ende der Zeilen behalten
     * werden (default: nein).
     */
    public void keepSpacesAtBreaks() {
        keepSpacesAtBreaks = true;
    }

    /**
     * Versieht einen langen Text an Leerzeichen mit Zeilenumbrüchen, so dass die Zeilen höchstens
     * lineLength Zeichen lang sind, - falls ausreichend Leerzeichen dafür vorhanden sind.
     *
     * Hierbei wird der Text zuvor an schon vorhandenen Umbrüchen umgebrochen.
     *
     * @param text
     *            Gegebener Text.
     * @return Umgebrochener Text.
     */
    public String addLineBreaks(String text) {
        List<String> brokenParts = new ArrayList<>();

        firstOutputLine = true; // erste Zeile des Outputs
        for (String part : Text.splitByLineBreaks(text)) {
            brokenParts.addAll(breakAtSpaces(part));
            firstOutputLine = false; // sollte überflüssig sein, aber sicherheitshalber
        }

        return generateOutputString(brokenParts);
    }

    private String generateOutputString(List<String> brokenParts) {
        StringBuilder builder = new StringBuilder();

        boolean first = true; // erste Zeile der zu bearbeitenden Zeilen
        for (String brokenPart : brokenParts) {
            if (first) {
                first = false;
            }
            else {
                builder.append(Text.LINE_BREAK);
                builder.append(Text.multipleString(' ', numberOfFrontSpaces));
            }
            builder.append(brokenPart);
        }

        return builder.toString();
    }

    private List<String> breakAtSpaces(String part) {
        List<String> brokenParts = new ArrayList<>();

        if (textIsSmallEnough(part) || textHasNoSpaces(part)) {
            brokenParts.add(part);
            firstOutputLine = false;
        }
        else {
            brokenParts.addAll(breakAtBestSpace(part));
        }

        return brokenParts;
    }

    private List<String> breakAtBestSpace(String part) {
        List<String> brokenParts = new ArrayList<>();

        int devidingIndex;

        int firstSpaceIndex = part.indexOf(" ");

        if (textIsSmallEnoughAtSpaceIndex(firstSpaceIndex)) {
            int lastGoodSpaceIndex = firstSpaceIndex;
            int aSpaceIndex = part.indexOf(" ", lastGoodSpaceIndex + 1);
            while (aSpaceIndex > -1 && textIsSmallEnoughAtSpaceIndex(aSpaceIndex)) {
                lastGoodSpaceIndex = aSpaceIndex;
                aSpaceIndex = part.indexOf(" ", lastGoodSpaceIndex + 1);
            }
            devidingIndex = lastGoodSpaceIndex;
        }
        else {
            devidingIndex = firstSpaceIndex;
        }

        String partBeforeSpace;
        if (keepSpacesAtBreaks) {
            partBeforeSpace = part.substring(0, devidingIndex + 1);
        }
        else {
            partBeforeSpace = part.substring(0, devidingIndex);
        }
        String partAfterSpace = part.substring(devidingIndex + 1);
        brokenParts.add(partBeforeSpace);
        firstOutputLine = false;
        brokenParts.addAll(breakAtSpaces(partAfterSpace));

        return brokenParts;
    }

    private boolean textIsSmallEnough(String part) {
        return part.length() <= determineLineLengthToCheck();
    }

    private boolean textIsSmallEnoughAtSpaceIndex(int spaceIndex) {
        if (keepSpacesAtBreaks) {
            return spaceIndex + 1 <= determineLineLengthToCheck();
        }
        else {
            return spaceIndex <= determineLineLengthToCheck();
        }
    }

    /**
     * Bestimmt die zu prüfende Zeilenlänge, diese Variiert, falls am Anfang von weiteren Zeilen
     * Leerzeichen hinzugefügt werden sollen, da diese nicht zur ersten Zeile hinzugefügt werden.
     */
    private int determineLineLengthToCheck() {
        if (firstOutputLine) {
            return lineLength + numberOfFrontSpaces;
        }
        else {
            return lineLength;
        }
    }

    private boolean textHasNoSpaces(String text) {
        return !text.contains(" ");
    }

}
