/**
 * @file LogFilter.h
 * @author Bartosz Janiak
 * 
 * @brief Contains the definition of the LogFilter class.
 *
 * 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 "LogInfo.h"
#include "RegistryKey.h"
#include "TStd.h"

#include <boost/algorithm/string/erase.hpp>
#include <boost/foreach.hpp>

#include <map>

#define LOGGING_KEYNAME_DEFAULT_LOG_LEVEL   _T("DefaultLogLevel")
#define LOGGING_KEYNAME_ORIGIN_RULES        _T("OriginRules")

namespace Nvidia {
namespace Logging {

/**
 * @brief Simple filter which decides whether the message should be filtered basing on its LogLevel and origin.
 * 
 * What this documentation and the LogFilter implementation refer to as "origin" is equivalent to the name of
 * the ScopeLogger from which the log message originated.
 */
class LogFilter
{
private:
    /**
     * @brief Map containing the filtering rules.
     *
     * Each mapping in the form ("someOrigin" -> someLevel) means that all messages whose:
     * - origin matches "someOrigin.*", and
     * - "someOrigin" is the longest match from all mappings in OriginRules, and
     * - logLevel is above someLevel
     * 
     * should be filtered out.
     */
    std::map<std::tstring, LogLevel> OriginRules;
    typedef std::map<std::tstring, LogLevel>::const_iterator OriginRulesIterator;

    /// @brief Default LogLevel to use when there is no match in OriginRules for particular origin.
    LogLevel m_defaultLogLevel;

public:
    /**
     * @brief Constructor. Configures the LogFilter according to the information stored in the provided registry key.
     *
     * The structure of the configuration in the registry mirrors the structure of LogFilter object:
     * - DefaultLogLevel is stored in a value called "DefaultLogLevel",
     * - OriginRules are stored as DWORD values in OriginRules subkey.
     */
    LogFilter(const RegistryKey& key);

    /**
     * @brief Constructor. Creates a LogFilter with no OriginRules.
     *
     * @param defaultLogLevel The default LogLevel.
     */
    LogFilter(LogLevel defaultLogLevel);

    /// @brief Helper method which uses GetMaximumLogLevel() to determine whether message with provided LogInfo should be filtered. 
    bool IsFiltered(const LogInfo& logInfo);
    
    /// @brief change filtering level
    void SetFilterLevel(LogLevel newLevel);

    /// @brief retrieve filtering level
    LogLevel GetFilterLevel() const;

    /**
     * @brief This method returns the maximum LogLevel at which messages should be passed through this filter if they originate from "origin".
     *
     * Implementation of this method follows this algorithm:
     * - originPrefix := logInfo.ScopeLoggerName
     * - while originPrefix is not empty
     *      - check if there is a mapping from originPrefix in OriginRules
     *      - if yes then
     *          - add (origin -> OriginRules[originPrefix]) mapping to OriginRules
     *              - this point is not mandatory, but it reduces the complexity of the algorithm, because for each ScopeLoggerName we need to go through all hierarchy levels only once.
     *          - return OriginRules[originPrefix]
     *      - strip last part of the originPrefix (to and including the last dot)
     * - return m_defaultLogLevel
    */
    LogLevel GetMaximumLogLevel(const std::tstring& scopeLoggerName);

private:
    /// @brief Helper method which does in-place removal of the last part of the origin string (to and including the last dot character).
    void StripLastPart(std::tstring& originPrefix);
};

inline LogFilter::LogFilter(LogLevel defaultLogLevel) : m_defaultLogLevel(defaultLogLevel) {}

inline LogFilter::LogFilter(const RegistryKey& key)
{
    m_defaultLogLevel = (LogLevel) key.GetDWORDValue(LOGGING_KEYNAME_DEFAULT_LOG_LEVEL);
    const RegistryKey& originRulesKey = key.GetSubKey(LOGGING_KEYNAME_ORIGIN_RULES);

    BOOST_FOREACH(std::tstring origin, originRulesKey.GetValueNames())
    {
        OriginRules[origin] = (LogLevel) originRulesKey.GetDWORDValue(origin);
    }
}

inline bool LogFilter::IsFiltered(const LogInfo& logInfo)
{
    return logInfo.GetLevel() > GetMaximumLogLevel(logInfo.GetScopeLoggerName());
}

inline void LogFilter::SetFilterLevel(LogLevel newLevel)
{
    m_defaultLogLevel = newLevel;
}

inline LogLevel LogFilter::GetFilterLevel() const
{
    return m_defaultLogLevel;
}

inline void LogFilter::StripLastPart(std::tstring& originPrefix)
{
    size_t lastDot = originPrefix.find_last_of(_T('.'));

    if( lastDot == std::tstring::npos )
    {
        originPrefix = _T("");
    }
    else
    {
        // Boost documentation is self-contradictory with regards to what the second parameter means:
        // http://www.boost.org/doc/libs/1_38_0/doc/html/boost/algorithm/erase_tail.html
        // passing length of the *tail*, not the head, seems to produce desired result.
        // I've submitted a bug report to boost tracker regarding this issue: https://svn.boost.org/trac/boost/ticket/3314
        boost::algorithm::erase_tail(originPrefix, (int)(originPrefix.length() - lastDot));
        // boost::algorithm::erase_tail(originPrefix, lastDot);
    }
}

inline LogLevel LogFilter::GetMaximumLogLevel(const std::tstring& origin)
{
    std::tstring originPrefix = origin;
    OriginRulesIterator iterator;

    while(originPrefix.size() > 0)
    {
        if( (iterator = OriginRules.find(originPrefix)) != OriginRules.end() )
        {
            OriginRules[origin] = iterator->second;
            return iterator->second;
        }
        StripLastPart(originPrefix);
    }

    return m_defaultLogLevel;
}

}
}