/**
 * @file XMLLogPrinter.h
 * @author Bartosz Janiak
 * 
 * @brief Contains the definition of XMLLogPrinter class and related LogContentsPrinter implementations.
 *
 * 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 "GenericLogPrinter.h"
#include "LogContentsPrinter.h"
#include "Loggable.h"
#include "CompositeLoggable.h"
#include "LogInfo.h"
#include "RegistryKey.h"

#include "TStd.h"
#include "TBoostFormat.h"

#include <boost/utility.hpp>

#define LOGGING_KEYNAME_MAX_PRINTING_DEPTH _T("MaxPrintingDepth")

namespace Nvidia {
namespace Logging {

/**
 * @brief Objects of this class represent nodes of the XML structure.
 */
class XMLNode
{
private:
    /// @brief Name of the node.
    std::tstring m_name;

    /// @brief Vector of child XML nodes.
    std::vector<XMLNode> m_subNodes;

    /// @brief Vector of attributes. Each pair contains the name and the value of the attribute.
    std::vector<std::pair<std::tstring, std::tstring> > m_attributes;

public:
    /// @brief Constructor. Creates a XMLNode with provided name.
    XMLNode(const std::tstring& name);

    /// @brief Adds a new XMLNode with provided name as a sub node of this node, and returns a reference to it.
    XMLNode& AddSubNode(const std::tstring& name);

    /// @brief Adds a new attribute to this node.
    void AddAttribute(const std::tstring& name, const std::tstring& value);

    /**
     * @brief Prints this node in XML format.
     *
     * @param stream Stream on which the node should be printed.
     * @param indentLevel Indicates how much should the output be indented.
     * Each increment of this parameter adds four spaces before each line of the output.
     */
    void PrintOn(std::tostringstream& stream, unsigned int indentLevel = 0);
};

/**
 * @brief This LogContentsPrinter implementation creates a XMLNode%s mirroring the
 * structure of passed Loggable%s.
 */
class XMLContentsPrinter : public LogContentsPrinter, private boost::noncopyable
{
private:
    /// @brief Stack of XMLNodes. Contains the currently processed node on top and all its parent nodes below, including the root node.
    std::vector<XMLNode*> m_xmlNodeStack;
    /// @brief m_xmlNodeStack height at which this XMLContentsPrinter should stop logging the contents of CompositeLoggable%s.
    unsigned int m_maxPrintingDepth;

public:
    /// @brief Constructor. Sets the value of m_maxPrintingDepth member.
    XMLContentsPrinter(unsigned int maxPrintingDepth);

    /**
     * @brief Adds a new XMLNode representing this SimpleLoggable to the parent node.
     *
     * If the name parameter is equal to "id", the loggable string representation is saved
     * as an attribute of parent node instead.
     */
    void LogContentInternal(const std::tstring& name, const SimpleLoggable& loggable);

    /**
     * @brief Adds a new XMLNode representing this CompositeLoggable to the parent node.
     *
     * If the m_xmlNodeStack height doesn't exceed m_maxPrintingDepth, this new node is temporarily
     * pushed onto the stack and the contents of this CompositeLoggable are printed on this
     * XMLContentsPrinter recursively.
     */
    void LogContentInternal(const std::tstring& name, const CompositeLoggable& loggable);

    /// @brief Clears m_xmlNodeStack and pushes the passed node onto it.
    void SetRootXMLNode(XMLNode& node);
};

/** 
 * @brief This GenericLogPrinter implementation formats the log messages into XML.
 *
 * Example output of XMLLogPrinter:
 * <pre>
 * <Debug timestamp="940789104" msg="42 TestLoggable123 vector<int> 3.1400000000000001 someString" origin="scopeLoggerName" function="function" file="file" line="123">
 *     <int value="42"/>
 *     <TestLoggable id="123">
 *         <int field="someField1" value="154"/>
 *         <TestLoggable2 field="someField2" id="abc">
 *             <double field="doubleField" value="9.8699999999999992"/>
 *         </TestLoggable2>
 *         <vector field="vectorField">
 *             <basic_string value="firstString"/>
 *             <basic_string value="secondString"/>
 *         </vector>
 *     </TestLoggable>
 *     <vector>
 *         <int value="2"/>
 *         <int value="5"/>
 *         <int value="8"/>
 *     </vector>
 *     <double value="3.1400000000000001"/>
 *     <char value="someString"/>
 * </Debug>
 * </pre>
 */
class XMLLogPrinter : public GenericLogPrinter<std::tstring>
{
private:
    XMLContentsPrinter m_xmlContentsPrinter;

public:
    /// @brief Default constructor. 
    XMLLogPrinter();

    /**
     * @brief Constructor.
     */
    XMLLogPrinter(const RegistryKey& key);

private: 
    /**
    * This DoFormatMessage implementation formats populates the root node of XML output with
    * the contents of logInfo structure, and then adds additional subnodes via m_xmlContentsPrinter.
    * The root XMLNode printed onto a string is returned as a result.
    */
    std::tstring DoFormatMessage(const LogInfo& logInfo, const CompositeLoggable& message);
};

inline XMLNode::XMLNode(const std::tstring& name) :
    m_name(name) {}

inline XMLNode& XMLNode::AddSubNode(const std::tstring& name)
{
    m_subNodes.push_back(XMLNode(name));
    return m_subNodes.back();
}

inline void XMLNode::AddAttribute(const std::tstring& name, const std::tstring& value)
{
    m_attributes.push_back(std::make_pair(name, value));
}

inline void XMLNode::PrintOn(std::tostringstream& stream, unsigned int indentLevel)
{
    for ( unsigned int i = 0 ; i < indentLevel ; i++ )
        stream << _T("    ");

    stream << _T("<") << m_name;
    std::pair<std::tstring, std::tstring> attribute;
    
    BOOST_FOREACH(attribute, m_attributes)
    {
        stream << _T(" ") << attribute.first << _T("=\"") << attribute.second << _T("\"") ;
    }

    if ( m_subNodes.size() == 0 )
    {
        stream << _T("/>");
    }
    else
    {
        stream << _T(">") << std::endl;

        // We need to operate on references to XMLNode%s to avoid excessive copying
        for ( size_t i = 0 ; i < m_subNodes.size() ; i++ )
        {
            m_subNodes[i].PrintOn(stream, indentLevel + 1);
        }

        for ( unsigned int i = 0 ; i < indentLevel ; i++ )
            stream << _T("    ");
        
        stream << _T("</") << m_name << _T(">");
    }

    if ( indentLevel > 0 )
        stream << std::endl;
}

inline XMLContentsPrinter::XMLContentsPrinter(unsigned int maxPrintingDepth) :
    m_maxPrintingDepth(maxPrintingDepth) {}

inline void XMLContentsPrinter::LogContentInternal(const std::tstring& name, const SimpleLoggable& loggable)
{
    if ( name == _T("id") )
    {
        m_xmlNodeStack.back()->AddAttribute(name, loggable.GetStringValue());
    }
    else
    {
        XMLNode& currentNode = m_xmlNodeStack.back()->AddSubNode(loggable.GetHumanReadableTypeName());
        if ( name.length() > 0 )
            currentNode.AddAttribute(_T("field"), name);
        currentNode.AddAttribute(_T("value"), loggable.GetStringValue());
    }
}

inline void XMLContentsPrinter::LogContentInternal(const std::tstring& name, const CompositeLoggable& loggable)
{
    XMLNode& currentNode = m_xmlNodeStack.back()->AddSubNode(loggable.GetHumanReadableTypeName());
    if ( name.length() > 0 )
        currentNode.AddAttribute(_T("field"), name);

    m_xmlNodeStack.push_back(&currentNode);
    if ( m_xmlNodeStack.size() <= m_maxPrintingDepth )
        loggable.LogContentsOn(*this);
    m_xmlNodeStack.pop_back();
}

inline void XMLContentsPrinter::SetRootXMLNode(XMLNode& node)
{
    m_xmlNodeStack.clear();
    m_xmlNodeStack.push_back(&node);
}

inline XMLLogPrinter::XMLLogPrinter() : m_xmlContentsPrinter(5) {}

inline XMLLogPrinter::XMLLogPrinter(const RegistryKey& key) :
    m_xmlContentsPrinter(key.GetDWORDValue(LOGGING_KEYNAME_MAX_PRINTING_DEPTH)) {}

inline std::tstring XMLLogPrinter::DoFormatMessage(const LogInfo& logInfo, const CompositeLoggable& message)
{
    XMLNode messageNode(boost::lexical_cast<std::tstring>(logInfo.GetLevel()));

    messageNode.AddAttribute(_T("timestamp"), boost::lexical_cast<std::tstring>(logInfo.GetAbsoluteTimestamp()));
    messageNode.AddAttribute(_T("msg"), message.GetStringValue());
    messageNode.AddAttribute(_T("origin"), logInfo.GetScopeLoggerName());
    messageNode.AddAttribute(_T("function"), logInfo.GetFunctionName());
    messageNode.AddAttribute(_T("file"), logInfo.GetFileName());
    messageNode.AddAttribute(_T("line"), boost::lexical_cast<std::tstring>(logInfo.GetLineNumber()));
    
    m_xmlContentsPrinter.SetRootXMLNode(messageNode);
    message.LogContentsOn(m_xmlContentsPrinter);

    std::tostringstream stream;
    messageNode.PrintOn(stream);
    return stream.str();
}


}
}