/**
 * @file ScopeLogger.h
 * @author Bartosz Janiak
 * 
 * @brief Contains the definition of ScopeLogger class and related macros.
 *
 * Copyright © 2009 NVIDIA Corporation. All rights reserved.
 *
 * NVIDIA Corporation and its licensors retain all intellectual property and proprietary
 * rights in and to this software, related documentation and any modifications thereto. Any
 * use, reproduction, disclosure or distribution of this software and related documentation
 * without express license agreement from NVIDIA Corporation is strictly prohibited.
 */

#pragma once

#include "LogLevel.h"
#include "CompositeLoggable.h"
#include "ChainLoggable.h"
#include "FormattedChainLoggable.h"
#include "Logger.h"
#include "TStd.h"

/**
 * @brief Makes a ScopeLogger with this name available to use in enclosing class or function.
 */
#define DEFINE_LOGGER(name)                                             \
class _ScopeLoggerWrapper {                                             \
public:                                                                 \
    static Nvidia::Logging::ScopeLogger& GetScopeLogger()               \
    {                                                                   \
        static Nvidia::Logging::ScopeLogger scopeLogger((name));        \
        return scopeLogger;                                             \
    }                                                                   \
}

/**
 * @brief Makes a ScopeLogger with this name available to use in enclosing class or function, with default LogLevel specified
 */
#define DEFINE_LOGGER_AT_LEVEL(name,level)                              \
class _ScopeLoggerWrapper {                                             \
public:                                                                 \
    static Nvidia::Logging::ScopeLogger& GetScopeLogger()               \
    {                                                                   \
        static Nvidia::Logging::ScopeLogger scopeLogger((name),(level));\
        return scopeLogger;                                             \
    }                                                                   \
}

/**
 * @brief Logs the passed message into the logging system via ScopeLogger defined in 
 * this class or function.
 *
 * @param logLevel LogLevel at which to log the message.
 * @param message List of loggable elements separated by "+" operator, or a single loggable element.
 * "Loggable elements" in this context are understood as:
 * - objects inheriting from Loggable abstract class,
 * - built-in C++ types,
 * - anything else which can be outputted to an ofstream,
 * - a vector of any of the above.
 */
#define LOG(logLevel, message)                                                         \
{                                                                                      \
    using namespace Nvidia::Logging;                                                   \
    if ( !_ScopeLoggerWrapper::GetScopeLogger().IsFiltered((LogLevel_ ## logLevel)) )  \
    {                                                                                  \
            _ScopeLoggerWrapper::GetScopeLogger().Log((LogLevel_ ## logLevel),         \
            MakeChainLoggable(MakeEmptyChain() + message),                             \
            _T(__FUNCTION__), __LINE__, _T(__FILE__));                                 \
    }                                                                                  \
}

/**
 * @brief Variation of LOG() macro allowing to specify custom formatting for the message.
 *
 * @param logLevel LogLevel at which to log the message.
 * @param format Format string conforming to the boost::format specification
 * (which is compatible with printf format specification, see
 * http://www.boost.org/doc/libs/1_39_0/libs/format/doc/format.html#printf_directives 
 * for details).
 * @param message List of loggable elements separated by "+" operator, or a single loggable element.
 * "Loggable elements" in this context are understood as:
 * - objects inheriting from Loggable abstract class,
 * - built-in C++ types,
 * - anything else which can be outputted to an ofstream,
 * - a vector of any of the above.
 */
#define LOGF(logLevel, format, message)                                                \
{                                                                                      \
    using namespace Nvidia::Logging;                                                   \
    if ( !_ScopeLoggerWrapper::GetScopeLogger().IsFiltered((LogLevel_ ## logLevel)) )  \
    {                                                                                  \
        _ScopeLoggerWrapper::GetScopeLogger().Log((LogLevel_ ## logLevel),             \
            MakeFormattedChainLoggable(MakeEmptyChain() + message, format),            \
            _T(__FUNCTION__), __LINE__, _T(__FILE__));                                 \
    }                                                                                  \
}

/**
 * @brief Variation of LOG() macro allowing to specify function, line and file.
 *
 * @param logLevel LogLevel at which to log the message.
 * @param message List of loggable elements separated by "+" operator, or a single loggable element.
 * "Loggable elements" in this context are understood as:
 * - objects inheriting from Loggable abstract class,
 * - built-in C++ types,
 * - anything else which can be outputted to an ofstream,
 * - a vector of any of the above.
 * @param func  Function name as std::tstring
 * @param line  Line number as int
 * @param file  File name as std::tstring
 */
#define LOGFLF(logLevel, message, func, line, file)                                    \
{                                                                                      \
    using namespace Nvidia::Logging;                                                   \
    if ( !_ScopeLoggerWrapper::GetScopeLogger().IsFiltered((LogLevel_ ## logLevel)) )  \
    {                                                                                  \
            _ScopeLoggerWrapper::GetScopeLogger().Log((LogLevel_ ## logLevel),         \
            MakeChainLoggable(MakeEmptyChain() + message),                             \
            func, line, file);                                                         \
    }                                                                                  \
}

/*!
    DEFINE_SCOPE_LOGGER defines an RAII class to enable logging at the exit point from a scope
    Can only be defined when logging is enabled with DEFINE_LOGGER();
    Use SCOPE_LOG() for the scope you want to log.
*/
#define DEFINE_SCOPE_LOGGER                                                                     \
    class _ScopeLog                                                                             \
    {                                                                                           \
        std::tstring m_message;                                                                 \
        std::tstring m_func;                                                                    \
        std::tstring m_file;                                                                    \
        int m_line;                                                                             \
        DWORD m_startTime;                                                                      \
    public:                                                                                     \
        _ScopeLog(const std::tstring &message, const std::tstring &func, int line,              \
                    const std::tstring &file) : m_message(message), m_func(func), m_line(line), \
                    m_file(file), m_startTime(::GetTickCount())                                 \
        {                                                                                       \
            LOGFLF(Info, "Entering Checkpoint:" + m_message, func, line, file);                 \
        }                                                                                       \
        ~_ScopeLog()                                                                            \
        {                                                                                       \
            DWORD elapsed = ::GetTickCount() - m_startTime;                                     \
            LOGFLF(Info, "Exiting Checkpoint:" + m_message + "(" + elapsed + "ms )", m_func,    \
                m_line, m_file);                                                                \
        }                                                                                       \
    };

/*!
    SCOPE_LOG logs a user-defined message at construction and destruction (at scope exit)
    Depends on DEFINE_SCOPE_LOGGER() having been defined previously.
*/
#define SCOPE_LOG(message) _ScopeLog MAKE_UNIQUE(_scopeLog_)(                                           \
    Nvidia::Logging::MakeChainLoggable(Nvidia::Logging::MakeEmptyChain() + message).GetStringValue(),   \
    _T(__FUNCTION__), __LINE__, _T(__FILE__));

/*!
    Enables write-through for all log managers that support buffering.
    Currently, this is only the FileLogManager.
    Useful when you don't want to lose logs due to catastrophic happenings
    such as a BSOD.
*/
#define ENABLE_LOG_WRITETHROUGH()                                       \
class _EnableLogWriteThrough                                            \
{                                                                       \
public:                                                                 \
    _EnableLogWriteThrough()                                            \
    {                                                                   \
        Nvidia::Logging::Logger::GetLogger().EnableWriteThrough();      \
    }                                                                   \
    ~_EnableLogWriteThrough()                                           \
    {                                                                   \
        Nvidia::Logging::Logger::GetLogger().EnableWriteThrough(false); \
    }                                                                   \
}_enableLogWriteThrough;

namespace Nvidia {
namespace Logging {

/**
 * @brief Helper class for the user of the logging system. Allows to define the hierarchy of the project.
 * 
 * This hierarchy is later used to filter and decorate the logging output. User should make one
 * static instance of this object available in each class or function he wants to use logging in.
 * This should be done using DEFINE_LOGGER() macro.
 */
class ScopeLogger
{
private:
    /// @brief Name of this ScopeLogger.
    std::tstring m_name;

    /**
     * @brief Maximum LogLevel at which log messages should be passed to static Logger object.
     * 
     * Each message with LogLevel above this value, when passed to this ScopeLogger via LOG() or LOGF() macro, will be filtered (ignored).
     */
    LogLevel m_maximumLogLevel;

public:
    /**
     * @brief Default constructor.
     *
     * Sets the m_name attribute and calls Logger::GetMaximumLogLevel(m_name) to determine m_maximumLogLevel for this ScopeLogger.
     * @param name String representing the place of the enclosing class or function in the project hierarchy. It should be in
     * the following form: "SystemName.SubSystemName.SubSubSystemName.(...).ElementName". User can assign arbitrary meanings
     * to the names on each level, as they are used only as identificators for filtering and decorating purporses.
     */
    ScopeLogger(const std::tstring& name);

    /**
     * @brief Constructor with LogLevel
     *
     * Sets the m_name attribute and m_maximumLogLevel for this ScopeLogger.
     * @param name String representing the place of the enclosing class or function in the project hierarchy. It should be in
     * the following form: "SystemName.SubSystemName.SubSubSystemName.(...).ElementName". User can assign arbitrary meanings
     * to the names on each level, as they are used only as identificators for filtering and decorating purporses.
     * This c-tor is useful for logging setup with programmatic control (as opposed to the registry-based).
     */
    ScopeLogger(const std::tstring& name, LogLevel startLevel);

    /**
     * @brief Passes the log message to the logging system via Logger::Log() method.
     *
     * This method shouldn't be used directly by the user, please use LOG() or LOGF() macros instead.
     */
    void Log(LogLevel logLevel, const CompositeLoggable& message, std::tstring functionName, int lineNumber, std::tstring fileName);
    
    /// @brief Helper method to determine whether message with the particular logLevel should be filtered or not.
    bool IsFiltered(LogLevel logLevel);

    /// @brief Helper method to control filtering level programmatically. Changes m_maximumLogLevel.
    void SetFilter(LogLevel logLevel);

    /// @brief Helper method to retrieve filtering level programmatically. Returns m_maximumLogLevel. All messages with *higher* log level will be discarded
    LogLevel GetFilter() const;
};

inline ScopeLogger::ScopeLogger(const std::tstring& name) :
    m_name(name),
    m_maximumLogLevel(Logger::GetLogger().GetMaximumLogLevel(name)) 
{
}

inline ScopeLogger::ScopeLogger(const std::tstring& name, LogLevel startLevel) :
    m_name(name),
    m_maximumLogLevel(startLevel) 
{
}

inline void ScopeLogger::Log(LogLevel logLevel, const CompositeLoggable& message, std::tstring functionName, int lineNumber, std::tstring fileName)
{
    LogInfo logInfo(logLevel, m_name, functionName, fileName, lineNumber);
    Logger::GetLogger().Log(logInfo, message);
}

/// @brief check whether log level is greater than m_maximumLogLevel AND logger wasn't asked to be quiet. 
/// Need to ensure that for m_maximumLogLevel set to the LogLevel_Quiet we won't print anything in LOG(LogLevel_Quiet, ... );
inline bool ScopeLogger::IsFiltered(LogLevel logLevel)
{
    return (logLevel>m_maximumLogLevel && logLevel!=LogLevel_Quiet);
}

inline void ScopeLogger::SetFilter(LogLevel logLevel)
{
    m_maximumLogLevel = logLevel;
}

inline LogLevel ScopeLogger::GetFilter() const
{
    return m_maximumLogLevel;
}

}
}