package de.duehl.basics.logging;

/*
 * Copyright 2021 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;

import de.duehl.basics.datetime.Timestamp;
import de.duehl.basics.io.FileHelper;
import de.duehl.basics.logging.eraser.LogEraser;
import de.duehl.basics.text.Text;

/**
 * Dieses Klasse dient zum Loggen von Prozessabläufen in eine Datei. Es wird vermerkt, wann und von
 * wo aufgerufen wird. Die Folgenden Informationen werden geloggt:
 *
 *     1: Datum
 *     2: Uhrzeit
 *     3: Klasse des Aufrufers
 *     4: Methode des Aufrufers
 *     5: Zeilennummer
 *     6: Inhalt der Nachricht
 *
 * @version 1.01     2021-02-17
 * @author Christian Dühl
 */

public class FileLogger extends StructuredLogger {

    /** Name des Logfiles. */
    private String logFileName;

    /** Titel der Spalten. */
    private final List<String> columnHeaders;

    /**
     * Konstruktor. Die angegebene Datei wird, so vorhanden, überschrieben und nicht angehängt!
     *
     * @param logFileName
     *            Name des Logfiles.
     */
    FileLogger(String logFileName) {
        super();
        this.logFileName = logFileName;
        //checkThatFileDoesNotExists(logFileName);
        //Sonst gibt es Probleme in Tests, die in der gleichen Sekunde mehrere Logger erzeugen...
        FileHelper.createEmptyFile(logFileName); // wir Überschrieben im Zweifelsfall auch!
        columnHeaders = new ArrayList<>();
        initColumnHeaders();
    }

    private void initColumnHeaders() {
        columnHeaders.add("Datum");
        columnHeaders.add("Zeit");
        columnHeaders.add("Klasse");
        columnHeaders.add("Methode");
        columnHeaders.add("Zeilennummer");
        columnHeaders.add("Nachricht");
    }

    /**
     * Hier wird ein komplexerer Eintrag erzeugt mit sechs tabgetrennten Spalten.
     *     1: Datum
     *     2: Zeit
     *     3: Klasse des Aufrufers
     *     4: Methode des Aufrufers
     *     5: Zeilennummer
     *     6: Inhalt der Nachricht
     *
     * @param text
     *            Inhalt der Nachricht.
     */
    @Override
    public void log(String text) {
        log(text, 1);
        /*
         * Diese Methode kann man nicht einfach weglassen, sie ist zwar in StructuredLogger
         * vorhanden, aber dann stimmt der Offset nicht. Warum eigentlich nicht?
         */
    }

    /**
     * Hier wird ein komplexerer Eintrag erzeugt mit sechs tabgetrennten Spalten.
     *     1: Datum
     *     2: Zeit
     *     3: Klasse des Aufrufers
     *     4: Methode des Aufrufers
     *     5: Zeilennummer
     *     6: Inhalt der Nachricht
     *
     * @param text
     *            Inhalt der Nachricht.
     * @param stacktraceOffset
     *            Offset im Stacktrace zum Ermitteln des richtigen Aufrufers.
     */
    @Override
    synchronized
    public void log(String text, int stacktraceOffset) {
        super.log(text, stacktraceOffset);
        LogEntry logEntry = getLastLogEntry();
        String finalMessage = logEntry.asLine();
        logInternal(finalMessage);
    }

    /** Schreibt eine Titelzeile in das Log. */
    void logTitle() {
        String message = Text.joinWithTabulator(columnHeaders);
        logInternal(message);
    }

    /**
     * Schreibt den gegebenen Text ergänzt um einen Zeilenumbruch in das Logfile. Der Offset im
     * Stacktrace zum Ermitteln des richtigen Aufrufers ist hier 0.
     *
     * @param text
     *            Zu loggender Text.
     */
    synchronized
    private void logInternal(String text) {
        checkNoLineBreaks(text);
        FileHelper.appendLineToFile(text, logFileName);
    }

    /**
     * Kann nicht passieren, weil wir oben in element.asLine() mit Text.removeLineBreaks() Umbrüche
     * entfernen, aber sicher ist sicher.
     */
    static void checkNoLineBreaks(String textOut) {
        if (Text.countLineBreaks(textOut) > 0) {
            throw new RuntimeException("Zeilenumbrüche in Lognachricht!");
        }
    }

    /**
     * Ändert den Pfad zu den Logdateien im laufenden Betrieb.
     *
     * @param newLogPath
     *            Neuer Pfad zu den Logdateien.
     * @throws IllegalArgumentException
     *             Falls der neue Pfad kein bestehendes Verzeichnis ist oder dort eine Logdatei mit
     *             dem auf den aktuellen Zeitpunkt angepassten Namen der Logdatei bereits
     *             existiert (was wegen des sekundengenauen Dateinamens eher unwahrscheinlich ist).
     */
    public void switchLogPath(String newLogPath) {
        checkIfPathExistsAndIsDirectory(newLogPath);

        String oldLogFileNameWithPath = getLogFileName();
        String oldLogFileName = FileHelper.getBareName(oldLogFileNameWithPath);
        String oldLogPath = FileHelper.getDirName(oldLogFileNameWithPath);

        String newLogFileName = Timestamp.switchDateTimePartInName(oldLogFileName);
        String newLogFile = FileHelper.concatPathes(newLogPath, newLogFileName);
        checkThatFileDoesNotExists(newLogFile);

        log("Wechsel des Pfades der Logdateien von " + oldLogPath + " nach " + newLogPath + ".", 0);
        log("Alte Logdatei: " + oldLogFileNameWithPath, 0);
        log("Neue Logdatei: " + newLogFile, 0);

        logFileName = newLogFile;

        log("Im laufenden Programm wurde der Pfad der Logdateien von " + oldLogPath + " auf "
                + newLogPath + " geändert.", 0);
        log("Alte Logdatei: " + oldLogFileNameWithPath, 0);
        log("Neue Logdatei: " + getLogFileName(), 0);
    }

    /**
     * Prüft, ob der angegeben Pfad existiert und ein Verzeichnis ist.
     *
     * @param path
     *            Pfad zur Logdatei.
     * @throws IllegalArgumentException
     *             Falls der Pfad kein bestehendes Verzeichnis ist.
     */
    static void checkIfPathExistsAndIsDirectory(String path) {
        if (FileHelper.isNotADirectory(path)) {
            throw new IllegalArgumentException("Pfad '" + path + "' nicht gefunden!");
        }
    }

    /**
     * Prüft auf Nicht-Existenz der angegebenen Datei.
     *
     * @param newLogFile
     *            Name der Datei, die noch nicht existieren darf.
     * @throws IllegalArgumentException
     *             Falls im neuen Pfad bereits eine Logdatei mit dem auf den aktuellen Zeitpunkt
     *             angepassten Namen der Logdatei existiert (was wegen des sekundengenauen
     *             Dateinamens eher unwahrscheinlich ist).
     */
    private static void checkThatFileDoesNotExists(String newLogFile) {
        if (FileHelper.exists(newLogFile)) {
            throw new IllegalArgumentException("Datei '" + newLogFile + "' existiert bereits!");
        }
    }

    /** Getter für den Namen des Logfiles. */
    @Override
    public String getLogFileName() {
        return logFileName;
    }

    /** Gibt den Pfad zurück, in dem die Logdatei liegt. */
    @Override
    public String getLogPath() {
        return FileHelper.getDirName(logFileName);
    }

    /**
     * Gibt die zuletzt geschriebene Zeilennummer (oder anders gesagt die Anzahl an Zeilen im
     * Logger) zurück.
     *
     * @return Zeilennummer der zuletzt geschriebenen Zeile.
     */
    @Override
    public int getLastWrittenLineNumber() {
        return super.getLastWrittenLineNumber() + 1; // + 1 für die Titelzeile.
    }

    /** Gibt eine Stringrepresentation für Debuggingzwecke zurück. */
    @Override
    public String toString() {
        return "FileLogger [logFileName=" + logFileName + "]";
    }

    /**
     * Erzeugt einen FileLogger mit einem leeren Logfile und entfernt gleich passend alte
     * Logdateien.
     *
     * @param begin
     *            Anfang des Logdateinamen. Falls dieser nicht auf Unterstrich endet, wird einer
     *            angehängt.
     * @param end
     *            Ende des Logdateinamen. Falls dieser nicht mit einem Unterstrich oder Punkt
     *            beginnt, wird ein Unterstrich vorangestellt.
     * @param path
     *            Pfad zur Logdatei.
     * @return Logger-Objekt.
     * @throws IllegalArgumentException
     *             Falls der Pfad kein bestehendes Verzeichnis ist.
     */
    public static FileLogger create(String begin, String end, String path) {
        FileHelper.createDirectoryWithMissingSubdirectoriesIfNotExists(path);
        checkIfPathExistsAndIsDirectory(path);

        FileLogger logger = new FileLogger(createLogFileName(begin, end, path));
        logger.logTitle();
        logger.log("Gestartet");

        LogEraser eraser = new LogEraser(path, begin, end, logger);
        eraser.erase();

        return logger;
    }

    /**
     * Erzeugt den Dateinamen für den FileLogger mit einem leeren Logfile und entfernt gleich
     * passend alte Logdateien.
     *
     * @param begin
     *            Anfang des Logdateinamen. Falls dieser nicht auf Unterstrich endet, wird einer
     *            angehängt.
     * @param end
     *            Ende des Logdateinamen. Falls dieser nicht auf Unterstrich oder Punkt endet, wird
     *            einer Unterstrich angehängt.
     * @param path
     *            Pfad zur Logdatei.
     * @return Dateiname für den FileLogger
     */
    static String createLogFileName(String begin, String end, String path) {
        String fullBeginn = begin.endsWith("_") ? begin : begin + "_";
        String fullEnd = end.startsWith("_") || end.startsWith(".")? end :  "_" + end;
        String pureFilename = fullBeginn + Timestamp.fullTimestamp() + fullEnd;
        String filename = FileHelper.concatPathes(path, pureFilename);

        return filename;
    }

}
