//  (C) Copyright Gennadiy Rozental 2001-2004.
//  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)

//  See http://www.boost.org/libs/test for the library home page.
//
//  File        : $RCSfile: unit_test_log.cpp,v $
//
//  Version     : $Revision: 1.24 $
//
//  Description : implemets Unit Test Log, Majority of implementation details
//  are hidden in this file with use of pimpl idiom.
// ***************************************************************************

// Boost.Test
#include <boost/test/unit_test_log.hpp>
#include <boost/test/unit_test_log_formatter.hpp>
#include <boost/test/detail/supplied_log_formatters.hpp>
#include <boost/test/detail/unit_test_parameters.hpp>
#include <boost/test/unit_test_result.hpp>
#include <boost/test/detail/basic_cstring/compare.hpp>
#include <boost/test/detail/fixed_mapping.hpp>

// BOOST
#include <boost/scoped_ptr.hpp>
#include <boost/progress.hpp>

// STL
#include <algorithm>
#include <map>

# ifdef BOOST_NO_STDC_NAMESPACE
namespace std { using ::strcmp; }
# endif

namespace boost {

namespace unit_test {

// ************************************************************************** //
// **************                 unit_test_log                ************** //
// ************************************************************************** //

struct unit_test_log::Impl {
    // Constructor
    Impl() : m_stream( &std::cout ) {}

    // log data
    std::ostream*       m_stream;
    log_level           m_threshold_level;
    boost::scoped_ptr<boost::progress_display> m_progress_display;
    boost::scoped_ptr<unit_test_log_formatter> m_log_formatter;

    // entry data
    bool                m_entry_in_progress;
    bool                m_entry_has_value;
    log_entry_data      m_entry_data;

    // checkpoint data
    log_checkpoint_data m_checkpoint_data;

    // helper functions
    std::ostream&       stream()            { return *m_stream; }
    void                flush_entry()       { stream() << std::endl; }
    void                clear_entry_data()
    {
        m_entry_data.clear();
        m_entry_in_progress = false;
        m_entry_has_value   = false;
    }
    void                set_checkpoint( checkpoint const& cp )
    {
        cp.m_message.assign_to( m_checkpoint_data.m_message );
        m_checkpoint_data.m_file    = m_entry_data.m_file;
        m_checkpoint_data.m_line    = m_entry_data.m_line;
    }
    void                clear_checkpoint()  { m_checkpoint_data.clear(); }
};

//____________________________________________________________________________//

unit_test_log::unit_test_log() : m_pimpl( new Impl() )
{
    m_pimpl->m_threshold_level = log_all_errors;

    m_pimpl->m_log_formatter.reset( new ut_detail::msvc65_like_log_formatter( *this ) );

    m_pimpl->clear_entry_data();
    m_pimpl->clear_checkpoint();
}

//____________________________________________________________________________//

unit_test_log&
unit_test_log::instance()
{
    static unit_test_log the_instance;

    return the_instance;
}

//____________________________________________________________________________//

unit_test_log::~unit_test_log()
{
    delete m_pimpl;
}

//____________________________________________________________________________//

void
unit_test_log::set_log_stream( std::ostream& str )
{
    if( m_pimpl->m_entry_in_progress )
        return;

    m_pimpl->m_stream = &str;
}

//____________________________________________________________________________//

void
unit_test_log::set_log_threshold_level( log_level lev )
{
    if( m_pimpl->m_entry_in_progress || lev == invalid_log_level )
        return;

    m_pimpl->m_threshold_level = lev;
}

//____________________________________________________________________________//

void
unit_test_log::set_log_threshold_level_by_name( const_string lev )
{
    static fixed_mapping<const_string,log_level> log_level_name(
        "all"           , log_successful_tests,
        "success"       , log_successful_tests,
        "test_suite"    , log_test_suites,
        "messages"      , log_messages,
        "warnings"      , log_warnings,
        "all_errors"    , log_all_errors,
        "cpp_exceptions", log_cpp_exception_errors,
        "system_errors" , log_system_errors,
        "fatal_errors"  , log_fatal_errors,
        "progress"      , log_progress_only,
        "nothing"       , log_nothing,

        invalid_log_level
    );

    if( m_pimpl->m_entry_in_progress )
        return;

    set_log_threshold_level( log_level_name[lev] );
}

//____________________________________________________________________________//

void
unit_test_log::clear_checkpoint()
{
    m_pimpl->clear_checkpoint();
}

//____________________________________________________________________________//

void
unit_test_log::track_test_case_scope( test_case const& tc, bool in_out )
{
    if( m_pimpl->m_threshold_level > log_test_suites )
        return;

    *this << begin();

    m_pimpl->m_log_formatter->track_test_case_scope( m_pimpl->stream(), tc, in_out );
    m_pimpl->m_entry_has_value = true;

    *this << end();
}

//____________________________________________________________________________//

unit_test_log&
unit_test_log::operator<<( begin const& )
{
    if( m_pimpl->m_entry_in_progress )
        *this << end();

    m_pimpl->m_entry_in_progress = true;

    return *this;
}

//____________________________________________________________________________//

unit_test_log&
unit_test_log::operator<<( end const& )
{
    if( m_pimpl->m_entry_has_value ) {
        m_pimpl->m_log_formatter->end_log_entry( m_pimpl->stream() );
        m_pimpl->flush_entry();
    }

    m_pimpl->clear_entry_data();

    return *this;
}

//____________________________________________________________________________//

char
set_unix_slash( char in )
{
    return in == '\\' ? '/' : in;
}

unit_test_log&
unit_test_log::operator<<( file const& f )
{
    if( m_pimpl->m_entry_in_progress ) {
        f.m_file_name.assign_to( m_pimpl->m_entry_data.m_file );

        // normalize file name
        std::transform( m_pimpl->m_entry_data.m_file.begin(), m_pimpl->m_entry_data.m_file.end(), 
                        m_pimpl->m_entry_data.m_file.begin(),
                        &set_unix_slash );
    }

    return *this;
}

//____________________________________________________________________________//

unit_test_log&
unit_test_log::operator<<( line const& l )
{
    if( m_pimpl->m_entry_in_progress )
        m_pimpl->m_entry_data.m_line = l.m_line_num;

    return *this;
}

//____________________________________________________________________________//

unit_test_log&
unit_test_log::operator<<( level const& lev )
{
    if( m_pimpl->m_entry_in_progress )
        m_pimpl->m_entry_data.m_level = lev.m_level;

    return *this;
}

//____________________________________________________________________________//

unit_test_log&
unit_test_log::operator<<( checkpoint const& cp )
{
    if( m_pimpl->m_entry_in_progress )
        m_pimpl->set_checkpoint( cp );

    return *this;
}

//____________________________________________________________________________//

unit_test_log&
unit_test_log::operator<<( log_exception const& re )
{
    if( m_pimpl->m_entry_in_progress && m_pimpl->m_entry_data.m_level >= m_pimpl->m_threshold_level ) {
        m_pimpl->m_log_formatter->log_exception( m_pimpl->stream(), unit_test_result::instance().test_case_name(), re.m_what );
        m_pimpl->m_entry_has_value = true;
    }

    return *this;
}

//____________________________________________________________________________//

unit_test_log&
unit_test_log::operator<<( log_progress const& )
{
    if( m_pimpl->m_progress_display )
        ++(*m_pimpl->m_progress_display);

    return *this;
}

//____________________________________________________________________________//

unit_test_log&
unit_test_log::operator<<( const_string value )
{
    if( m_pimpl->m_entry_in_progress && m_pimpl->m_entry_data.m_level >= m_pimpl->m_threshold_level && !value.empty() ) {
        if( !m_pimpl->m_entry_has_value ) {
            switch( m_pimpl->m_entry_data.m_level ) {
            case log_successful_tests:
                m_pimpl->m_log_formatter->begin_log_entry( m_pimpl->stream(), unit_test_log_formatter::BOOST_UTL_ET_INFO );
                break;
            case log_messages:
                m_pimpl->m_log_formatter->begin_log_entry( m_pimpl->stream(), unit_test_log_formatter::BOOST_UTL_ET_MESSAGE );
                break;
            case log_warnings:
                m_pimpl->m_log_formatter->begin_log_entry( m_pimpl->stream(), unit_test_log_formatter::BOOST_UTL_ET_WARNING );
                break;
            case log_all_errors:
            case log_cpp_exception_errors:
            case log_system_errors:
                m_pimpl->m_log_formatter->begin_log_entry( m_pimpl->stream(), unit_test_log_formatter::BOOST_UTL_ET_ERROR );
                break;
            case log_fatal_errors:
                m_pimpl->m_log_formatter->begin_log_entry( m_pimpl->stream(), unit_test_log_formatter::BOOST_UTL_ET_FATAL_ERROR );
                break;
            case log_progress_only:
            case log_nothing:
            case log_test_suites:
            case invalid_log_level:
                return *this;
            }
        }

        m_pimpl->m_log_formatter->log_entry_value( m_pimpl->stream(), value );
        m_pimpl->m_entry_has_value = true;
    }

    return *this;
}

//____________________________________________________________________________//

void
unit_test_log::start( bool print_build_info )
{
    m_pimpl->m_log_formatter->start_log( m_pimpl->stream(), print_build_info );
}

//____________________________________________________________________________//

void
unit_test_log::header( unit_test_counter test_cases_amount )
{
    if( m_pimpl->m_threshold_level != log_nothing && test_cases_amount > 0 )
        m_pimpl->m_log_formatter->log_header( m_pimpl->stream(), test_cases_amount );

    if( m_pimpl->m_threshold_level == log_progress_only )
        m_pimpl->m_progress_display.reset(
            new boost::progress_display( test_cases_amount, m_pimpl->stream() ) );
    else
        m_pimpl->m_progress_display.reset();
}

//____________________________________________________________________________//

void
unit_test_log::finish( unit_test_counter test_cases_amount )
{
    if( test_cases_amount == 1 )
        *this << log_progress();

    m_pimpl->m_log_formatter->finish_log( m_pimpl->stream() );
}

//____________________________________________________________________________//

void
unit_test_log::set_log_format( const_string log_format_name )
{
    if( m_pimpl->m_entry_in_progress )
        return;

    static fixed_mapping<const_string,output_format,case_ins_less<char const> > log_format(
        "HRF", HRF,
        "XML", XML,

        HRF
        );

    if( log_format[log_format_name] == HRF )
        set_log_formatter( new ut_detail::msvc65_like_log_formatter( *this ) );
    else
        set_log_formatter( new ut_detail::xml_log_formatter( *this ) );
}

//____________________________________________________________________________//

void
unit_test_log::set_log_formatter( unit_test_log_formatter* the_formatter )
{
    m_pimpl->m_log_formatter.reset( the_formatter );
}

//____________________________________________________________________________//

log_entry_data const&
unit_test_log::entry_data() const
{
    return m_pimpl->m_entry_data;
}

//____________________________________________________________________________//

log_checkpoint_data const&
unit_test_log::checkpoint_data() const
{
    return m_pimpl->m_checkpoint_data;
}

//____________________________________________________________________________//

} // namespace unit_test

} // namespace boost

// ***************************************************************************
//  Revision History :
//
//  $Log: unit_test_log.cpp,v $
//  Revision 1.24  2004/06/07 07:34:22  rogeeff
//  detail namespace renamed
//
//  Revision 1.23  2004/05/27 06:29:59  rogeeff
//  eliminate  warnings
//
//  Revision 1.22  2004/05/21 06:26:09  rogeeff
//  licence update
//
//  Revision 1.21  2004/05/13 09:04:43  rogeeff
//  added fixed_mapping
//
//  Revision 1.20  2004/05/11 11:04:44  rogeeff
//  basic_cstring introduced and used everywhere
//  class properties reworked
//  namespace names shortened
//
//  Revision 1.19  2003/12/01 00:42:37  rogeeff
//  prerelease cleaning
//

// ***************************************************************************

// EOF
