/**
 * @file Logger.h
 * @author Bartosz Janiak
 * 
 * @brief Contains the definition of Logger class and related functions.
 *
 * 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 "LogInfo.h"
#include "ILogPrinter.h"
#include "LogLevel.h"
#include "LogFilter.h"
#include "ILogManager.h"
#include "RegistryKey.h"
#include "SimpleTextLogPrinter.h"
#include "DebugOutputLogManager.h"
#include "XMLLogPrinter.h"
#include "FileLogManager.h"
#include "StreamManager.h"

#include <vector>
#include <set>
#include <map>
#include <memory>
#include <boost/any.hpp>

#include <algorithm>
#include <boost/foreach.hpp>
#include <boost/algorithm/string/erase.hpp>
#pragma warning (push)
#pragma warning (disable:6328)
#include "boost/date_time/posix_time/posix_time.hpp"
#pragma warning (pop)
#include <exception>

#define LOGGING_KEYNAME_DEFAULT_PROCESS             _T("DefaultProcess")
#define LOGGING_KEYNAME_SOFTWARE                    _T("Software")
#define LOGGING_KEYNAME_NVIDIA_CORPORATION          _T("NVIDIA Corporation")
#define LOGGING_KEYNAME_LOGGING                     _T("Logging")
#define LOGGING_KEYNAME_PROCESSES                   _T("Processes")
#define LOGGING_KEYNAME_DEFINITIONS                 _T("Definitions")
#define LOGGING_KEYNAME_LOGCHANNELS                 _T("LogChannels")
#define LOGGING_KEYNAME_LOGPRINTERS                 _T("LogPrinters")
#define LOGGING_KEYNAME_LOGMANAGERS                 _T("LogManagers")
#define LOGGING_KEYNAME_LOGFILTERS                  _T("LogFilters")
#define LOGGING_KEYNAME_LOGPRINTER                  _T("LogPrinter")
#define LOGGING_KEYNAME_LOGFILTER                   _T("LogFilter")
#define LOGGING_KEYNAME_CLASSNAME                   _T("ClassName")

#define LOGGING_SYSTEM_INFO(message)                                                                            \
{                                                                                                               \
    Log(LogInfo(LogLevel_Info, std::tstring(_T("system")), _T(__FUNCTION__), _T(__FILE__), __LINE__),           \
    MakeChainLoggable(MakeEmptyChain() + boost::posix_time::second_clock::local_time() + _T(": ") + message));  \
}

namespace Nvidia {
namespace Logging {

namespace Test {
    class LoggerTest;
    class LoggerConstructorTest;
}

/**
 * @brief Central object of the logging system.
 *
 * Logger manages all ILogPrinter%s, LogFilter%s and ILogManager%s present in the 
 * system. It also acts as a collector and distributor of the log messages - messages
 * from all ScopeLogger%s are passed to Logger via Log() function, and all LogPrinter%s
 * retrieve the filtered messages from Logger.
 *
 * There should be only one instance of the Logger object per process. This instance
 * can be accessed using _GetLogger() function.
 */
class Logger
{
    friend class Nvidia::Logging::Test::LoggerTest;
    friend class Nvidia::Logging::Test::LoggerConstructorTest;

private:
    /**
     * @brief Vector containing LogFilter%s and ILogPrinter%s present in the system.
     *
     * The pairing between LogFilter and ILogPrinter is determined by the configuration
     * in the registry - for each LogChannel defined in the configuration for this process
     * there will be one pair of LogFilter and ILogPrinter from that LogChannel in this Vector.
     *
     * The definitions for LogFilter%s and ILogPrinter%s in the registry configuration are
     * treated only as instructions on how to create particular LogFilter or ILogPrinter. In practice
     * this means that if a LogFilter or ILogPrinter definition is referenced twice from different
     * LogChannel definitions, then two instances of LogFilter or ILogPrinter will be created.
     * This is in contrast to the ILogManager%s management, where only one ILogManager is created for
     * each definition in configuration.
     */
    std::vector<std::pair<LogFilter*, ILogPrinter*> > m_filtersAndPrinters;

    /**
     * @brief Map containing ILogManager&s present in the system, keyed by their names.
     * 
     * Only ILogManager%s referenced from LogChannels attached to this process are created
     * and stored in this map.
     */
    std::map<std::tstring, ILogManager*> m_managers;

    /**
     * @brief string containing results of the Logger startup. 
     * 
     * buffer messages from Logger c-tors, so the instantiating code could control spew 
     * from Logger initialization and keep it quiet if necessary.
     *
     */
    std::tstring m_started;

    /// @brief default logging level
    LogLevel     m_defaultLevel;

public:
    /**
    * @brief Default Constructor.
    *
    * Current implementation tries to configure the logging system using configuration stored in registry,
    * trying the keys in following order:
    * - HKEY_CURRENT_USER/Software/NVIDIA Corporation/Logging/(this process name)/
    * - HKEY_LOCAL_MACHINE/Software/NVIDIA Corporation/Logging/(this process name)/
    * - HKEY_CURRENT_USER/Software/NVIDIA Corporation/Logging/DefaultProcess/
    * - HKEY_LOCAL_MACHINE/Software/NVIDIA Corporation/Logging/DefaultProcess/
    * 
    * If all of the above fail (due to either unavailability of the key or its invalid structure),
    * then "hardcoded" default configuration is used.
    */
    Logger();

    /**
    * @brief Fine-tune Constructor. Won't initialize logging by default. 
    *
    * If all of the above fail, caller has to call either ConfigureDefault(), ConfigureTee() or manually add printers and filters
    * @param startLevel start-up logging level
    */
    Logger(LogLevel startLevel);

    /// @brief Destructor. Deletes all LogFilter%s, ILogPrinter%s and ILogManager%s created during construction.
    ~Logger();
  
    /**
    * @brief Logs the message into the logging system.
    *
    * This method passes the LogInfo instance to each of the LogFilters registered in the m_filtersAndPrinters container,
    * via LogFilter::isFiltered(LogInfo) method. For each LogFilter that returned false from that method, the logInfo
    * together with the message is passed to PrintMessage method of the ILogPrinter paired with that LogFilter.
    */
    void Log(const LogInfo& logInfo, const CompositeLoggable& message);

    /**
    * @brief Returns the maximum LogLevel for which there is at least one LogFilter which
    * doesn't filter out messages from ScopeLogger named "scopeLoggerName".
    *
    * This method is used by the ScopeLogger to determine which messages won't be accepted by
    * any of the registered LogFilters and thus can be filtered out before they are even created.
    * Logic of that filtering is contained in LOG() and LOGF() macros.
    */
    LogLevel GetMaximumLogLevel(const std::tstring& scopeLoggerName);
    
    /**
    * @brief Sets the default LogLevel 
    *
    * This method should be used for the programmatic, dynamic run-time control of the logging leve. 
    * As opposed to the start-up registry-based (which doesn't allow dynamic changes in the log level)
    */
    void SetFilterLevel(LogLevel newLevel);
    
    /**
    * @brief Gets the default LogLevel 
    *
    * This method should be used for the programmatic, dynamic run-time control of the logging leve. 
    * As opposed to the start-up registry-based (which doesn't allow dynamic changes in the log level)
    */
    LogLevel GetFilterLevel();

    /**
    * @brief Static function returning the static instance of Logger.
    *
    * We need this function instead of a static instance to prevent the so-called
    * "static initialization order fiasco".
    */
    static Logger& GetLogger();

    /**
     * @brief Configures this Logger using default, "hardcoded" configuration.
     * calls ConfigureFromRegistry().
     * The default configuration contains one LogChannel consisting of:
     * - LogFilter filtering out any messages above Warning LogLevel,
     * - SimpleTextLogPrinter,
     * - DebugOutputLogManager.
     */
    void ConfigureDefault();

    /**
     * @brief Configures this Logger to do a 'tee' logging into both debugger stream and file log
     * @param[in] atLevel filter out messages with greater levels
     * calls ConfigureFromRegistry().
     * The default configuration contains one LogChannel consisting of:
     * - LogFilter filtering out any messages above given LogLevel,
     * - SimpleTextLogPrinter,
     * - DebugOutputLogManager,
     * - FileLogManager,
     * - StreamManager
     */
    void ConfigureTee(LogLevel atLevel);

    /**
     * @brief Retrieve message with startup banner
     */
    const std::tstring& GetStarted() const;

    /**
     * @brief Enables/Disables write-through for file logging
     * @param[in] enable boolean to determine whether to enable or disable
     */
    void EnableWriteThrough(bool enable = true);

private:

    /**
     * @brief Current implementation tries to configure the logging system using configuration stored in registry.
     * @param[in,out] tss - ref to the TCHAR-based string stream, captures 'startup banner'. Up to a caller to dispose or print this banner
     * trying the keys in following order:
     * - HKEY_CURRENT_USER/Software/NVIDIA Corporation/Logging/(this process name)/
     * - HKEY_LOCAL_MACHINE/Software/NVIDIA Corporation/Logging/(this process name)/
     * - HKEY_CURRENT_USER/Software/NVIDIA Corporation/Logging/DefaultProcess/
     * - HKEY_LOCAL_MACHINE/Software/NVIDIA Corporation/Logging/DefaultProcess/
     * 
     */
    bool ConfigureFromRegistry(std::tstringstream& tss);

    /**
     * @brief Configures this Logger using configuration stored under particular base registry key.
     *
     * Current implementation will first determine whether the structure of the Software/NVIDIA Corporation/Logging
     * subkey is valid. False will be returned if this is not the case. Then the configuration for each
     * of the LogChannels referenced in configuration of this process will be read, and the elements of the LogChannel
     * will be created by ConfigureLogChannel(). No exceptions are thrown during this phase. Any error occurring during
     * processing of particular LogChannel will cause this LogChannel to be ignored.
     *
     * @param baseKeyHandle Open key handle for the base key. This should be either HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE.
     * @param processName Name of the process whose configuration should be read.
     * @return Boolean value indicating whether the configuration was applied.
     */
    bool TryConfigureFromRegistry(HKEY baseKeyHandle, const std::tstring& processName);

    /**
     * @brief Configures the LogChannel.
     *
     * Current implementation follows this algorithm:
     * - Try to create the LogFilter. If unsuccessful - silently fail.
     * - Try to create the ILogPrinter using CreateLogPrinter(). If unsuccessful - destroy the LogFilter
     *   created in previous step and silently fail.
     * - For each element of logManagers
     *     - Try to create or retrieve an existing instance of ILogManager using GetOrCreateLogManager().
     *       If unsuccessful - move on to next logManagers element.
     *     - Try to add the new ILogManager to the ILogPrinter. If unsuccessful - move on to next logManagers
     *       element. This particular step doesn't require destruction of the unused LogManager because it
     *       was saved in m_managers container by GetOrCreateLogManager() and will be destroyed in ~Logger().
     *
     * @param logFilterName Name of the LogFilter in this LogChannel.
     * @param logPrinterName Name of the ILogPrinter in this LogChannel.
     * @param logManagers Set of names of the ILogManager%s in this LogChannel.
     * @param definitionsKey The registry key containing the definitions of LogChannel elements.
     */
    void ConfigureLogChannel(const std::tstring& logFilterName, const std::tstring& logPrinterName, 
        const std::set<std::tstring>& logManagers, const RegistryKey& definitionsKey);

    /**
     * @brief Creates and returns a new ILogPrinter instance.
     *
     * This method reads the className value of the passed registry key and executes appropriate
     * constructor according to it. Contents of this method should be changed when new ILogPrinter
     * implementation is written.
     * @param key Registry key containing the configuration of ILogPrinter.
     * @return NULL on failure or a log printer for the given registry key on success.
     */
    ILogPrinter* CreateLogPrinter(const RegistryKey& key) const;

    /**
     * @brief Creates and returns a new ILogManager instance.
     *
     * This method reads the className value of the passed registry key and executes appropriate
     * constructor according to it. Contents of this method should be changed when new ILogManager
     * implementation is written.
     * @param key Registry key containing the configuration of this ILogManager.
     * @return NULL on failure or a new ILogManager on success
     */
    ILogManager* CreateLogManager(const RegistryKey& key) const;

    /**
     * @brief Returns a existing LogManager with specified name, or creates a new one if it doesn't exist.
     *
     * If a new ILogManager instance had to be created, it is saved in m_managers container to allow its
     * reuse in other LogChannels, and destruction in ~Logger().
     * @param key Registry key containing the configuration of this ILogManager.
     * @return NULL on failure or the ILogManager on success
     */
    ILogManager* GetOrCreateLogManager(const std::tstring& name, const RegistryKey& key);
};

// Registry configuration order:
// 1. HKCU/.../PROCESS.EXE
// 2. HKLM/.../PROCESS.EXE
// 3. HKCU/.../DEFAULT
// 4. HKLM/.../DEFAULT
// 5. Hardcoded default
inline bool Logger::ConfigureFromRegistry(std::tstringstream& tss)
{
    bool registryUsed = true;
    std::tstring processName = Utils::GetProcessName();
    if( TryConfigureFromRegistry(HKEY_CURRENT_USER, processName) )
    {
        tss<<_T("Logging init OK. Using configuration from HKCU for ")<<processName;
    } 
    else if( TryConfigureFromRegistry(HKEY_LOCAL_MACHINE, processName) )
    {
        tss<<_T("Logging init OK. Using configuration from HKLM for ")<<processName;
    } 
    else if( TryConfigureFromRegistry(HKEY_CURRENT_USER, LOGGING_KEYNAME_DEFAULT_PROCESS) )
    {
        tss<<_T("Logging init OK. Using configuration from HKCU for ")<<LOGGING_KEYNAME_DEFAULT_PROCESS<<_T(", for the ")<<processName;
    }
    else if( TryConfigureFromRegistry(HKEY_LOCAL_MACHINE, LOGGING_KEYNAME_DEFAULT_PROCESS) )
    {
        tss<<_T("Logging init OK. Using configuration from HKLM for ")<<LOGGING_KEYNAME_DEFAULT_PROCESS<<_T(", for the ")<<processName;
    }
    else
    {
        tss<<_T("Logging init OK. For the process ")<<processName;
        registryUsed = false;
    }

    return registryUsed;
}

inline Logger::Logger()
{
    std::tstringstream tss;
    bool configuredFromRegistry = ConfigureFromRegistry(tss);
    
    if(!configuredFromRegistry)
    {
        ConfigureDefault();
    }

    m_started = tss.str();
    LOGGING_SYSTEM_INFO(m_started);
}

inline Logger::Logger(LogLevel startLevel)
{
    std::tstringstream tss;

    m_defaultLevel = startLevel;

    tss<<boost::posix_time::microsec_clock::local_time()<<_T(" : ");
    ConfigureFromRegistry(tss);
    
    // @note by default, do nothing in the c-tor, so that code that instatiate Logger could configure default options
    // and override registry if necessary. Do not send anything to the log just yet - so that we could hush the message about logging init etc.
    m_started = tss.str();
}

inline void Logger::ConfigureDefault()
{
    std::auto_ptr<LogFilter> logFilter(new LogFilter(LogLevel_Warning));  
    std::auto_ptr<ILogPrinter> logPrinter(new SimpleTextLogPrinter());
    std::auto_ptr<ILogManager> logManager(new DebugOutputLogManager());
    logPrinter->AddLogManager(logManager.get());

    m_filtersAndPrinters.push_back(std::make_pair(logFilter.release(), logPrinter.release()));
    m_managers[_T("default")] = logManager.release();
}

inline void Logger::ConfigureTee(LogLevel atLevel)
{
    std::auto_ptr<LogFilter> logFilter(new LogFilter(atLevel));  
    std::auto_ptr<ILogPrinter> logPrinter(new SimpleTextLogPrinter());
    std::auto_ptr<ILogManager> logManager(new DebugOutputLogManager());
    logPrinter->AddLogManager(logManager.get());

    std::auto_ptr<ILogManager> fileLogManager(new FileLogManager());
    logPrinter->AddLogManager(fileLogManager.get());

    std::auto_ptr<ILogManager> streamManager(new StreamManager(&std::tcout));
    logPrinter->AddLogManager(streamManager.get());

    m_filtersAndPrinters.push_back(std::make_pair(logFilter.release(), logPrinter.release()));
    
    m_managers[_T("debugger")] = logManager.release();
    m_managers[_T("filelog")] = fileLogManager.release();
    m_managers[_T("stdout")] = streamManager.release();
}

inline LogLevel Logger::GetFilterLevel()
{
    return m_defaultLevel;
}

inline void Logger::SetFilterLevel(LogLevel newLevel)
{
    typedef std::vector<std::pair<LogFilter*, ILogPrinter*> > PairLogFilterPrinter;

    PairLogFilterPrinter::iterator itFilterPrinter;
    for( itFilterPrinter=m_filtersAndPrinters.begin(); itFilterPrinter!=m_filtersAndPrinters.end(); itFilterPrinter++ )
    {
        LogFilter* pFilter = itFilterPrinter->first;
        if( pFilter )
        {
            pFilter->SetFilterLevel(newLevel);
        }
        else
        {
            //assert(!"unexpected null pointer to the LogFilter");
        }
    }
}

inline const std::tstring& Logger::GetStarted() const
{
    return m_started;
}

inline void Logger::EnableWriteThrough(bool enable)
{
    std::pair<std::tstring, ILogManager*> pair;

    BOOST_FOREACH(pair, m_managers)
    {
        pair.second->EnableWriteThrough(enable);
    }
}

inline bool Logger::TryConfigureFromRegistry(HKEY baseKeyHandle, const std::tstring& processName)
{
    boost::optional<RegistryKey> loggingKey;

    boost::optional<RegistryKey> processLogChannelsKey;
    boost::optional<RegistryKey> definitionsKey;
    boost::optional<RegistryKey> logChannelsKey;
    boost::optional<RegistryKey> logPrintersKey;
    boost::optional<RegistryKey> logManagersKey;
    boost::optional<RegistryKey> logFiltersKey;

    bool registryOk =
        (RegistryKey::TryOpen(loggingKey, baseKeyHandle, 
            LOGGING_KEYNAME_SOFTWARE _T("\\") LOGGING_KEYNAME_NVIDIA_CORPORATION _T("\\") 
            LOGGING_KEYNAME_LOGGING) == ERROR_SUCCESS &&
         loggingKey->TryGetSubKey(processLogChannelsKey, LOGGING_KEYNAME_PROCESSES _T("\\") +
         processName + _T("\\") LOGGING_KEYNAME_LOGCHANNELS) == ERROR_SUCCESS &&
         loggingKey->TryGetSubKey(definitionsKey, LOGGING_KEYNAME_DEFINITIONS) == ERROR_SUCCESS &&
         definitionsKey->TryGetSubKey(logChannelsKey, LOGGING_KEYNAME_LOGCHANNELS) == ERROR_SUCCESS &&
         definitionsKey->TryGetSubKey(logPrintersKey, LOGGING_KEYNAME_LOGPRINTERS) == ERROR_SUCCESS &&
         definitionsKey->TryGetSubKey(logManagersKey, LOGGING_KEYNAME_LOGMANAGERS) == ERROR_SUCCESS &&
         definitionsKey->TryGetSubKey(logFiltersKey, LOGGING_KEYNAME_LOGFILTERS) == ERROR_SUCCESS);

    if (registryOk)
    {

        //if above didn't throw then process config structure is right.
        //Errors (i.e. missing LogXXX definition) are now dealt with locally.
        BOOST_FOREACH(std::tstring logChannelName, processLogChannelsKey->GetValueNames())
        {
            boost::optional<RegistryKey> thisChannelKey;
            boost::optional<RegistryKey> logManagersSubKey;
            std::tstring logFilterName;
            std::tstring logPrinterName;
            std::set<std::tstring> logManagers;

            if (logChannelsKey->TryGetSubKey(thisChannelKey, logChannelName) == ERROR_SUCCESS &&
                thisChannelKey->TryGetValue(LOGGING_KEYNAME_LOGFILTER, logFilterName) == ERROR_SUCCESS &&
                thisChannelKey->TryGetValue(LOGGING_KEYNAME_LOGPRINTER, logPrinterName) == ERROR_SUCCESS &&
                thisChannelKey->TryGetSubKey(logManagersSubKey, LOGGING_KEYNAME_LOGMANAGERS) == ERROR_SUCCESS &&
                logManagersSubKey->TryGetValueNames(logManagers) == ERROR_SUCCESS)
            {
                try
                {
                    ConfigureLogChannel(logFilterName, logPrinterName, logManagers, *definitionsKey);
                }
                catch (RegistryException &)
                {
                }
            }
        }      
    }
    return registryOk;
}

inline void Logger::ConfigureLogChannel(const std::tstring& logFilterName, const std::tstring& logPrinterName, 
                                 const std::set<std::tstring>& logManagers, const RegistryKey& definitionsKey)
{
    try
    {
        std::auto_ptr<LogFilter> logFilter(new LogFilter(definitionsKey.GetSubKey(LOGGING_KEYNAME_LOGFILTERS).GetSubKey(logFilterName)));
        std::auto_ptr<ILogPrinter> logPrinter(CreateLogPrinter(definitionsKey.GetSubKey(LOGGING_KEYNAME_LOGPRINTERS).GetSubKey(logPrinterName)));
        if (logPrinter.get() != NULL)
        {
            BOOST_FOREACH(std::tstring logManagerName, logManagers)
            {
                try
                {
                    boost::optional<RegistryKey> logManagerNameKey;
                    if (definitionsKey.TryGetSubKey(logManagerNameKey, 
                        LOGGING_KEYNAME_LOGMANAGERS _T("\\") + logManagerName) == ERROR_SUCCESS)
                    {
                        std::auto_ptr<ILogManager> logManager(GetOrCreateLogManager(logManagerName, *logManagerNameKey));
                        if (logManager.get() != NULL)
                        {
                            logPrinter->AddLogManager(logManager.release()); 
                        }
                    }
                } 
                catch (std::exception &)
                {
                }
            }
            m_filtersAndPrinters.push_back(std::make_pair(logFilter.get(), logPrinter.get()));
            logFilter.release();
            logPrinter.release();
        }
    }
    catch (std::exception &)
    {   // must be a problem with the log filter or log printer
    }
}

// This function should be modified when new LogPrinters are added.
inline ILogPrinter* Logger::CreateLogPrinter(const RegistryKey& key) const
{
    const std::tstring& className = key.GetStringValue(LOGGING_KEYNAME_CLASSNAME);
    if ( className == _T("SimpleTextLogPrinter") )
    {
        return new SimpleTextLogPrinter(key);
    } 
    else if ( className == _T("XMLLogPrinter") )
    {
        return new XMLLogPrinter(key);
    } 
    else 
    {
        throw std::runtime_error("No LogPrinter defined with such nume.");
    }
}

// This function should be modified when new LogManagers are added.
inline ILogManager* Logger::CreateLogManager(const RegistryKey& key) const
{
    const std::tstring& className = key.GetStringValue(LOGGING_KEYNAME_CLASSNAME);
    if ( className == _T("DebugOutputLogManager") )
    {
        return new DebugOutputLogManager(key);
    } 
    else if ( className == _T("FileLogManager") )
    {
        return new FileLogManager(key);
    } 
    else if ( className == _T("StreamManager") )
    {
        /// @todo read from the registry stream std spec - (w)cout, (w)cerr or (w)clog. 
        /// For now it's always tcout (wcout or cout, depends on UNICODE)
        return new StreamManager(&std::tcout);
    } 
    else 
    {
        throw std::runtime_error("No LogManager defined with such name");
    }
}

inline ILogManager* Logger::GetOrCreateLogManager(const std::tstring& name, const RegistryKey& key)
{
    if ( m_managers.count(name) )
    {
        return m_managers[name];
    }
    ILogManager* logManager = CreateLogManager(key);
    m_managers[name] = logManager;
    return logManager;
}

inline Logger::~Logger()
{
    std::pair<LogFilter*, ILogPrinter*> pair;
    BOOST_FOREACH(pair, m_filtersAndPrinters)
    {
        delete pair.first;
        delete pair.second;
    }

    std::pair<std::tstring, ILogManager*> pair2;

    BOOST_FOREACH(pair2, m_managers)
    {
        delete pair2.second;
    }
}

inline void Logger::Log(const LogInfo& logInfo, const CompositeLoggable& message)
{
    std::pair<LogFilter*, ILogPrinter*> pair;
    BOOST_FOREACH(pair, m_filtersAndPrinters)
    {
        if( !(pair.first)->IsFiltered(logInfo) )
        {
            (pair.second)->PrintMessage(logInfo, message);
        }
    }  
}

inline LogLevel Logger::GetMaximumLogLevel(const std::tstring& scopeLoggerName)
{
    LogFilter* filter;
    LogLevel maximumLogLevel = LogLevel_System;

    std::pair<LogFilter*, ILogPrinter*> pair;

    BOOST_FOREACH(pair, m_filtersAndPrinters)
    {
        filter = pair.first;
        maximumLogLevel = Utils::Maximum(maximumLogLevel, filter->GetMaximumLogLevel(scopeLoggerName));
    }

    return maximumLogLevel;
}

inline Logger& Logger::GetLogger()
{
    static Logger logger;
    return logger;
}

}
}
