/**
 * @file SimpleTextLogPrinter.h
 * @author Bartosz Janiak
 * 
 * @brief Contains the definition of SimpleTextLogPrinter class and related LogContentsPrinter implementations.
 *
 * Copyright © 2009-2011 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>
#include <boost/date_time/c_local_time_adjustor.hpp>

// date formatting in boost 1.45 seems to be broken: %S always gives fractional seconds
// and I only want to give seconds up to milliseconds. Therefore do it in parts.
// The format for date is YYYY-MM-DD HH:MM:SS.mmm 
#define _LOGGING_FORMAT _T("%04d-%02d-%02d %02d:%02d:%02d.%03d (%11.3lf) | %8x: [%x] %d@%x : %x. ")

namespace Nvidia {
namespace Logging {

namespace Test {
    class LoggerTest;
    class LoggerConstructorTest;
}

/**
 * @brief This LogContentsPrinter implementation writes both the SimpleLoggable%s and CompositeLoggable%s
 * onto a std::ostringstream in a simple format including the Loggable's name.
 */
class SimpleContentsPrinter : public LogContentsPrinter, private boost::noncopyable
{
private:
    std::tostringstream& m_stream;
public:
    SimpleContentsPrinter(std::tostringstream& stream);
    void LogContentInternal(const std::tstring& name, const SimpleLoggable& loggable);
    void LogContentInternal(const std::tstring& name, const CompositeLoggable& loggable);
};

/**
 * @brief This LogContentsPrinter implementation writes only the CompositeLoggable%s
 * onto a std::ostringstream in a simple format that doesn't include the Loggable's name.
 *
 * SimpleContentsPrinter is used to format Loggables found deeper in the structure of the message.
 */
class FirstLevelContentsPrinter : public LogContentsPrinter, private boost::noncopyable
{
private:
    std::tostringstream& m_stream;
    SimpleContentsPrinter m_simpleContentsPrinter;
public:
    FirstLevelContentsPrinter(std::tostringstream& stream);
    void LogContentInternal(const std::tstring& name, const SimpleLoggable& loggable);
    void LogContentInternal(const std::tstring& name, const CompositeLoggable& loggable);
};

/** 
 * @brief This GenericLogPrinter implementation formats the log messages into a simple text format,
 * suitable for displaying on a terminal output.
 *
 * Example output of SimpleTextLogPrinter:
 * <pre>
 *         0.901 |     INFO: 123 @ foo::bar() : This is a simple log message.
 *       430.433 |    DEBUG: 99 @ abcDef::Ghi() : This is a 003.141 formatted log message.
 *    232114.021 | CRITICAL: 5432 @ Display::attach() : Display1 attached to Gpu2. Display1=(Type=LCD, Serial=00001, IsLidClosed=false), Gpu2=(...)
 * </pre>
 */
class SimpleTextLogPrinter : public GenericLogPrinter<std::tstring>
{
    friend class Nvidia::Logging::Test::LoggerTest;
    friend class Nvidia::Logging::Test::LoggerConstructorTest;

private:
    boost::tformat m_formatter;
    std::tostringstream m_stream;
    FirstLevelContentsPrinter m_firstLevelContentsPrinter;

public:
    /// @brief Default constructor. Initializes the Stream, Formatter and FirstLevelContentsPrinter attributes.
    SimpleTextLogPrinter();

    /**
     * @brief Constructor. Initializes the Stream, Formatter and FirstLevelContentsPrinter attributes.
     *
     * As there is no configuration needed for this GenericLogPrinter, the key parameter is ignored.
     */
    SimpleTextLogPrinter(const RegistryKey& key);

private: 
    /**
    * This DoFormatMessage implementation formats the logInfo parameter using boost::format and
    * appends the result of formatting the message on FirstLevelContentsPrinter to the result.
    * That result is then returned.
    */
    std::tstring DoFormatMessage(const LogInfo& logInfo, const CompositeLoggable& message);
};

inline SimpleContentsPrinter::SimpleContentsPrinter(std::tostringstream& stream) :
    m_stream(stream) {}

inline void SimpleContentsPrinter::LogContentInternal(const std::tstring& name, const SimpleLoggable& loggable)
{
    if ( name.length() > 0 )
        m_stream << name << _T("=");

    m_stream << _T("\"") << loggable.GetStringValue() << _T("\" ") ;
}

inline void SimpleContentsPrinter::LogContentInternal(const std::tstring& name, const CompositeLoggable& loggable)
{
    if ( name.length() > 0 )
        m_stream << name << _T("=");

    m_stream << loggable.GetHumanReadableTypeName() << _T("( ") ;

    loggable.LogContentsOn(*this);
    m_stream << _T(") ");
}

inline FirstLevelContentsPrinter::FirstLevelContentsPrinter(std::tostringstream& stream) :
    m_stream(stream),
    m_simpleContentsPrinter(stream) {}

inline void FirstLevelContentsPrinter::LogContentInternal(const std::tstring&, const SimpleLoggable&)
{
    return;
}

inline void FirstLevelContentsPrinter::LogContentInternal(const std::tstring&, const CompositeLoggable& loggable)
{
    m_stream << loggable.GetHumanReadableTypeName() << _T("( ") ;
    loggable.LogContentsOn(m_simpleContentsPrinter);
    m_stream << _T(") ");
}

inline SimpleTextLogPrinter::SimpleTextLogPrinter() :
    m_stream(),
    m_formatter(_LOGGING_FORMAT),
    m_firstLevelContentsPrinter(m_stream) {}

inline SimpleTextLogPrinter::SimpleTextLogPrinter(const RegistryKey&) :
    m_stream(),
    m_formatter(_LOGGING_FORMAT),
    m_firstLevelContentsPrinter(m_stream) {}

inline std::tstring SimpleTextLogPrinter::DoFormatMessage(const LogInfo& logInfo, const CompositeLoggable& message)
{
    using namespace boost::posix_time;
    using namespace boost::date_time;
    using namespace std;
    const ptime &startTime = Utils::GetStartTime();
    ptime::time_duration_type startTimeToNow = milliseconds(logInfo.GetRelativeTimestamp());

    // adjust from UTC to local time
    ptime timeStamp = c_local_adjustor<ptime>::utc_to_local(startTime + startTimeToNow);
    ptime::date_type::ymd_type date = timeStamp.date().year_month_day();
    ptime::time_duration_type time = timeStamp.time_of_day();

    m_formatter % date.year % date.month.as_number() % date.day;
    m_formatter % time.hours() % time.minutes() % (time.total_seconds() % 60);
    m_formatter % (time.total_milliseconds() % 1000);

    m_formatter % (((double) logInfo.GetRelativeTimestamp()) / 1000);
    m_formatter % Utils::CapitalizeString(boost::lexical_cast<std::tstring>(logInfo.GetLevel()));
    m_formatter % logInfo.GetScopeLoggerName();
    m_formatter % logInfo.GetLineNumber();
    m_formatter % logInfo.GetFunctionName();
    m_formatter % message.GetStringValue();

    m_stream.str(_T(""));
    m_stream << m_formatter;
    message.LogContentsOn(m_firstLevelContentsPrinter);
    return m_stream.str();
}

}
}