package de.duehl.basics.text;

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

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.io.textfile.dictionary.Dictionary;
import de.duehl.basics.io.textfile.dictionary.DictionaryEntry;

/**
 * Diese Klasse verbessert allgemeine Tipp- und Rechtschreibfehler oder sonstige Umsetzungen in
 * Texten.
 *
 * Damit die Reihenfolge von langen und kurzen falschen Begriffen im Verzeichnis keine Rolle
 * spielt, wird hier ein gewisser Aufwand betrieben.
 *
 * Außerdem wird verhindert, dass man etwas ersetzt, das zuvor bereits ersetzt wurde.
 *
 * @version 1.01     2026-02-16
 * @author Christian Dühl
 */

public class SpellingErrorCorrector {

    /**
     * Das Ersetzungszeichen zum Verhindern von weiteren Ersetzungen in bereits ersetzten Teilen.
     *
     * Da die Längen sich später vor vor der aktuellen Ersetzung ändern könnten, wird mit einem
     * zweiten Text gearbeitet, in dem ersetztes durch dieses Zeichen ersetzt wird.
     *
     * Dieses Zeichen sollte natürlich in den Texten, auf die man diese Klasse anwendet, nicht
     * vorkommen. Daher ist es per Default auf das Hiragana "bu" (ぷ) gesetzt.
     */
    private static final String DEFAULT_MASK_CHARACTRER = "ぷ";


    /**
     * Die Liste mit allen falsch geschriebenen Worten aus dem Verzeichnis mit den richtigen und
     * zugehörigen falschen Schreibweisen.
     */
    private final List<String> mispelledPhrases;

    /** Das Verzeichnis mit den Falsch geschriebenen Worten zur richtig geschriebenen Variante. */
    private final Map<String, String> mispelledToCorrectWords;

    /**
     * Das Ersetzungszeichen zum Verhindern von weiteren Ersetzungen in bereits ersetzten Teilen.
     *
     * Dieses Zeichen sollte natürlich in den Texten, auf die man diese Klasse anwendet, nicht
     * vorkommen. Daher ist es per Default auf das Hiragana "bu" (ぷ) gesetzt.
     */
    private String maskCharacter;

    /**
     * Konstruktor.
     *
     * @param spellingErrorsDictionary
     *            Verzeichnis mit den richtigen und zugehörigen falschen Schreibweisen.
     */
    public SpellingErrorCorrector(Dictionary spellingErrorsDictionary) {
        mispelledPhrases = new ArrayList<>();
        mispelledToCorrectWords = new HashMap<>();

        initListAndMapWithDictionary(spellingErrorsDictionary);
        sortMispelledPhrasesByLengthDescanding();

        maskCharacter = DEFAULT_MASK_CHARACTRER;
    }

    private void initListAndMapWithDictionary(Dictionary spellingErrorsDictionary) {
        for (DictionaryEntry dictionaryEntry : spellingErrorsDictionary) {
            initListAndMapWithDictionaryList(dictionaryEntry);
        }
    }

    private void initListAndMapWithDictionaryList(DictionaryEntry dictionaryEntry) {
        String correctSpelledPhrase = dictionaryEntry.getMainWord();
        List<String> mispelledAlternatives = dictionaryEntry.getAlternatives();

        if (mispelledAlternatives.isEmpty()) {
            throw new RuntimeException("Eine Liste im Dictionary enthält nur ein richtig "
                    + "geschriebenes Wort, aber keine falsch geschriebenen: '"
                    + correctSpelledPhrase + "'.");
        }

        for (String mispelledPhrase : mispelledAlternatives) {
            if (mispelledPhrases.contains(mispelledPhrase)) {
                throw new RuntimeException("Falschgeschriebener Begriff ist doppelt "
                        + "vorhanden: '" + mispelledPhrase + "'.");
            }
            mispelledPhrases.add(mispelledPhrase);
            mispelledToCorrectWords.put(mispelledPhrase, correctSpelledPhrase);
        }
    }

    private void sortMispelledPhrasesByLengthDescanding() {
        CollectionsHelper.sortStringListByLengthDescanding(mispelledPhrases);
    }

    /**
     * Setter für das Ersetzungszeichen zum Verhindern von weiteren Ersetzungen in bereits
     * ersetzten Teilen.
     *
     * Dieses Zeichen sollte natürlich in den Texten, auf die man diese Klasse anwendet, nicht
     * vorkommen. Es ist per Default auf das Hiragana "bu" (ぷ) gesetzt und kann hier geändert
     * werden, wenn man Texte korrigieren möchte, die das Default-Zeichen beinhalten..
     */
    public void setMaskCharacter(String maskCharacter) {
        this.maskCharacter = maskCharacter;
    }

    /**
     * Korrigiert einen einzelnen String, dabei werden die längsten Ersetzungen zuerst vorgenommen,
     * auch wenn sie in späteren Ersetzungslisten auftauchen und es wird verhindert, dass man etwas
     * ersetzt, das zuvor bereits ersetzt wurde.
     *
     * Ohne sicherzustellen, dass man nur einmal ersetzt, ging in der Schleife einfach:
     *     String correctPhrase = mispelledToCorrectWords.get(mispelledPhrase);
     *     text = text.replace(mispelledPhrase, correctPhrase);
     */
    public String correct(String originalText) {
        String text = originalText;
        String maskCorrectedText = originalText;

        for (String mispelledPhrase : mispelledPhrases) {
            int searchIndex = 0;
            boolean search = true;
            while (search && searchIndex < text.length()) {
                int index = text.indexOf(mispelledPhrase, searchIndex);
                if (-1 == index) {
                    search = false;
                }
                else {
                    int end = index + mispelledPhrase.length();
                    String maskCorrectedTextPart = maskCorrectedText.substring(index, end);
                    if (!maskCorrectedTextPart.contains(maskCharacter)) {
                        String beforeText = text.substring(0, index);
                        String afterText = text.substring(end);
                        String correctPhrase = mispelledToCorrectWords.get(mispelledPhrase);
                        text = beforeText + correctPhrase + afterText;

                        String beforeMaskText = maskCorrectedText.substring(0, index);
                        String afterMaskText = maskCorrectedText.substring(end);
                        String maskReplace = Text.multipleString(maskCharacter,
                                correctPhrase.length());
                        maskCorrectedText = beforeMaskText + maskReplace + afterMaskText;

                        searchIndex = end - mispelledPhrase.length() + correctPhrase.length();
                    }
                    else {
                        searchIndex = end;
                    }
                }
            }
        }

        return text;
    }

    /** Korrigiert eine ganze Liste von String, die Inhalte der Liste werden dabei verändert. */
    public void correct(List<String> texts) {
        for (int index = 0; index < texts.size(); ++index) {
            String text = texts.get(index);
            String correctedText = correct(text);
            if (!text.equals(correctedText)) {
                texts.set(index, correctedText);
            }
        }
    }

    /**
     * Getter für die Liste mit allen falsch geschriebenen Worten aus dem Verzeichnis mit den
     * richtigen und zugehörigen falschen Schreibweisen.
     */
    public List<String> getMispelledPhrases() {
        return mispelledPhrases;
    }

}
