package de.duehl.basics.text;

/*
 * Copyright 2025 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.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.duehl.basics.debug.Assure;

/**
 * Diese Klasse enthält kleine, statische Hilfsmethoden für String, die Zahlen enthalten.
 *
 * @version 1.01     2025-08-21
 * @author Christian Dühl
 */

public class NumberString {

    /** Stellt eine Zahl mit Tausenderpunkten dar. */
    public static String taupu(int number) {
        DecimalFormat df = new DecimalFormat(",###");
        return df.format(number);
    }

    /** Stellt eine Zahl mit Tausenderpunkten dar. */
    public static String taupu(long number) {
        DecimalFormat df = new DecimalFormat(",###");
        return df.format(number);
    }

    /** Entfernt die Tausenderpunkte wieder aus dem String. */
    public static String removeTaupu(String taupu) {
        return taupu.replace(".", "");
    }

    private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+");

    /**
     * Prüft, ob der gegebene String vollständig aus Ziffern besteht. Mindestens eine Ziffer muss
     * vorhanden sein.
     */
    public static boolean isDigitSequence(String text) {
        Matcher numberMatcher = NUMBER_PATTERN.matcher(text);
        return numberMatcher.matches();
    }

    /**
     * Prüft, ob der gegebene String mit dem Minuszeichen beginnt und danach vollständig aus
     * Ziffern besteht. Mindestens eine Ziffer muss vorhanden sein.
     */
    public static boolean isNegativeDigitSequence(String text) {
        if (text.startsWith("-")) {
            return isDigitSequence(text.substring(1));
        }
        else {
            return false;
        }
    }

    private static final Pattern NUMBER_PATTERN_WITH_POINT = Pattern.compile("\\d[\\d\\.]*");

    /**
     * Prüft, ob der gegebene String vollständig aus Ziffern und Punkten besteht. Mindestens eine
     * Ziffer muss vorhanden sein und der String muss mit einer Ziffer beginnen.
     */
    public static boolean isDigitPerhapsWithPointSequence(String text) {
        Matcher numberMatcher = NUMBER_PATTERN_WITH_POINT.matcher(text);
        return numberMatcher.matches();
    }

    private static final Pattern NUMBER_PATTERN_WITH_POINT_NEGATIVE_POSSIBLE = Pattern.compile(
            "-?\\d[\\d\\.]*");

    /**
     * Prüft, ob der gegebene String vollständig aus Ziffern und Punkten besteht. Mindestens eine
     * Ziffer muss vorhanden sein und der String muss mit einer Ziffer beginnen.
     */
    public static boolean isDigitPerhapsNegativeWithPointSequence(String text) {
        Matcher numberMatcher = NUMBER_PATTERN_WITH_POINT_NEGATIVE_POSSIBLE.matcher(text);
        return numberMatcher.matches();
    }

    private static final Pattern NUMBER_PATTERN_WITH_COMMA = Pattern.compile("\\d[\\d,]*");

    /**
     * Prüft, ob der gegebene String vollständig aus Ziffern und Kommata besteht. Mindestens eine
     * Ziffer muss vorhanden sein und der String muss mit einer Ziffer beginnen.
     */
    public static boolean isDigitPerhapsWithCommaSequence(String text) {
        Matcher numberMatcher = NUMBER_PATTERN_WITH_COMMA.matcher(text);
        return numberMatcher.matches();
    }

    /**
     * Prüft, ob der gegebene String vollständig aus Ziffern Komma und zwei weiteren Ziffern
     * besteht. Mindestens eine Ziffer muss vorhanden sein und der String muss mit einer Ziffer
     * beginnen.
     */
    public static boolean isDigitWithCommaAndTwoNachkommastellenSequence(String number) {
        int commaIndex = number.indexOf(",");
        if (commaIndex > -1) {
            int secondCommaIndex = number.indexOf(",", commaIndex + 1);
            if (secondCommaIndex > -1) {
                return false;
            }
            else {
                String front = number.substring(0, commaIndex); // ohne Dezimalkomma
                String nachkommastellen = number.substring(commaIndex + 1);
                return nachkommastellen.length() == 2
                        && isDigitSequence(front)
                        && isDigitSequence(nachkommastellen);
            }
        }
        else {
            return false;
        }
    }

    private static final Pattern ANY_NUMBER_PATTERN = Pattern.compile("\\d[\\d\\.,]*");

    /**
     * Prüft, ob der gegebene String vollständig aus Ziffern, Kommata und Punkten besteht.
     * Mindestens eine Ziffer muss vorhanden sein und der String muss mit einer Ziffer beginnen.
     */
    public static boolean isAnyDigitSequence(String text) {
        Matcher numberMatcher = ANY_NUMBER_PATTERN.matcher(text);
        return numberMatcher.matches();
    }

    /** Prüft, ob der gegebene String (neben anderem) mindestens eine Ziffer enthält. */
    public static boolean containsDigit(String text) {
        Matcher numberMatcher = NUMBER_PATTERN.matcher(text);
        return numberMatcher.find();
    }

    /** Entfernt von einem String (der ein integer oder dergleichen darstellt) am Anfang Nullen. */
    public static String removeLeadingZeroes(String text) {
        String numberText = text;

        while (numberText.startsWith("0")) {
            numberText = numberText.substring(1);
        }

        return numberText;
    }

    /**
     * Entfernt von einem String (der ein double darstellt) am Ende Nullen und dann auch noch den
     * Dezimalpunkt.
     */
    public static String removeZeroEnding(String text) {
        String numberText = text;

        if (numberText.contains(".")) {
            while (numberText.endsWith("0")) {
                numberText = numberText.substring(0, numberText.length() - 1);
            }
        }
        if (numberText.endsWith(".")) {
            numberText = numberText.substring(0, numberText.length() - 1);
        }

        return numberText;
    }

    /**
     * Bereinigt Zahlenschreibweisen mit Kommas oder Punkten.
     *
     * @param source
     *            Zahlenstring mit Punkt oder Komma als Dezimaltrenner und Punkt
     *            als Tausendertrennzeichen.
     * @return Zahlenstring mit Punkt als Dezimaltrenner.
     */
    public static String cleanNumber(String source) {
        String cleanedSource = source;

        int numberOfPoints = Text.countPartInString(cleanedSource, ".");
        int numberOfKommas = Text.countPartInString(cleanedSource, ",");

        if (numberOfPoints > 1 || numberOfKommas > 0) {
            cleanedSource = cleanedSource.replace(".", "");
        }
        if (numberOfKommas > 1) {
            cleanedSource = cleanedSource.replace(",", "");
        }

        cleanedSource = cleanedSource.replace(",", ".");

        return cleanedSource;
    }

    private final static Pattern MATCHING_NUMBER_PATTERN = Pattern.compile("(\\d+)");

    /**
     * Findet alle ganzzahligen Integerzahlen im Eingabetext.
     *
     * @param text
     *            Eingabetext.
     * @return Liste mit den gefundenen Integerzahlen.
     */
    public static List<Integer> findNumbers(String text) {
        List<Integer> numbers = new ArrayList<>();
        for (String numberString : findNumberStrings(text)) {
            Integer number = Integer.parseInt(numberString);
            numbers.add(number);
        }

        return numbers;
    }

    /**
     * Findet alle ganzzahligen Integerzahlen im Eingabetext.
     *
     * @param text
     *            Eingabetext.
     * @return Liste mit den gefundenen Integerzahlen.
     */
    public static List<String> findNumberStrings(String text) {
        List<String> numbers = new ArrayList<>();
        Matcher matcher = MATCHING_NUMBER_PATTERN.matcher(text);
        while (matcher.find()) {
            String number = matcher.group(1);
            numbers.add(number);
        }

        return numbers;
    }

    private final static Pattern MATCHING_NUMBER_AT_FRONT_PATTERN = Pattern.compile("^(\\d+)");

    /**
     * Gibt die ganzzahlige Integerzahl zu Beginn zurück. Falls dort keine gefunden wurde, wird -1
     * zurückgegeben.
     */
    public static int findNaturalNumberAtStart(String text) {
        String naturalNumberAtStart = findNaturalNumberAtStartString(text);
        if (naturalNumberAtStart.isEmpty()) {
            return -1;
        }
        else {
            return Integer.parseInt(naturalNumberAtStart);
        }
    }

    /**
     * Gibt die ganzzahlige Integerzahl zu Beginn zurück. Falls dort keine gefunden wurde, wird der
     * leere String zurückgegeben.
     */
    public static String findNaturalNumberAtStartString(String text) {
        Matcher matcher = MATCHING_NUMBER_AT_FRONT_PATTERN.matcher(text);
        if (matcher.find()) {
            return matcher.group(1);
        }

        return "";
    }

    /**
     * Wandelt eine Zahl in einen String um und fügt dabei führende Nullen hinzu. Aus number = 13
     * und numberOfDigits = 5 wird so "00013".
     *
     * @param number
     *            Umzuwandelnde Zahl.
     * @param numberOfDigits
     *            Anzahl der Stellen, entsprechend viele Nullen werden vorn aufgefüllt.
     * @return Zahl als String mit führenden Nullen.
     */
    public static String addLeadingZeroes(int number, int numberOfDigits) {
        String zeroNumber = Integer.toString(number);
        if (zeroNumber.length() >= numberOfDigits || number < 0 || numberOfDigits < 0) {
            return zeroNumber;
        }
        else {
            return Text.multipleString("0", numberOfDigits - zeroNumber.length()) + zeroNumber;
        }
    }

    /**
     * Wandelt eine Zahl in einen String um und fügt dabei führende Nullen hinzu. Aus number = 13
     * und numberOfDigits = 5 wird so "00013".
     *
     * @param number
     *            Umzuwandelnde Zahl.
     * @param numberOfDigits
     *            Anzahl der Stellen, entsprechend viele Nullen werden vorn aufgefüllt.
     * @return Zahl als String mit führenden Nullen.
     */
    public static String addLeadingZeroes(long number, int numberOfDigits) {
        String zeroNumber = Long.toString(number);
        if (zeroNumber.length() >= numberOfDigits || number < 0 || numberOfDigits < 0) {
            return zeroNumber;
        }
        else {
            return Text.multipleString("0", numberOfDigits - zeroNumber.length()) + zeroNumber;
        }
    }

    /**
     * Fügt vor einen String (mit Ziffern oder anderem) führende Nullen hinzu. Aus number = "13"
     * und numberOfDigits = 5 wird so "00013".
     *
     * @param number
     *            Umzuwandelnde Zahl oder anderes als String.
     * @param numberOfDigits
     *            Anzahl der Stellen, entsprechend viele Nullen werden vorn aufgefüllt.
     * @return Zahl als String mit führenden Nullen.
     */
    public static String addLeadingZeroes(String number, int numberOfDigits) {
        String zeroNumber = number;
        if (zeroNumber.length() >= numberOfDigits || number.startsWith("-") || numberOfDigits < 0) {
            return zeroNumber;
        }
        else {
            return Text.multipleString("0", numberOfDigits - zeroNumber.length()) + zeroNumber;
        }
    }

    /**
     * Die übergebene Zahl in einem String wird in einen entsprechenden int-Wert umgewandelt. Wenn
     * dabei (per Integer.parseInt(...)) eine NumberFormatException auftrat, wird statt dieser eine
     * RuntimeException mit einem entsprechenden Fehlertext geworfen.
     *
     * @param numberString
     *            String mit einer Integerzahl darin (oder auch nicht).
     * @return Gegebene Zahl als int.
     * @throws RuntimeException
     *             Falls ein Fehler beim Parsen der Zahl auftrat.
     */
    public static int parseInt(String numberString) {
        String message = "Lässt sich nicht als Integer parsen: '" + numberString + "'";
        return parseInt(numberString, message);
    }

    /**
     * Die übergebenen Zahlen in Strings werden in entsprechende int-Wert umgewandelt. Wenn dabei
     * (per Integer.parseInt(...)) eine NumberFormatException auftrat, wird statt dieser eine
     * RuntimeException mit einem entsprechenden Fehlertext geworfen.
     *
     * @param indexStrings
     *            Die Strings mit je einer Integerzahl darin (oder auch nicht).
     * @return Liste der Zahlen.
     * @throws RuntimeException
     *             Falls ein Fehler beim Parsen der Zahlen auftrat.
     */
    public static List<Integer> parseInts(List<String> strings) {
        List<Integer> integers = new ArrayList<>();

        for (String string : strings) {
            int integer = parseInt(string);
            integers.add(integer);
        }

        return integers;
    }

    /**
     * Die übergebene Zahl in einem String wird in einen entsprechenden int-Wert umgewandelt. Wenn
     * dabei (per Integer.parseInt(...)) eine NumberFormatException auftrat, wird statt dieser eine
     * RuntimeException mit dem übergebenen Fehlertext geworfen.
     *
     * @param numberString
     *            String mit einer Integerzahl darin (oder auch nicht).
     * @param errorMessage
     *            Fehlertext.
     * @return Gegebene Zahl als int.
     * @throws RuntimeException
     *             Falls ein Fehler beim Parsen der Zahl auftrat.
     */
    public static int parseInt(String numberString, String errorMessage) {
        try {
            return Integer.parseInt(numberString);
        }
        catch (NumberFormatException exception) {
            throw new RuntimeException(errorMessage, exception);
        }
    }

    /**
     * Die übergebene Zahl in einem String wird in einen entsprechenden int-Wert umgewandelt. Wenn
     * dabei (per Integer.parseInt(...)) eine NumberFormatException auftrat, wird statt dieser der
     * Fehlerwert zurückgegeben.
     *
     * @param numberString
     *            String mit einer Integerzahl darin (oder auch nicht).
     * @param failureValue
     *            Wert, der im Fehlerfall zurückgegeben wird.
     * @return Gegebene Zahl als int.
     */
    public static int parseIntIgnore(String numberString, int failureValue) {
        try {
            return Integer.parseInt(numberString);
        }
        catch (NumberFormatException exception) {
            return failureValue;
        }
    }

    /** Prüft, ob der übergebene String eine Integer-Zahl enthält. */
    public static boolean isInteger(String numberString) {
        try {
            Integer.parseInt(numberString);
            return true;
        }
        catch (NumberFormatException exception) {
            return false;
        }
    }

    /**
     * Die übergebende Zahl in einem String wird in einen entsprechenden int-Wert umgewandelt. Wenn
     * dabei (per Long.parseLong(...)) eine NumberFormatException auftrat, wird statt dieser eine
     * RuntimeException mit einem entsprechenden Fehlertext geworfen.
     *
     * @param numberString
     *            String mit einer Longzahl darin (oder auch nicht).
     * @return Gegebene Zahl als long.
     * @throws RuntimeException
     *             Falls ein Fehler beim Parsen der Zahl auftrat.
     */
    public static long parseLong(String numberString) {
        String message = "Lässt sich nicht als Long parsen: '" + numberString + "'";
        return parseLong(numberString, message);
    }

    /**
     * Die übergebende Zahl in einem String wird in einen entsprechenden Long-Wert umgewandelt.
     * Wenn dabei (per Long.parseLong(...)) eine NumberFormatException auftrat, wird statt dieser
     * eine RuntimeException mit dem übergebenen Fehlertext geworfen.
     *
     * @param numberString
     *            String mit einer Longzahl darin (oder auch nicht).
     * @param errorMessage
     *            Fehlertext.
     * @return Gegebene Zahl als long.
     * @throws RuntimeException
     *             Falls ein Fehler beim Parsen der Zahl auftrat.
     */
    public static long parseLong(String numberString, String errorMessage) {
        try {
            return Long.parseLong(numberString);
        }
        catch (NumberFormatException exception) {
            throw new RuntimeException(errorMessage, exception);
        }
    }

    /**
     * Die übergebende Zahl in einem String wird in einen entsprechenden Long-Wert umgewandelt.
     * Wenn dabei (per Long.parseLong(...)) eine NumberFormatException auftrat, wird statt dieser
     * der Fehlerwert zurückgegeben.
     *
     * @param numberString
     *            String mit einer Integerzahl darin (oder auch nicht).
     * @param failureValue
     *            Wert, der im Fehlerfall zurückgegeben wird.
     * @return Gegebene Zahl als long.
     */
    public static long parseLongIgnore(String numberString, long failureValue) {
        try {
            return Long.parseLong(numberString);
        }
        catch (NumberFormatException exception) {
            return failureValue;
        }
    }

    /**
     * Die übergebende Zahl in einem String wird in einen entsprechenden double-Wert umgewandelt.
     * Wenn dabei (per Double.parseDouble(...)) eine NumberFormatException auftrat, wird statt
     * dieser eine RuntimeException mit dem übergebenen Fehlertext geworfen.
     *
     * @param numberString
     *            String mit einer Double-Zahl darin (oder auch nicht).
     * @param errorMessage
     *            Fehlertext.
     * @return Gegebene Zahl als double.
     * @throws RuntimeException
     *             Falls ein Fehler beim Parsen der Zahl auftrat.
     */
    public static double parseDouble(String numberString, String errorMessage) {
        try {
            return Double.parseDouble(numberString);
        }
        catch (NumberFormatException exception) {
            throw new RuntimeException(errorMessage, exception);
        }
    }

    /**
     * Die übergebende Zahl in einem String wird in einen entsprechenden double-Wert umgewandelt.
     * Wenn dabei (per Double.parseDouble(...)) eine NumberFormatException auftrat, wird statt
     * dieser der Fehlerwert zurückgegeben.
     *
     * @param numberString
     *            String mit einer Double-Zahl darin (oder auch nicht).
     * @param failureValue
     *            Wert, der im Fehlerfall zurückgegeben wird.
     * @return Gegebene Zahl als double.
     */
    public static double parseDoubleIgnore(String numberString, double failureValue) {
        try {
            return Double.parseDouble(numberString);
        }
        catch (NumberFormatException exception) {
            return failureValue;
        }
    }

    /** Wandelt eine zweistellige Zahl in einem String in einen Integerwert um. */
    public static int twoDigitStringToInteger(String twoDigits) {
        if (twoDigits.length() != 2) {
            throw new RuntimeException(
                    "Es werden zwei Stellen erwartet! twoDigits = '" + twoDigits + "'.");
        }
        String number;
        if (twoDigits.startsWith("0")) {
            number = twoDigits.substring(1);
        }
        else {
            number = twoDigits;
        }
        return Integer.parseInt(number);
    }

    /** Gibt die Fließkommazahl als String mit zwei Nachkommastellen zurück. */
    public static String twoDecimalPlaces(double number) {
        return String.format("%.02f", number);
    }

    /**
     * Gibt die Fließkommazahl als String mit zwei Nachkommastellen zurück. Zusätzlich wird der
     * Teil vor dem Komma mit Tausenderpunkten versehen.
     */
    public static String twoDecimalPlacesTaupu(double number) {
        long before = (long) number;
        double after = number - before;
        String zeroCommaAndAfter = twoDecimalPlaces(after);
        String commaAndAfter = zeroCommaAndAfter.substring(1);
        return taupu(before) + commaAndAfter;
    }

    /**
     * Erzeugt aus einer Liste von double-Werten eine Liste von Fließkommazahl als String mit zwei
     * Nachkommastellen zurück. Zusätzlich wird der Teil vor dem Komma mit Tausenderpunkten
     * versehen.
     */
    public static List<String> doubleListToTwoDecimalPlacesTaupuList(List<Double> numbers) {
        List<String> twoDecimalPlacesTaupuList = new ArrayList<>();

        for (double number : numbers) {
            String twoDecimalPlacesTaupu = twoDecimalPlacesTaupu(number);
            twoDecimalPlacesTaupuList.add(twoDecimalPlacesTaupu);
        }

        return twoDecimalPlacesTaupuList;
    }

    /** Bestimmt die Anzahl Stellen einer positiven ganzen Zahl. */
    public static int calculateDigits(int number) {
        Assure.isAtLeast(number, 0);

        // Vergleiche AnzahlStellenEinerZahl im ShortTestCodes.math
        return Integer.toString(number).length();
    }

    /** Berechnet einen Prozentwert von count an der Gesamtzahl total. */
    public static double percentAsNumber(int count, int total) {
        return 100.0 * (count / (double) total);
    }

    /**
     * Berechnet einen Prozentwert von count an der Gesamtzahl total und gibt ihn als String mit
     * zwei Nachkommastellen zurück.
     */
    public static String percent(int count, int total) {
        return twoDecimalPlaces(percentAsNumber(count, total));
    }

    /** Berechnet einen Prozentwert von count an der Gesamtzahl total. */
    public static double percentAsNumber(long count, long total) {
        return 100.0 * (count / (double) total);
    }

    /**
     * Berechnet einen Prozentwert von count an der Gesamtzahl total und gibt ihn als String mit
     * zwei Nachkommastellen zurück.
     */
    public static String percent(long count, long total) {
        return twoDecimalPlaces(percentAsNumber(count, total));
    }

    /** Gibt eine Zahl in einem String als sich selbst, "Null" oder "Ein" zurück. */
    public static String toGermanNumber(String number) {
        if (number.equals("0")) {
            return "Null";
        }
        else if (number.equals("1")) {
            return "Ein";
        }
        else {
            return number;
        }
    }

    /** Gibt eine Zahl in einem String als sich selbst, "kein" oder "ein" zurück. */
    public static String toSmallGermanNumber(String number) {
        if (number.equals("0")) {
            return "kein";
        }
        else if (number.equals("1")) {
            return "ein";
        }
        else {
            return number;
        }
    }

    /** Gibt eine Zahl in einem String als sich selbst, "kein" oder "ein" zurück. */
    public static String toSmallGermanNumber(int number) {
        if (number == 0) {
            return "kein";
        }
        else if (number == 1) {
            return "ein";
        }
        else {
            return Integer.toString(number);
        }
    }

    /** Gibt eine Zahl in einem String als sich selbst, "keine" oder "eine" zurück. */
    public static String toSmallGermanNumberWithE(String number) {
        if (number.equals("0")) {
            return "keine";
        }
        else if (number.equals("1")) {
            return "eine";
        }
        else {
            return number;
        }
    }

    /** Gibt eine Zahl in einem String als sich selbst, "keine" oder "eine" zurück. */
    public static String toSmallGermanNumberWithE(int number) {
        if (number == 0) {
            return "keine";
        }
        else if (number == 1) {
            return "eine";
        }
        else {
            return Integer.toString(number);
        }
    }

    /** Gibt abhängig von der Zahl die Plural- oder die Singular-Form des Wortes zurück. */
    public static String germanPlural(int number, String plural, String singular) {
        if (number == 1) {
            return singular;
        }
        else {
            return plural;
        }
    }

    /** Berechnet 16 hoch exponent als long. */
    public static long hexPow(int exponent) {
        long base = 16;
        long result = 1;

        for (int i = 0; i < exponent; ++i) {
            result *= base;
        }

        return result;
    }

    /** Erzeugt aus einer einstelligen "Zahl" im hexadezimalen Format einen long-Wert. */
    public static long hexChar2long(String hexCharacter) {
        if (hexCharacter.length() != 1) {
            throw new IllegalArgumentException("Die Methode hexChar2long() darf nur mit einer "
                    + "einstelligen hexadezimalen Ziffer aufgerufen werden.");
        }

        switch (Text.toLowerCase(hexCharacter)) {
            case "0": return 0L;
            case "1": return 1L;
            case "2": return 2L;
            case "3": return 3L;
            case "4": return 4L;
            case "5": return 5L;
            case "6": return 6L;
            case "7": return 7L;
            case "8": return 8L;
            case "9": return 9L;

            case "a": return 10L;
            case "b": return 11L;
            case "c": return 12L;
            case "d": return 13L;
            case "e": return 14L;
            case "f": return 15L;
        }

        throw new IllegalArgumentException("Die Methode hexChar2long() darf nur mit einer "
                + "einstelligen hexadezimalen Ziffer mit einem der möglichen Werte 0-9, a-f oder "
                + "A-F aufgerufen werden.");
    }

    /** Erzeugt aus einer "Zahl" im hexadezimalen Format einen long-Wert. */
    public static long hex2long(String hex) {
        if (hex.isEmpty()) {
            throw new IllegalArgumentException(
                    "Die Methode hex2long() darf nicht mit eine leeren Eingabe aufgerufen werden.");
        }
        long hexValue = 0;

        for (int index = 0; index < hex.length(); ++index) {
            String hexCharacter = hex.substring(index, index + 1);
            int exponent = hex.length() - index - 1;
            long multiplier = hexPow(exponent);
            long hexCharacterBaseValue = hexChar2long(hexCharacter);
            long hexCharacterValue = hexCharacterBaseValue * multiplier;
            hexValue += hexCharacterValue;
        }

        return hexValue;
    }

    /** Bereinigt die Nummer um Leerzeichen und führende Nullen. */
    public static String cleanNumberFromSpacesAndLeadingZeros(String number) {
        String cleanNumber = number;
        cleanNumber = Text.removeWhitespace(cleanNumber);
        cleanNumber = NumberString.removeLeadingZeroes(cleanNumber);
        return cleanNumber;
    }

    /**
     * Stellt fest, ob der übergebene Text eine pipegetrennte Liste von Ziffernfolgen ist, z.B.
     * '123|456|78'.
     */
    public static boolean isPipeSeparatedListOfNumbers(String input) {
        List<String> parts = Text.splitByPipeNotConsumingWhitespace(input);
        if (parts.isEmpty()) {
            return false;
        }

        for (String part : parts) {
            if (!isDigitSequence(part)) {
                return false;
            }
        }

        return true;
    }

    /** Prüft ob eine Art von Nummern, ggf. ergänzt um Leerzeichen, Slashes oder Minus vorliegt. */
    public static boolean looksLikeNumberPerhapsWithSpaceSlashMinus(String input) {
        String test = Text.removeWhitespace(input);
        test = test.replace("/", "");
        test = test.replace("-", "");
        return isDigitSequence(test);
    }

    /** Prüft ob die Eingabe mit einer Ziffer beginnt. */
    public static boolean startsWithDigit(String input) {
        if (input.isEmpty()) {
            return false;
        }
        else {
            String firstCharacter = input.substring(0, 1);
            return Text.DIGITS.contains(firstCharacter);
        }
    }

    /** Prüft ob die Eingabe mit fünf Ziffern und einem Leerzeichen beginnt. */
    public static boolean startsWithFiveDigitAndSpace(String input) {
        if (input.length() < 6) {
            return false;
        }
        else {
            String character1 = input.substring(0, 1);
            String character2 = input.substring(1, 2);
            String character3 = input.substring(2, 3);
            String character4 = input.substring(3, 4);
            String character5 = input.substring(4, 5);
            String character6 = input.substring(5, 6);
            return Text.DIGITS.contains(character1)
                    && Text.DIGITS.contains(character2)
                    && Text.DIGITS.contains(character3)
                    && Text.DIGITS.contains(character4)
                    && Text.DIGITS.contains(character5)
                    && character6.equals(" ")
                    ;
        }
    }

    /** Prüft ob die Eingabe auf eine Ziffer endet. */
    public static boolean endsWithDigit(String input) {
        if (input.isEmpty()) {
            return false;
        }
        else {
            String lastCharacter = input.substring(input.length() - 1, input.length());
            return Text.DIGITS.contains(lastCharacter);
        }
    }

    /**
     * Fügt Tausenderpunkte in eine Zahl als String ein, wenn diese Zahl genug ist, ansonsten wird
     * die originale Zahl (also der eingegebene String) zurückgegeben.
     */
    public static String taupuIfPossible(String number) {
        if (NumberString.isDigitSequence(number)) {
            try {
                long numberAsLong = Long.parseLong(number);
                return NumberString.taupu(numberAsLong);
            }
            catch (NumberFormatException exception) {
                return number;
            }
        }
        else {
            return number;
        }
    }

    /**
     * Erzeugt die Ausgabe einer Anzahl mit Prozent, passend eingerückt, damit diese untereinander
     * angeordnet ausgegeben werden können.
     *
     * @param number
     *            Die Anzahl die ausgegeben werden soll.
     * @param total
     *            Die Anzahl aller Fälle für die Prozentangabe.
     * @param lenghtOfNumberWithDot
     *            Die Länge der auszugebenden Anzahl mit Dezimalpunkt(en), auf diese wird passend
     *            eingerückt.
     */
    public static String numberAndPercent(int number, int total, int lenghtOfNumberWithDot) {
        String numberPart = NumberString.taupu(number);
        numberPart = Text.fillWithSpacesAtFront(numberPart, lenghtOfNumberWithDot);
        String percent = NumberString.percent(number, total);
        percent = Text.fillWithSpacesAtFront(percent, 6);
        String percentBrace = "(" + percent + "%)";
        return numberPart + " " + percentBrace;
    }

    /**
     * Parst eine Double-Zahl mit Tausenderpunkten und Komma als Trenner vor den Nachkommastellen.
     *
     * @param numberString
     *            Die Zahl mit Tausenderpunkten und Komma als Trenner vor den Nachkommastellen.
     * @return Die Zahl als Double-Wert.
     */
    public static double parseDoubleWithTaupuAndKomma(String numberString, double failureValue) {
        String withoutPoints = numberString.replace(".", "");
        String withDot = withoutPoints.replace(",", ".");
        return parseDoubleIgnore(withDot, failureValue);
    }

}
