package de.duehl.basics.io.textfile;

/*
 * 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 de.duehl.basics.datetime.time.ContinousTimeMeasurement;
import de.duehl.basics.io.Charset;
import de.duehl.basics.io.Reader;
import de.duehl.basics.text.NumberString;

/**
 * Diese Klasse ist die abstrakte Basis um eine Text-Datei einzulesen.
 *
 * @version 1.01     2025-01-14
 * @author Christian Dühl
 */

public abstract class FullTextFileReader {

    private static final int NUMBER_OF_LINES_READ_TO_PRINT_PROGRESS = 100_000;

    /** Der Name der einzulesenden Datei. */
    protected final String filename;

    /** Die Kodierung der einzulesenden Datei. */
    protected final Charset charset;

    /** Beschreibung des Zeileninhalts der einzulesenden Datei (für die Fortschrittsausgaben). */
    private String lineDescription;

    /** Zählt die analysierten Zeilen. */
    private int countAnalysedLines;

    /** Anfang der Ausgaben für die Fortschrittsausgaben. */
    private String progressTextStart;

    /** Gibt an, ob leere Zeilen übersprungen werden sollen. Default ist false. */
    private boolean skipBlankLines;

    /** Gibt an, ob Fortschrittsausgaben nach STDOUT erfolgen sollen. Default: true. */
    private boolean verboose;

    /** Gibt an, ob die erste Zeile übersprungen werden soll. Default ist false. */
    private boolean skipFirstLine;

    /** Übersprungene erste Zeile, oder der leere String, falls keine übersprungen wird. */
    private String skippedFirstLine;

    /** Anzahl der Zeilen, nach der eine Fortschrittsausgabe nach STDOUT erfolgen soll. */
    private int numberOfLinesReadToPrintProgress;

    /** Liest die Datei ein. */
    private Reader reader;

    /** Gibt an, ob das Einlesen bereits vor Ende der Datei beendet werden soll. */
    private boolean stopReading;

    /**
     * Gibt an, ob die verbrauchte Zeit seit der letzten Zeitmessung und der Prozentsatz zur ersten
     * Zeitmessung angezeigt werden soll.
     */
    private boolean showDiffenceBetweenTimeMeasurements;

    /** Objekt zur fortlaufenden Zeitmessung. */
    private ContinousTimeMeasurement timeMeasurement;

    /**
     * Konstruktor.
     *
     * @param filename
     *            Der Name der einzulesenden Datei.
     * @param charset
     *            Die Kodierung der einzulesenden Datei.
     */
    public FullTextFileReader(String filename, Charset charset) {
        this.filename = filename;
        this.charset = charset;
        progressTextStart = "Einlesen";
        lineDescription = "";
        skipBlankLines = false;
        verboose = true;
        skipFirstLine = false;
        skippedFirstLine = "";
        numberOfLinesReadToPrintProgress = NUMBER_OF_LINES_READ_TO_PRINT_PROGRESS;
        stopReading = false;
        showDiffenceBetweenTimeMeasurements = true;
        timeMeasurement = new ContinousTimeMeasurement();
    }

    /**
     * Setter für die Beschreibung des Zeileninhalts der einzulesenden Datei (für die
     * Fortschrittsausgaben).
     */
    public void setLineDescription(String lineDescription) {
        this.lineDescription = lineDescription;
    }

    /** Setter für den Anfang der Ausgaben für die Fortschrittsausgaben (Default ist "Einlesen"). */
    public void setProgressTextStart(String progressTextStart) {
        this.progressTextStart = progressTextStart;
    }

    /** Legt fest, dass leeren Zeilen übersprungen werden sollen. */
    public void skipFirstLine() {
        skipFirstLine = true;
    }

    /** Legt fest, dass keine leeren Zeilen übersprungen werden sollen. Dies ist der Default. */
    public void doNotSkipBlankLines() {
        skipBlankLines = false;
    }

    /** Legt fest, dass leeren Zeilen übersprungen werden sollen. */
    public void skipBlankLines() {
        skipBlankLines = true;
    }

    /** Legt fest, dass keine Fortschrittsausgaben nach STDOUT erfolgen sollen. */
    public void beQuiet() {
        verboose = false;
    }

    /**
     * Setter für die Anzahl der Zeilen, nach der eine Fortschrittsausgabe nach STDOUT erfolgen
     * soll (Default ist 100.000).
     */
    public void setNumberOfLinesReadToPrintProgress(int numberOfLinesReadToPrintProgress) {
        this.numberOfLinesReadToPrintProgress = numberOfLinesReadToPrintProgress;
    }

    /** Beendet das Einlesen bereits vor Ende der Datei. */
    public void stopReading() {
        stopReading = true;
    }

    /**
     * Legt fest, dass die verbrauchte Zeit seit der letzten Zeitmessung und der Prozentsatz zur
     * ersten Zeitmessung nicht angezeigt werden soll.
     */
    public void hideDiffenceBetweenTimeMeasurements() {
        showDiffenceBetweenTimeMeasurements = false;
    }

    /**
     * List die Datei ein.                                                                  <br><br>
     *
     * Der Aufruf kann in der folgenden Form erfolgen:              <br><tt>&nbsp;&nbsp;&nbsp;&nbsp;
     *     reader.read(line -> analyseLine(line));                                         </tt><br>
     * wobei die Methode                                            <br><tt>&nbsp;&nbsp;&nbsp;&nbsp;
     *     analyseLine                                                                     </tt><br>
     * eine Methode in der gleichen Klasse ist.
     *
     * @param analyzer
     *            Objekt, dass die gewünschten Zeilen der Datei verarbeitet.
     */
    public void read(NotEmptyLineAnalyzer analyzer) {
        timeMeasurement = new ContinousTimeMeasurement();
        timeMeasurement.startTimeMeasuring();

        reader = openReader();

        if (skipFirstLine) {
            skippedFirstLine = reader.readNextLine();
        }

        countAnalysedLines = 0;
        String line;
        while ((line = reader.readNextLine()) != null && !stopReading) {
            if (!skipBlankLines || !line.trim().isEmpty()) {
                analyzer.analyseLine(line);
                ++countAnalysedLines;
                if (countAnalysedLines % numberOfLinesReadToPrintProgress == 0) {
                    printProgressOutputInLoop();
                }
            }
        }
        printProgressOutputAfterLoop();

        reader.close();
        timeMeasurement.stopTimeMeasuring();
    }

    /** Öffnet den Reader zum Einlesen der Datei. */
    abstract protected Reader openReader();

    private void printProgressOutputInLoop() {
        printProgressOutput("bislang");
    }

    private void printProgressOutputAfterLoop() {
        printProgressOutput("insgesamt");
    }

    private void printProgressOutput(String additional) {
        if (verboose) {
            reallyPrintProgressOutput(additional);
        }
    }

    private void reallyPrintProgressOutput(String additional) {
        StringBuilder builder = new StringBuilder();
        builder.append(progressTextStart)
                .append(" von ")
                .append(NumberString.taupu(countAnalysedLines))
                ;

        if (!lineDescription.isEmpty()) {
            builder.append(" mit ").append(lineDescription);
        }

        builder.append(" in ")
                .append(additional)
                .append(": ")
                ;

        if (showDiffenceBetweenTimeMeasurements) {
            builder.append(timeMeasurement.timeMeasurement());
        }
        else {
            builder.append(getRuntime());
        }

        System.out.println(builder.toString());
    }

    /** Liefert während der Bearbeitung der Zeilen die Zeilennummer. */
    public int getLineNumber() {
        return reader.getLineNumber();
    }

    /** Liefert die Laufzeit zurück. */
    public String getRuntime() {
        return timeMeasurement.getRuntime();
    }

    /**
     * Getter für die Übersprungene erste Zeile, oder der leere String, falls keine übersprungen
     * wird.
     */
    public String getSkippedFirstLine() {
        return skippedFirstLine;
    }

    /** Getter für den Namen der einzulesenden Datei. */
    public String getFilename() {
        return filename;
    }

}
