/*****************************************************************************
 * $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 LOG_H
#define LOG_H

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

namespace camitk {

class InterfaceLogger;

/**
 * @ingroup group_sdk_libraries_core_utils
 *
 * @brief
 * This class is a log utility.
 *
 * The aims of the log in CamiTK are:
 *    - to simplify and give a flexible way to log message in any application
 *    - to be minimally invasive in the source code
 *    - to help bug tracking and report
 *    - to help new development by providing automatically generated debugging information
 *    - to suppress any need to use qDebug macro or std::cout / std::cerr "printf"
 *
 * There are four types of message (@see InterfaceLogger):
 * - ERROR    error messages should be used only when a problem occurs that
 *            - cannot be taken care of by the source code
 *            - imply a preventive crash action
 *            - might result in unfinished process / undefined behaviour
 * - WARNING  warning messages should be used when something went wrong but is not critical to the application state
 *            but might require some corrective or alternative action by the user
 * - INFO:    information messages should be used to show normal processing, stage or intermediate step
 *            or display some useful data values
 * - TRACE:   trace messages should be used to trace anything useful for debugging or log post-processing
 *
 * These messages are written using the corresponding CAMITK log macros:
 * CAMITK_ERROR, CAMITK_WARNING, CAMITK_INFO and CAMITK_TRACE.
 *
 * For static methods and non QObject class, use the ALT CAMITK log macros:
 * CAMITK_ERROR_ALT, CAMITK_WARNING_ALT, CAMITK_INFO_ALT and CAMITK_TRACE_ALT.
 *
 * There are also specific IF CAMITK macros use to log only if a given boolean expression is true:
 * CAMITK_ERROR_IF, CAMITK_WARNING_IF, CAMITK_INFO_IF and CAMITK_TRACE_IF.
 *
 * And their corresponding counterparts for static methods or non QObject class:
 * CAMITK_ERROR_IF_ALT, CAMITK_WARNING_IF_ALT, CAMITK_INFO_IF_ALT and CAMITK_TRACE_IF_ALT.
 *
 * Depending on the current application logger log level, not all the messages are displayed.
 *
 * A default logger is instantiated for all application using the CamiTKLogger class
 * @see CamiTKLogger
 *
 * The current log level can be set to
 * - NONE     in this case, no message are logged. Logger is completely silent
 * - ERROR    only error messages are logged
 * - WARNING  only WARNING and ERROR messages are logged
 * - INFO     INFO, WARNING and ERROR messages are logged
 * - TRACE    all types of messages are logged
 *
 * A message box level is also available: any message equals or above the message box level will be shown in a modal
 * QMessageBox dialog.
 *
 * Debug information are also available: it will automatically print the method name, class or file name and line where
 * the log message originated. This is very useful during debug session.
 *
 * Debug information are optional.
 *
 * To log message, use the macro defined in this class header.
 *
 * The log message time stamp can be turned on and off as well.
 *
 * \note How to use the macro in the source:
 * - Make sure that the file Log.h is correctly included in the current source code
 * - Determine the level of your log message: ERROR, WARNING, INFO and TRACE
 * - If the log message should be written only if a corresponding boolean expression is true, use the _IF version
 * - If the log message is in a static method or a non QObject derived class (most CamiTK class derived from QObject), use the ALT version
 * - Start the log message with an uppercase
 * - Do not add the file, class, or method name, this is taken care of automatically by the debug information
 * - The log message should be a QString:
 *     - use QString::number(..) to convert primitive numeric type to QString
 *     - use QString::fromStdString(..) to convert std::string
 *
 * \note completely disabling log
 * To void any log message, add the following line at the beginning of the CMakeLists.txt corresponding to the project you want
 * to silence:
 * \code
 add_definitions(-DCAMITK_DISABLE_LOG)
 # you can also also add "-DCAMITK_DISABLE_LOG" to the camitk_extension macro DEFINES option
 * \endcode
 * This will transform all CAMITK log macro written in your code to null function.
 * Note that it will not void the camitkcore log entry (to have absolutely no log at all, you need to compile CamiTK SDK
 * with "-DCAMITK_DISABLE_LOG"
 *
 * Some simple examples:
 * \code
        unsigned int value = 4;

        CAMITK_INFO(tr("Current value: %1").arg(QString::number(value)))  // tr(..) is for translation of string to other language
        CAMITK_TRACE_IF((value<10), tr("Current value is a digit"))
   \endcode
 *
 * \note if the source code is compiled with CAMITK_DISABLE_LOG defined, then any log macro results into a NOOP.
 * This does not mean that there can't be any logger.
 * In this case to add debug information you will explicitely have to call Log::getLogger()->log(...)
 *
 * \note
 * The default logger can be used/started at any time, even without any camitk application instance.
 * In order to define the default log levels and parameter, you need to set the values before the camitk::Application
 * is instantiated.
 * If the default settings are modified by the user, they will automatically replace the default log values.
 * Here is an example:
 * \code
        // in main.cpp
        ...

        // instantiate default CamiTKLogger and set default levels
        // This level are used for the first run, and may be later overriden by the user if saved in the user settings
        camitk::Log::getLogger()->setLogLevel(InterfaceLogger::INFO);
        camitk::Log::getLogger()->setMessageBoxLevel(InterfaceLogger::WARNING);
        // force to log all messages to a file as well as stdout
        camitk::Log::getLogger()->setLogToFile(true);

        // Say hello
        CAMITK_INFO_ALT(tr("Logger started, log is written to file %1").arg(camitk::Log::getLogger()->getLogFileInfo().fileName()))
        ...

        // init a camitk application context
        camitk::Application a("camitk-myapp", argc, argv, true, true);
   \endcode
 *
 * \note In order to force log settings and ignore user settings, you need modify the Application property object directly.
 * For a CamiTK application, you can do that in the constructor of the camitk::Application inherited class (as the first
 * lines for instance, if you don't want to miss anything):
 * \code
    MyCamiTKApplication::MyCamiTKApplication(int& argc, char** argv, QString inputFileName, QString outputDirectory) : Application("camitk-myapplication", argc, argv) {
        // modify default log options for this application
        setProperty("Log to File", true);
        setProperty("Logger Level",InterfaceLogger::TRACE);
        ...
   \endcode
 * Or you can do that from outside (e.g. in the main.cpp), using the application property object:
 * \code
#include <PropertyObject.h>
...
main(..) {
        // create a camitk application
        MyCamiTKApplication myapp(argc, argv, ...);

        // Force the log parameters and ignore the user saved settings, by directly changing the application properties
        // so that it is recorded at every launch (overriding any previous user choice)
        myapp.getPropertyObject()->setProperty("Logger Level",InterfaceLogger::INFO);
        myapp.getPropertyObject()->setProperty("Message Box Level",InterfaceLogger::WARNING);
        ...
   \endcode
 *
 * Available application property names linked to log settings:
 *     - "Logger Level"
 *     - "Message Box Level"
 *     - "Log to Standard Output"
 *     - "Log to File"
 *     - "Display Debug Information to Log Message"
 *     - "Display Time Stamp Information to Log Message"
 *
 * \note for MSVC users
 * A (legacy) MS Windows header defines ERROR as a preprocessor macro. This header is unfortunately sometimes
 * included in low-level library code. This will clash with the CAMITK_ERROR macro.
 * Hints: if you use the CAMITK_ERROR or CAMITK_ERROR_ALT macro in your code and get these compilation errors:
 * - error C2589: 'constant': illegal token on right side of '::'
 * - error C2059: syntax error: '::'
 * Move your \#include&gt;Log.h> line at the end of your \#include lines.
 * Therefore \#include &gtLog.h> should be the last \#include directive.
 *
 */
class CAMITK_API Log {
public:
    /** set the application logger and delete the previous logger
     *  Call this method transfers the logger instance ownership to class Log.
     */
    static void setLogger(InterfaceLogger* logger);

    /// get the current application logger
    static InterfaceLogger* getLogger();

    /// get the enum value as a text
    static QString getLevelAsString(InterfaceLogger::LogLevel level);

    /// get the enum value from the text
    static InterfaceLogger::LogLevel getLevelFromString(QString levelString);

private:
    static InterfaceLogger* applicationLogger;
};

/// Helper macro that produce a QString representing a pointer
#define CAMITK_PRINT_POINTER(PTR)    (QString(#PTR) + QString("->[0x%1]").arg((quintptr)PTR, QT_POINTER_SIZE * 2, 16, QChar('0')))

/// Generic log macro (it is NOT recommended to use it directly...)
#if !defined(CAMITK_DISABLE_LOG)
#define CAMITK_LOG(LEVEL,MSG,SENDER) camitk::Log::getLogger()->log(MSG, LEVEL, __FILE__, Q_FUNC_INFO, __LINE__, SENDER);
#define CAMITK_LOG_ALT(LEVEL,MSG)    camitk::Log::getLogger()->log(MSG, LEVEL, __FILE__, __func__, __LINE__);
#else
#define CAMITK_LOG(LEVEL,MSG,SENDER)
#define CAMITK_LOG_ALT(LEVEL,MSG)
#endif

/// Log for trace verbosity (the most verbose one, should be used for bug report)
/// The msg will appear only if the user asked for TRACE verbosity
#define CAMITK_TRACE(MSG)          CAMITK_LOG(camitk::InterfaceLogger::TRACE, MSG, this)
#define CAMITK_TRACE_ALT(MSG)      CAMITK_LOG_ALT(camitk::InterfaceLogger::TRACE, MSG)

/// Log for info verbosity (the second most verbose one)
/// The msg will appear only if the user asked for INFO verbosity
#define CAMITK_INFO(MSG)           CAMITK_LOG(camitk::InterfaceLogger::INFO, MSG, this)
#define CAMITK_INFO_ALT(MSG)       CAMITK_LOG_ALT(camitk::InterfaceLogger::INFO, MSG)

/// Log for warning verbosity (the most common one)
/// Will appear by default
#define CAMITK_WARNING(MSG)        CAMITK_LOG(camitk::InterfaceLogger::WARNING, MSG, this)
#define CAMITK_WARNING_ALT(MSG)    CAMITK_LOG_ALT(camitk::InterfaceLogger::WARNING, MSG)

/// Log for error verbosity (the minimum verbosity)
/// Will always appear
#ifdef ERROR
#define CAMITK_WINDOWS_SYSTEM_ERROR_SAFEGUARD ERROR
#undef ERROR
#endif

#define CAMITK_ERROR(MSG)          CAMITK_LOG(camitk::InterfaceLogger::ERROR, MSG, this)
#define CAMITK_ERROR_ALT(MSG)      CAMITK_LOG_ALT(camitk::InterfaceLogger::ERROR, MSG)

#ifdef CAMITK_WINDOWS_SYSTEM_ERROR_SAFEGUARD
#define ERROR CAMITK_WINDOWS_SYSTEM_ERROR_SAFEGUARD
#endif

/// The LOG_VERBOSITY_IF Macros are triggered only if the given condition is true.
/// This allow to log only if something unexpected happens
#define CAMITK_TRACE_IF(COND, MSG)          \
{                                           \
    if (((COND))) {                         \
        CAMITK_TRACE(MSG)                   \
    }                                       \
}

#define CAMITK_TRACE_IF_ALT(COND, MSG)    \
{                                           \
    if (((COND))) {                         \
        CAMITK_TRACE_ALT(MSG)             \
    }                                       \
}

#define CAMITK_INFO_IF(COND, MSG)           \
{                                           \
    if (((COND))) {                         \
        CAMITK_INFO(MSG)                    \
    }                                       \
}

#define CAMITK_INFO_IF_ALT(COND, MSG)     \
{                                           \
    if (((COND))) {                         \
        CAMITK_INFO_ALT(MSG)              \
    }                                       \
}

#define CAMITK_WARNING_IF(COND, MSG)        \
{                                           \
    if (((COND))) {                         \
        CAMITK_WARNING(MSG)                 \
    }                                       \
}

#define CAMITK_WARNING_IF_ALT(COND, MSG)  \
{                                           \
    if (((COND))) {                         \
        CAMITK_WARNING_ALT(MSG)           \
    }                                       \
}

#define CAMITK_ERROR_IF(COND, MSG)          \
{                                           \
    if (((COND))) {                         \
        CAMITK_ERROR(MSG)                   \
    }                                       \
}

#define CAMITK_ERROR_IF_ALT(COND, MSG)    \
{                                           \
    if (((COND))) {                         \
        CAMITK_ERROR_ALT(MSG)             \
    }                                       \
}


}

#endif // LOG_H
