/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#ifndef CAMITKLOGGER_H
#define CAMITKLOGGER_H

// -- Core stuff
#include "CamiTKAPI.h"
#include "InterfaceLogger.h"

// -- Qt stuff
#include <QDir>
#include <QDateTime>
class QFile;
class QTextStream;

namespace camitk {

/**
 * @ingroup group_sdk_libraries_core_utils
 *
 * @brief This is the default logger for CamiTK.
 *
 * This class implements InterfaceLogger with the following default parameters:
 * - the verbosity log level is WARNING
 * - the message box level is NONE (there is no dialog whatever the level of the log message)
 * - the log messages are written to the standard output
 * - the log messages are NOT written to a log file
 * - the debug information are NOT shown
 * - the timestamp is shown
 *
 * \note About the log file name
 * The log file name is unique for a given instance of CamiTKLogger as it is based directly on the date/time of instantiation unless
 * the log file directory is modified during the life of the instance using setLogFileDirectory(). In this case a new date/time is
 * generated inside setLogFileDirectory().
 *
 * This way if the application or user decides to temporarily stop writing to the log file, all messages will be in the same file, even
 * the one logged after the writing to the log file is resumed.
 *
 * The log file name is "log-yyyy-MM-ddTHH-mm-ss.camitklog".
 * The date/time format is based on ISO 8601 (as given by Qt::ISODate) with the difference that all ":" are replaced by "-", as
 * ":" in file name does not always agree with Windows.
 * The date/time It is based on the instantiation time (or the time the directory name was changed if  is called after the instantiation)
 *
 * \note About writing log to file
 * The default directory for the log file is the subdirectory "CamiTK" in the system temporary directory.
 * The filename is automatically determined by the logger.
 * The log file is opened in append mode. This ways, temporarily stop writing to the log file is possible without losing any data.
 *
 * The log file is permanently kept open for performance reason, and is flushed everytime a log message is appended for safety.
 * This should cover all normal case (even application crash).
 * However, this does not prevent an external process (or user) to truncate or remove the current file. The former will result
 * in missing log message, the latter will result in a complete loss of all previous message and of all newer message.
 *
 * \note About message box level
 * When a log message is above the message box level, it will display a modal QMessageBox that depends on the log message level:
 * - TRACE and INFO messages are displayed in QMessageBox::Information dialogs
 * - WARNING messages are displayed in QMessageBox::Warning dialogs
 * - ERROR messages are displayed in QMessageBox::Critical dialogs
 * The main text of the message box contains only the user message but a detailed text is available when the "Show details..."
 * button is pressed. This way the main message to the user is not "polluted" by log timestamp and debug details, but they are both
 * still available to the user (and can be copied to the clipboard for bug report or debugging session).
 * To avoid any QMessageBox (e.g. in tests), use setMessageBoxLevel(NONE).
 *
 * \note About logger self message
 * CamiTKLogger also log some messages:
 * - during instantiation, when everything is ready, a TRACE message is logged
 * - when the configuration change (e.g., the log has changed) or the log file is opened/closed, TRACE messages are logged
 * - WARNING messages are logged when something strange occurs on the QTextStream used for logging in file
 * - ERROR messages are logged when the log file directory cannot be created, when the log file cannot be opened or when the
 *   QTextStream changed to WriteFailed status.
 *
 * \note About debugging information
 * Allows the logger to add debug information to the log message.
 * Debug information contains the filename, line number and method names where the log was called.
 * Note that the exact content of the method name debug information depends on the compiler.
 *
 * \note About the log message
 * The log macro message is log using the following format:
 * yyyy-MM-dd HH:mm:ss.zzz [LEVEL] [DEBUG_INFO] message
 * ^^^^^^^^^^^^^^^^^^^^^^^         ^^^^^^^^^^^^
 * ^if time stamp shown            ^ if debug info shown
 *
 * Moreover, if the log macro is used inside a CamiTK inherited class (e.g., Action, Component,...)
 * then message is prepend with a specific text (see below)
 *
 * \note About log macro used inside a CamiTK inherited class
 * If the log macro is used in a CamiTK inherited class, then it will automatically be prepend with
 * a specific text.
 *
 * For example:
 * - if a log macro is called from a camitk::Action or any class inheriting from camitk::Action
 *   then the text "Action 'MyActionName' - " is automatically added at the beginning of the log message
 * - if a log macro is called from a camitk::Component or any subclass, then the text
 *   "Component 'MyComponentName' - " is automatically added at the beginning of the log message
 * - ...
 *
 * Action, Component, Viewer, Application, MainWindow, ComponentExtension and ActionExtension are
 * currently supported.
 */

class CAMITK_API CamiTKLogger : public InterfaceLogger {

public :
    /// Default constructor
    CamiTKLogger();

    /// Destructor
    ~CamiTKLogger() override;

    /** Get Current verbosity level of the log:
     */
    LogLevel getLogLevel() override;

    /** Sets Current verbosity level of the log:
     */
    void setLogLevel(LogLevel level) override;

    /** Allows the logger to write on std::cout
     */
    void setLogToStandardOutput(bool writeToStdOut) override;

    /** check if the logger is currently writing on standard output (std::cout)
     *  @return true if the logger is currently writing on std output, false otherwise.
     */
    bool getLogToStandardOutput() override;

    /** Allows the logger to write to the log file.
     *  By default, the directory to write to is the subdirectory "CamiTK" in the system temporary directory.
     *  The file name itself will be determined by the logger.
     *
     *  @return true if the logger is going to log into the file (if the parameter is true, it also means that there
     *          was no error in opening the logfile for writing in the log file directory)
     */
    bool setLogToFile(bool writeToFile) override;

    /** Set the specific directory to write to (default is the subdirectory "CamiTK" in the system temporary directory).
     *  If the directory does not exists it is going to be created.
     *
     *  If the log directory is different than the existing one:
     *  - if moveExistingLogFile is true (default), the current log file is moved to the new directory, new messages will
     *    be appened
     *  - if moveExistingLogFile is false, a new log file is generated, previous log file is kept untouched in the previous
     *    temporary directory
     *
     * @param directoryName the new directory to use for the log file
     * @param moveExistingLogFile if true, the existing log file is move to the new directory and use to append the log messages
     *        from now.
     *
     * @return if the logger is already logging to a file, returns true if the log file could have been created in the
     *         given directory, otherwise returns the log to file status.
     */
    bool setLogFileDirectory(QDir directoryName, bool moveExistingLogFile = true) override;

    /// check if the logger is currently writing on a file
    bool getLogToFile() override;

    /// @return the current file information about the current log file
    QFileInfo getLogFileInfo() override;

    /** Set the lowest log level that will open a modal message box for messages instead
     *  of simply (silently/non-interactively) writing on std output.
     *
     *  Default value is NONE (no messages are displayed in a QMessageBox).
     */
    void setMessageBoxLevel(LogLevel level) override;

    /// @return get the current lowest log level that will generate modal message box
    LogLevel getMessageBoxLevel() override;

    /** Allows the logger to add debug information to the log message.
     *  Debug information contains the filename, line number and method names where the log was called.
     *  Note that the exact content of the method name debug information depends on the compiler.
     */
    void setDebugInformation(bool) override;

    /// @return true if the logger is currently printing the debug information in the log message.
    bool getDebugInformation() override;

    /** By default a logger should always show the time-stamp in the form of "yyyy-MM-dd HH:mm:ss.zzz"
     *  In test environment, reproducible log message might be prefered. In this case the time
     *  stamp can be disabled.
     *  @param showTimeStamp if false the time stamp is not printed in log message.
     */
    void setTimeStampInformation(bool showTimeStamp) override;

    /// @return true if the logger is currently printing the time stamp information in the log messages.
    bool getTimeStampInformation() override;

    /** Log a message:
     *  If the logger is allowed to write on standard output, it will display the message (and timestamp) on the standard output
     *  If the logger is allowed to write on a file, it will also display the message in the log file.
     *
     *  The level parameter gives the current message level that has to be compared with the logger level:
     *  if this level is greater or equals to the current log level, the current message will be logged.
     *
     *  Example: if the logger level is at WARNING, only ERROR and WARNING level messages will be displayed.
     *
     *  If in a static method or a non QObject class, then call this method with an empty last parameter.
     */
    QString log(const QString msg, const LogLevel level, char const* fileName, char const* methodName, int lineNumber, const QObject* sender = nullptr) override final;


protected:
    /// Builds a log message of correct format
    virtual QString buildLogMessage(QString message, LogLevel level, char const* fileName, char const* methodName, int lineNumber, const QObject* sender = nullptr);

private:
    /// Returns CamiTK API description string if and only if sender derived from CamiTK Action
    QString getCamiTKAPIInformation(const QObject* sender);

    /// open the log file, @return true if and only if the log file can be open in write mode
    bool openLogFile(bool moveFile = false, QFileInfo fileToMove = QFileInfo());

    /// close the current log file and reset state
    void closeLogFile();

    /// current log level
    InterfaceLogger::LogLevel level;

    /// current level for message boxes
    InterfaceLogger::LogLevel messageBoxLevel;

    /// is the logger currently writing everything to the standard output
    bool logToStdOut;

    /// is the logger currently writing everything to a file
    bool logToFile;

    /// display debug information (file/class name, method name and line number)
    bool displayDebugInformation;

    /// display time stamp information (in the form "yyyy-MM-dd HH:mm:ss.zzz")
    bool displayTimeStampInformation;

    /// Current directory for the log file
    QDir logFileDirectory;

    /// Current log file (the stream is flushed log message by log message)
    QFile* logFile;

    /// Current stream to output to the log file
    QTextStream* logStream;

    /// instantiation time
    QDateTime logStartTime;

};

}

#endif // CAMITKLOGGER_H
