/**
 * @file ChainLoggable.h
 * @author Bartosz Janiak
 * 
 * @brief Contains the definition of ChainLoggable class and related utility 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 "CompositeLoggable.h"
#include "Loggable.h"
#include "LogContentsPrinter.h"
#include "Chain.h"
#include "StreamOperators.h"
#include "LoggableTraits.h"

#include "TStd.h"
#include <vector>

namespace Nvidia {
namespace Logging {

/**
 * @brief Wrapper which exposes the contents of the Chain object as a CompositeLoggable.
 *
 * @tparam ChainType Type of the wrapped Chain.
 */
template <typename ChainType>
class ChainLoggable : public CompositeLoggable
{
protected:
    /// @brief Reference to the wrapped chain.
    const ChainType& m_wrappedChain;

public:
    /**
    * @brief Constructor.
    *
    * @param chain Chain which should be wrapped in this ChainLoggable.
    */
    ChainLoggable(const ChainType& chain) : m_wrappedChain(chain) {}

    /// @brief Copy constructor.
    ChainLoggable(const ChainLoggable& chainLoggable)
    {
        m_wrappedChain = chainLoggable.m_wrappedChain;
    }

    /// @brief Copy assignment operator.
    ChainLoggable& operator=(const ChainLoggable& chainLoggable)
    {
        m_wrappedChain = chainLoggable.m_wrappedChain;
        return *this;
    }

public:
    /**
    * @brief Passes all the elements of the m_wrappedChain to the logContentsPrinter.
    *
    * The m_wrappedChain is processed in reverse, i.e. from the tail to the head.
    */
    virtual void LogContentsOn(LogContentsPrinter& logContentsPrinter) const
    {
        LogChainOn(m_wrappedChain, logContentsPrinter);
    }

    /**
    * @brief Returns the concatenation of string representations of all the elements of the m_wrappedChain.
    *
    * The m_wrappedChain is processed in reverse, i.e. from the tail to the head.
    */
    virtual std::tstring GetStringValue() const
    {
        std::tostringstream stream;
        // TODO: because of the following (i.e. starting append from the Chain.getTail, not the Chain itself)
        // m_wrappedChain needs to have at least one not-NullType element.
        AppendChainTo(m_wrappedChain.GetTail(), stream);
        AppendChainElementTo(m_wrappedChain.GetHead(), stream);
        return stream.str();
    }

private:    
    /**
     * @brief Recursively passes the elements of the chain to the logContentsPrinter.
     *
     * The chain is processed in reverse, i.e. from the tail to the head.
     */
    template<typename HeadType, typename TailType>
    void LogChainOn(const Chain<HeadType, TailType>& chain, LogContentsPrinter& logContentsPrinter) const
    {
        LogChainOn(chain.GetTail(), logContentsPrinter);
        logContentsPrinter.LogContent(_T(""), chain.GetHead());
    }

    /**
     * @brief Specialization of LogChainOn() for EmptyChain. Does nothing.
     */
    template<>
    void LogChainOn<NullType, NullType>(const EmptyChain&, LogContentsPrinter&) const {}

    /**
     * @brief Recursively outputs the elements of the chain to the stream, with space characters between them.
     *
     * The chain is processed in reverse, i.e. from the tail to the head.
     */
    template<typename HeadType, typename TailType>
    void AppendChainTo(const Chain<HeadType, TailType>& chain, std::tostringstream& stream) const
    {
        AppendChainTo(chain.GetTail(), stream);
        AppendChainElementTo(chain.GetHead(), stream);
        stream << _T(" ");
    }
    
    ///@brief Outputs a signgle chain element to the stream.
    template<typename HeadType>
    static void AppendChainElementTo(HeadType head, std::tostringstream& stream)
    {
        typename LoggableTraits<HeadType>::LoggableType loggable(head);
        stream << loggable;
    }

    /**
     * @brief Outputs a signgle chain element to the stream.
     * 
     * Template function specialization for std::vector (necessary because we can't use LoggableTraits with templated types).
     */
    template<typename T>
    static void AppendChainElementTo(std::vector<T>& head, std::tostringstream& stream)
    {
        stream << head;
    }

     /**
     * @brief Specialization of AppendChainTo() for EmptyChain. Does nothing.
     */
    template<>
    void AppendChainTo(const EmptyChain&, std::tostringstream&) const
    {}
};

/// @brief Convenience function wrapping the ChainLoggable constructor.
template <typename ChainType>
ChainLoggable<ChainType> MakeChainLoggable(const ChainType& chain)
{
    return ChainLoggable<ChainType>(chain);
}

}
}
