/*
 *          Copyright Andrey Semashev 2007 - 2015.
 * Distributed under the Boost Software License, Version 1.0.
 *    (See accompanying file LICENSE_1_0.txt or copy at
 *          http://www.boost.org/LICENSE_1_0.txt)
 */
/*!
 * \file   event_log_backend.cpp
 * \author Andrey Semashev
 * \date   07.11.2008
 *
 * \brief  A logging sink backend that uses Windows NT event log API
 *         for signalling application events.
 */

#ifndef BOOST_LOG_WITHOUT_EVENT_LOG

#include "windows_version.hpp"
#include <memory>
#include <string>
#include <vector>
#include <ostream>
#include <stdexcept>
#include <boost/scoped_array.hpp>
#include <boost/log/exceptions.hpp>
#include <boost/log/sinks/event_log_backend.hpp>
#include <boost/log/sinks/event_log_constants.hpp>
#include <boost/log/utility/once_block.hpp>
#include <boost/log/detail/cleanup_scope_guard.hpp>
#include <boost/log/detail/attachable_sstream_buf.hpp>
#include <boost/log/detail/code_conversion.hpp>
#include <boost/log/utility/formatting_ostream.hpp>
#include "event_log_registry.hpp"
#include <windows.h>
#include <psapi.h>
#include "simple_event_log.h"
#include <boost/log/detail/header.hpp>

#ifdef _MSC_VER
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "advapi32.lib")
#endif


namespace boost {

BOOST_LOG_OPEN_NAMESPACE

namespace sinks {

namespace event_log {

    //! The function constructs log record level from an integer
    BOOST_LOG_API event_type make_event_type(unsigned short lev)
    {
        switch (lev)
        {
        case success: return success;
        case warning: return warning;
        case error: return error;
        default:
            BOOST_THROW_EXCEPTION(std::out_of_range("Windows NT event type is out of range"));
            BOOST_LOG_UNREACHABLE_RETURN(info);
        case info: return info;
        }
    }

} // namespace event_log

BOOST_LOG_ANONYMOUS_NAMESPACE {

#ifdef BOOST_LOG_USE_CHAR
    //! A simple forwarder to the ReportEvent API
    inline BOOL report_event(
        HANDLE hEventLog,
        WORD wType,
        WORD wCategory,
        DWORD dwEventID,
        PSID lpUserSid,
        WORD wNumStrings,
        DWORD dwDataSize,
        const char** lpStrings,
        LPVOID lpRawData)
    {
        return ReportEventA(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
    }
    //! A simple forwarder to the GetModuleFileName API
    inline DWORD get_module_file_name(HMODULE hModule, char* lpFilename, DWORD nSize)
    {
        return GetModuleFileNameA(hModule, lpFilename, nSize);
    }
    //! A simple forwarder to the RegisterEventSource API
    inline HANDLE register_event_source(const char* lpUNCServerName, const char* lpSourceName)
    {
        return RegisterEventSourceA(lpUNCServerName, lpSourceName);
    }
    //! The function completes default source name for the sink backend
    inline void complete_default_simple_event_log_source_name(std::string& name)
    {
        name += " simple event source";
    }
    //! The function completes default source name for the sink backend
    inline void complete_default_event_log_source_name(std::string& name)
    {
        name += " event source";
    }
#endif // BOOST_LOG_USE_CHAR

#ifdef BOOST_LOG_USE_WCHAR_T
    //! A simple forwarder to the ReportEvent API
    inline BOOL report_event(
        HANDLE hEventLog,
        WORD wType,
        WORD wCategory,
        DWORD dwEventID,
        PSID lpUserSid,
        WORD wNumStrings,
        DWORD dwDataSize,
        const wchar_t** lpStrings,
        LPVOID lpRawData)
    {
        return ReportEventW(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
    }
    //! A simple forwarder to the GetModuleFileName API
    inline DWORD get_module_file_name(HMODULE hModule, wchar_t* lpFilename, DWORD nSize)
    {
        return GetModuleFileNameW(hModule, lpFilename, nSize);
    }
    //! A simple forwarder to the RegisterEventSource API
    inline HANDLE register_event_source(const wchar_t* lpUNCServerName, const wchar_t* lpSourceName)
    {
        return RegisterEventSourceW(lpUNCServerName, lpSourceName);
    }
    //! The function completes default source name for the sink backend
    inline void complete_default_simple_event_log_source_name(std::wstring& name)
    {
        name += L" simple event source";
    }
    //! The function completes default source name for the sink backend
    inline void complete_default_event_log_source_name(std::wstring& name)
    {
        name += L" event source";
    }
#endif // BOOST_LOG_USE_WCHAR_T

    //! The function finds the handle for the current module
    void init_self_module_handle(HMODULE& handle)
    {
        // Acquire all modules of the current process
        HANDLE hProcess = GetCurrentProcess();
        std::vector< HMODULE > modules;
        DWORD module_count = 1024;
        do
        {
            modules.resize(module_count, HMODULE(0));
            BOOL res = EnumProcessModules(
                hProcess,
                &modules[0],
                static_cast< DWORD >(modules.size() * sizeof(HMODULE)),
                &module_count);
            module_count /= sizeof(HMODULE);

            if (!res)
                BOOST_LOG_THROW_DESCR(system_error, "Could not enumerate process modules");
        }
        while (module_count > modules.size());
        modules.resize(module_count, HMODULE(0));

        // Now find the current module among them
        void* p = (void*)&init_self_module_handle;
        for (std::size_t i = 0, n = modules.size(); i < n; ++i)
        {
            MODULEINFO info;
            if (!GetModuleInformation(hProcess, modules[i], &info, sizeof(info)))
                BOOST_LOG_THROW_DESCR(system_error, "Could not acquire module information");

            if (info.lpBaseOfDll <= p && (static_cast< unsigned char* >(info.lpBaseOfDll) + info.SizeOfImage) > p)
            {
                // Found it
                handle = modules[i];
                break;
            }
        }

        if (!handle)
            BOOST_LOG_THROW_DESCR(system_error, "Could not find self module information");
    }

    //! Retrieves the full name of the current module (be that dll or exe)
    template< typename CharT >
    std::basic_string< CharT > get_current_module_name()
    {
        static HMODULE hSelfModule = 0;

        BOOST_LOG_ONCE_BLOCK()
        {
            init_self_module_handle(hSelfModule);
        }

        // Get the module file name
        CharT buf[MAX_PATH];
        DWORD size = get_module_file_name(hSelfModule, buf, sizeof(buf) / sizeof(*buf));
        if (size == 0)
            BOOST_LOG_THROW_DESCR(system_error, "Could not get module file name");

        return std::basic_string< CharT >(buf, buf + size);
    }

} // namespace

//////////////////////////////////////////////////////////////////////////
//  Simple event log backend implementation
//////////////////////////////////////////////////////////////////////////
//! Sink backend implementation
template< typename CharT >
struct basic_simple_event_log_backend< CharT >::implementation
{
    //! A handle for the registered event provider
    HANDLE m_SourceHandle;
    //! A level mapping functor
    event_type_mapper_type m_LevelMapper;

    implementation() : m_SourceHandle(0)
    {
    }
};

//! Default constructor. Registers event source Boost.Log <Boost version> in the Application log.
template< typename CharT >
BOOST_LOG_API basic_simple_event_log_backend< CharT >::basic_simple_event_log_backend()
{
    construct(log::aux::empty_arg_list());
}

//! Destructor
template< typename CharT >
BOOST_LOG_API basic_simple_event_log_backend< CharT >::~basic_simple_event_log_backend()
{
    DeregisterEventSource(m_pImpl->m_SourceHandle);
    delete m_pImpl;
}

//! Constructs backend implementation
template< typename CharT >
BOOST_LOG_API void basic_simple_event_log_backend< CharT >::construct(
    string_type const& target, string_type const& log_name, string_type const& source_name, event_log::registration_mode reg_mode)
{
    if (reg_mode != event_log::never)
    {
        aux::registry_params< char_type > reg_params;
        reg_params.event_message_file = get_current_module_name< char_type >();
        reg_params.types_supported = DWORD(
            EVENTLOG_SUCCESS |
            EVENTLOG_INFORMATION_TYPE |
            EVENTLOG_WARNING_TYPE |
            EVENTLOG_ERROR_TYPE);
        aux::init_event_log_registry(log_name, source_name, reg_mode == event_log::forced, reg_params);
    }

    std::auto_ptr< implementation > p(new implementation());

    const char_type* target_unc = NULL;
    if (!target.empty())
        target_unc = target.c_str();

    HANDLE hSource = register_event_source(target_unc, source_name.c_str());
    if (!hSource)
        BOOST_LOG_THROW_DESCR(system_error, "Could not register event source");

    p->m_SourceHandle = hSource;

    m_pImpl = p.release();
}

//! Returns default log name
template< typename CharT >
BOOST_LOG_API typename basic_simple_event_log_backend< CharT >::string_type
basic_simple_event_log_backend< CharT >::get_default_log_name()
{
    return aux::registry_traits< char_type >::make_default_log_name();
}

//! Returns default source name
template< typename CharT >
BOOST_LOG_API typename basic_simple_event_log_backend< CharT >::string_type
basic_simple_event_log_backend< CharT >::get_default_source_name()
{
    string_type source_name = aux::registry_traits< char_type >::make_default_source_name();
    complete_default_simple_event_log_source_name(source_name);
    return source_name;
}

//! The method installs the function object that maps application severity levels to WinAPI event types
template< typename CharT >
BOOST_LOG_API void basic_simple_event_log_backend< CharT >::set_event_type_mapper(event_type_mapper_type const& mapper)
{
    m_pImpl->m_LevelMapper = mapper;
}

//! The method puts the formatted message to the event log
template< typename CharT >
BOOST_LOG_API void basic_simple_event_log_backend< CharT >::consume(record_view const& rec, string_type const& formatted_message)
{
    const char_type* message = formatted_message.c_str();
    event_log::event_type evt_type = event_log::info;
    if (!m_pImpl->m_LevelMapper.empty())
        evt_type = m_pImpl->m_LevelMapper(rec);

    DWORD event_id;
    switch (evt_type)
    {
    case event_log::success:
        event_id = BOOST_LOG_MSG_DEBUG; break;
    case event_log::warning:
        event_id = BOOST_LOG_MSG_WARNING; break;
    case event_log::error:
        event_id = BOOST_LOG_MSG_ERROR; break;
    default:
        event_id = BOOST_LOG_MSG_INFO; break;
    }

    report_event(
        m_pImpl->m_SourceHandle,        // Event log handle.
        static_cast< WORD >(evt_type),  // Event type.
        0,                              // Event category.
        event_id,                       // Event identifier.
        NULL,                           // No user security identifier.
        1,                              // Number of substitution strings.
        0,                              // No data.
        &message,                       // Pointer to strings.
        NULL);                          // No data.
}

//////////////////////////////////////////////////////////////////////////
//  Customizable event log backend implementation
//////////////////////////////////////////////////////////////////////////
namespace event_log {

    template< typename CharT >
    class basic_event_composer< CharT >::insertion_composer
    {
    public:
        //! Function object result type
        typedef void result_type;

    private:
        //! The list of insertion composers (in backward order)
        typedef std::vector< formatter_type > formatters;

    private:
        //! The insertion string composers
        formatters m_Formatters;

    public:
        //! Default constructor
        insertion_composer() {}
        //! Composition operator
        void operator() (record_view const& rec, insertion_list& insertions) const
        {
            std::size_t size = m_Formatters.size();
            insertions.resize(size);
            for (std::size_t i = 0; i < size; ++i)
            {
                typename formatter_type::stream_type strm(insertions[i]);
                m_Formatters[i](rec, strm);
                strm.flush();
            }
        }
        //! Adds a new formatter to the list
        void add_formatter(formatter_type const& fmt)
        {
            m_Formatters.push_back(formatter_type(fmt));
        }
    };

    //! Default constructor
    template< typename CharT >
    basic_event_composer< CharT >::basic_event_composer(event_id_mapper_type const& id_mapper) :
        m_EventIDMapper(id_mapper)
    {
    }
    //! Copy constructor
    template< typename CharT >
    basic_event_composer< CharT >::basic_event_composer(basic_event_composer const& that) :
        m_EventIDMapper(that.m_EventIDMapper),
        m_EventMap(that.m_EventMap)
    {
    }
    //! Destructor
    template< typename CharT >
    basic_event_composer< CharT >::~basic_event_composer()
    {
    }

    //! Assignment
    template< typename CharT >
    basic_event_composer< CharT >& basic_event_composer< CharT >::operator= (basic_event_composer that)
    {
        swap(that);
        return *this;
    }
    //! Swapping
    template< typename CharT >
    void basic_event_composer< CharT >::swap(basic_event_composer& that)
    {
        m_EventIDMapper.swap(that.m_EventIDMapper);
        m_EventMap.swap(that.m_EventMap);
    }
    //! Creates a new entry for a message
    template< typename CharT >
    typename basic_event_composer< CharT >::event_map_reference
    basic_event_composer< CharT >::operator[] (event_id id)
    {
        return event_map_reference(id, *this);
    }
    //! Creates a new entry for a message
    template< typename CharT >
    typename basic_event_composer< CharT >::event_map_reference
    basic_event_composer< CharT >::operator[] (int id)
    {
        return event_map_reference(make_event_id(id), *this);
    }

    //! Event composition operator
    template< typename CharT >
    event_id basic_event_composer< CharT >::operator() (record_view const& rec, insertion_list& insertions) const
    {
        event_id id = m_EventIDMapper(rec);
        typename event_map::const_iterator it = m_EventMap.find(id);
        if (it != m_EventMap.end())
            it->second(rec, insertions);
        return id;
    }

    //! Adds a formatter to the insertion composers list
    template< typename CharT >
    typename basic_event_composer< CharT >::insertion_composer*
    basic_event_composer< CharT >::add_formatter(event_id id, insertion_composer* composer, formatter_type const& fmt)
    {
        if (!composer)
            composer = &m_EventMap[id];
        composer->add_formatter(fmt);
        return composer;
    }

#ifdef BOOST_LOG_USE_CHAR
    template class BOOST_LOG_API basic_event_composer< char >;
#endif
#ifdef BOOST_LOG_USE_WCHAR_T
    template class BOOST_LOG_API basic_event_composer< wchar_t >;
#endif

} // namespace event_log


//! Backend implementation
template< typename CharT >
struct basic_event_log_backend< CharT >::implementation
{
    //  NOTE: This order of data members is critical for MSVC 9.0 in debug mode,
    //        as it ICEs if boost::functions are not the first members. Doh!

    //! An event category mapper
    event_category_mapper_type m_CategoryMapper;
    //! A level mapping functor
    event_type_mapper_type m_LevelMapper;

    //! A handle for the registered event provider
    HANDLE m_SourceHandle;
    //! A functor that composes an event
    event_composer_type m_EventComposer;
    //! An array of formatted insertions
    insertion_list m_Insertions;

    implementation() : m_SourceHandle(0)
    {
    }
};

//! Destructor
template< typename CharT >
BOOST_LOG_API basic_event_log_backend< CharT >::~basic_event_log_backend()
{
    DeregisterEventSource(m_pImpl->m_SourceHandle);
    delete m_pImpl;
}

//! Constructs backend implementation
template< typename CharT >
BOOST_LOG_API void basic_event_log_backend< CharT >::construct(
    filesystem::path const& message_file_name,
    string_type const& target,
    string_type const& log_name,
    string_type const& source_name,
    event_log::registration_mode reg_mode)
{
    if (reg_mode != event_log::never)
    {
        aux::registry_params< char_type > reg_params;
        string_type file_name;
        log::aux::code_convert(message_file_name.string(), file_name);
        reg_params.event_message_file = file_name;
        reg_params.types_supported = DWORD(
            EVENTLOG_SUCCESS |
            EVENTLOG_INFORMATION_TYPE |
            EVENTLOG_WARNING_TYPE |
            EVENTLOG_ERROR_TYPE);
        aux::init_event_log_registry(log_name, source_name, reg_mode == event_log::forced, reg_params);
    }

    std::auto_ptr< implementation > p(new implementation());

    const char_type* target_unc = NULL;
    if (!target.empty())
        target_unc = target.c_str();

    HANDLE hSource = register_event_source(target_unc, source_name.c_str());
    if (!hSource)
        BOOST_LOG_THROW_DESCR(system_error, "Could not register event source");

    p->m_SourceHandle = hSource;

    m_pImpl = p.release();
}

//! The method puts the formatted message to the event log
template< typename CharT >
BOOST_LOG_API void basic_event_log_backend< CharT >::consume(record_view const& rec)
{
    if (!m_pImpl->m_EventComposer.empty())
    {
        log::aux::cleanup_guard< insertion_list > cleaner(m_pImpl->m_Insertions);

        // Get event ID and construct insertions
        DWORD id = m_pImpl->m_EventComposer(rec, m_pImpl->m_Insertions);
        WORD string_count = static_cast< WORD >(m_pImpl->m_Insertions.size());
        scoped_array< const char_type* > strings(new const char_type*[string_count]);
        for (WORD i = 0; i < string_count; ++i)
            strings[i] = m_pImpl->m_Insertions[i].c_str();

        // Get event type
        WORD event_type = EVENTLOG_INFORMATION_TYPE;
        if (!m_pImpl->m_LevelMapper.empty())
            event_type = static_cast< WORD >(m_pImpl->m_LevelMapper(rec));

        WORD event_category = 0;
        if (!m_pImpl->m_CategoryMapper.empty())
            event_category = static_cast< WORD >(m_pImpl->m_CategoryMapper(rec));

        report_event(
            m_pImpl->m_SourceHandle,       // Event log handle.
            event_type,                    // Event type.
            event_category,                // Event category.
            id,                            // Event identifier.
            NULL,                          // No user security identifier.
            string_count,                  // Number of substitution strings.
            0,                             // No data.
            strings.get(),                 // Pointer to strings.
            NULL);                         // No data.
    }
}

//! Returns default log name
template< typename CharT >
BOOST_LOG_API typename basic_event_log_backend< CharT >::string_type
basic_event_log_backend< CharT >::get_default_log_name()
{
    return aux::registry_traits< char_type >::make_default_log_name();
}

//! Returns default source name
template< typename CharT >
BOOST_LOG_API typename basic_event_log_backend< CharT >::string_type
basic_event_log_backend< CharT >::get_default_source_name()
{
    string_type source_name = aux::registry_traits< char_type >::make_default_source_name();
    complete_default_event_log_source_name(source_name);
    return source_name;
}

//! The method installs the function object that maps application severity levels to WinAPI event types
template< typename CharT >
BOOST_LOG_API void basic_event_log_backend< CharT >::set_event_type_mapper(event_type_mapper_type const& mapper)
{
    m_pImpl->m_LevelMapper = mapper;
}

//! The method installs the function object that extracts event category from attribute values
template< typename CharT >
BOOST_LOG_API void basic_event_log_backend< CharT >::set_event_category_mapper(event_category_mapper_type const& mapper)
{
    m_pImpl->m_CategoryMapper = mapper;
}

/*!
 * The method installs the function object that extracts event identifier from the attributes and creates
 * insertion strings that will replace placeholders in the event message.
 */
template< typename CharT >
BOOST_LOG_API void basic_event_log_backend< CharT >::set_event_composer(event_composer_type const& composer)
{
    m_pImpl->m_EventComposer = composer;
}


#ifdef BOOST_LOG_USE_CHAR
template class basic_simple_event_log_backend< char >;
template class basic_event_log_backend< char >;
#endif
#ifdef BOOST_LOG_USE_WCHAR_T
template class basic_simple_event_log_backend< wchar_t >;
template class basic_event_log_backend< wchar_t >;
#endif

} // namespace sinks

BOOST_LOG_CLOSE_NAMESPACE // namespace log

} // namespace boost

#include <boost/log/detail/footer.hpp>

#endif // !defined(BOOST_LOG_WITHOUT_EVENT_LOG)
