/**
 * @file Globals.h
 *
 * @brief Declarations of global helper functions and classes
 *
 * This file contains declarations of global helper functions which assist
 * testing. These function are implemented in ODEMxUnitTest.cpp.
 *
 */

#ifndef GLOBALS_H_
#define GLOBALS_H_

#include <UnitTest++.h>
#include <odemx/data/SimRecordFilter.h>
#include <CppLog/NamedElement.h>
#include <CppLog/Consumer.h>
#include <odemx/data/Producer.h>
#include <odemx/data/SimRecord.h>
#include <odemx/util/StringConversion.h>
#include <iostream>
#include <ostream>
#include <string>
#include <vector>

using Log::NamedElement;
using Log::Detail::typeToString;
using namespace odemx;

/**
 * @brief Get error stream to examine error and warning output.
 *
 * For testing purposes, the error stream \c cerr is redirected into an
 * \c ostringstream, which can be examined after running a test to see
 * how a function call responded to erroneous input. It is used to
 * check ODEMx warnings and error messages.
 *
 * @return Reference to a static error stream object
 */
extern std::ostringstream& errorStream();

/**
 * @brief Clear the error stream buffer
 *
 * This function is used to empty the error stream's buffer before running
 * tests which produce warnings or error messages, it allows for using
 * string comparisons after tests to check for the expected output message.
 */
extern void clearErrorStream();

/**
 * @brief A log consumer for checking log records sent by ODEMx components
 */
struct TestLogConsumer
:	public Log::Consumer< data::SimRecord >
{
	typedef std::tr1::shared_ptr< TestLogConsumer > Ptr;
	typedef std::multimap< Log::ChannelId, data::SimRecord > SimRecordMap;

	static Ptr create()
	{
		return Ptr( new TestLogConsumer() );
	}
	virtual void consume( const Log::ChannelId id, const data::SimRecord& record )
	{
		history_.insert( SimRecordMap::value_type( id, record ) );
	}
	void clear()
	{
		history_.clear();
	}
	data::SimRecord* getRecord( Log::ChannelId id, const data::StringLiteral& text,
			const data::TypeInfo& scope, const data::Label& sender = "" )
	{
		std::pair< SimRecordMap::iterator, SimRecordMap::iterator >
			range = history_.equal_range( id );

		for( SimRecordMap::iterator iter = range.first; iter != range.second; ++iter )
		{
			data::SimRecord* current = &iter->second;
			if( current->getText() == text && current->getScope() == scope )
			{
				if( ! sender.empty() && current->getSender().getLabel() != sender )
				{
					continue;
				}
				return current;
			}
		}
		return 0;
	}
	const Poco::Dynamic::Var* getRecordDetail( data::SimRecord* record, const data::StringLiteral& name )
	{
		if( record->hasDetails() )
		{
			const data::SimRecord::DetailVec& details = record->getDetails();
			for( data::SimRecord::DetailVec::const_iterator iter = details.begin();
				iter != details.end(); ++iter )
			{
				if( iter->first == name )
				{
					return &( iter->second );
				}
			}
		}
		return 0;
	}
	data::SimRecord* getTraceRecord( const data::StringLiteral& text, const data::TypeInfo& scope, const data::Label& sender = "" )
	{
		return getRecord( data::channel_id::trace, text, scope, sender );
	}
	data::SimRecord* getDebugRecord( const data::StringLiteral& text, const data::TypeInfo& scope, const data::Label& sender = "" )
	{
		return getRecord( data::channel_id::debug, text, scope, sender );
	}
	data::SimRecord* getInfoRecord( const data::StringLiteral& text, const data::TypeInfo& scope, const data::Label& sender = "" )
	{
		return getRecord( data::channel_id::info, text, scope, sender );
	}
	data::SimRecord* getWarningRecord( const data::StringLiteral& text, const data::TypeInfo& scope, const data::Label& sender = "" )
	{
		return getRecord( data::channel_id::warning, text, scope, sender );
	}
	data::SimRecord* getErrorRecord( const data::StringLiteral& text, const data::TypeInfo& scope, const data::Label& sender = "" )
	{
		return getRecord( data::channel_id::error, text, scope, sender );
	}
	data::SimRecord* getFatalRecord( const data::StringLiteral& text, const data::TypeInfo& scope, const data::Label& sender = "" )
	{
		return getRecord( data::channel_id::fatal, text, scope, sender );
	}
	data::SimRecord* getStatisticsRecord( const data::StringLiteral& type,
			const data::StringLiteral& name, const data::TypeInfo& scope, const data::Label& sender = "" )
	{
		std::pair< SimRecordMap::iterator, SimRecordMap::iterator >
			range = history_.equal_range( data::channel_id::statistics );

		for( SimRecordMap::iterator iter = range.first; iter != range.second; ++iter )
		{
			data::SimRecord* current = &iter->second;
			if( current->getText() == type
				&& current->getScope() == scope
				&& getRecordDetail( current, name ) )
			{
				if( ! sender.empty() && current->getSender().getLabel() != sender )
				{
					continue;
				}
				return current;
			}
		}
		return 0;
	}
	SimRecordMap history_;
};

/**
 * @struct Observation Globals.h
 *
 * @brief Type holding information about observed simulation events
 *
 * This type stores four public string members describing a simulation event.
 * If the event does not require any descriptive details, these two strings
 * will be empty. The members of this struct may need to be updated if
 * ODEMx observer functions have more than 4 parameters.
 */
struct Observation
{
	std::string event,		///< observed event name
				sender,		///< event sender
				detail1,	///< event detail 1
				detail2,	///< event detail 2
				detail3;	///< event detail 3
	/**
	 * @brief Constructor
	 * @param e event name, usually the name of the called Observer function
	 * @param s sender name, the label of the observed object
	 * @param d1 event detail, such as partner objects, time, attributes
	 * @param d2 event detail, such as partner objects, time, attributes
	 */
	Observation( const std::string& e, const std::string& s,
				 const std::string& d1, const std::string& d2,
				 const std::string& d3 ):
		event( e ),
		sender( s ),
		detail1( d1 ),
		detail2( d2 ),
		detail3( d3 )
		{}
};

/**
 * @class ObserverBase Globals.h
 *
 * @brief Provides basic functionality for all test observer classes
 *
 * This class provides basic functionality for all observer classes used during
 * unit testing. Its primary purpose is to store all observations in a history,
 * making it possible to verify the occurrence of all expected events during
 * calls of tested functions.
 */
class ObserverBase
{
public:
	/// History vector storing Observation objects
	std::vector< Observation > history;
	/// Boolean value to switch console output on and off
	bool writeToConsole;

	/**
	 * @brief Constructor
	 * @param output set to \c true to output observed events to the console
	 */
	ObserverBase( bool output ):
		writeToConsole( output )
		{}

	/**
	 * @brief Get the last observation
	 * @return pointer to the last Observation, or \c 0 if history is empty
	 */
	Observation* lastObservation()
	{
		return history.empty() ? 0 : &( history.back() );
	}

	/// Write last observation to stdout
	void print()
	{
		if( lastObservation() )
		{
			std::cout << lastObservation()->sender
				 << ": " << lastObservation()->event;

			if( ! lastObservation()->detail1.empty() )
				std::cout << " -- " << lastObservation()->detail1;

			if( ! lastObservation()->detail2.empty() )
				std::cout << " -- " << lastObservation()->detail2;

			if( ! lastObservation()->detail3.empty() )
				std::cout << " -- " << lastObservation()->detail3;

			std::cout << std::endl;
		}
		else
		{
			std::cout << "???" << std::endl;
		}
	}

	/// Add new Observation to history
	void saw( const std::string& event, const std::string& sender,
			  const std::string& det1 = "", const std::string& det2 = "",
			  const std::string& det3 = "" )
	{
		history.push_back( Observation( event, sender, det1, det2, det3 ) );
		if( writeToConsole ) print();
	}

	/// Add new Observation to history
	void saw( const std::string& event, const NamedElement* sender,
			  const std::string& det1 = "", const std::string& det2 = "",
			  const std::string& det3 = "" )
	{
		history.push_back( Observation( event,
				sender ? sender->getName() : "0",
				det1, det2, det3 ) );
		if( writeToConsole ) print();
	}

	/// Add new Observation to history
	void saw( const std::string& event, const NamedElement* sender,
			  const NamedElement* partner1, const NamedElement* partner2 = 0 )
	{
		history.push_back( Observation( event,
				sender ? sender->getName() : "0",
				partner1 ? partner1->getName() : "",
				partner2 ? partner2->getName() : "",
				"" ) );
		if( writeToConsole ) print();
	}

	/**
	 * @brief Determine whether an expected event was observed
	 * @return \c true if found in history, \c false otherwise
	 */
	bool observed( const std::string& event,
				   const std::string& sender,
				   const std::string& detail1 = "",
				   const std::string& detail2 = "",
				   const std::string& detail3 = "" )
	{
		std::vector< Observation >::reverse_iterator i;
		for( i = history.rbegin(); i != history.rend(); ++i )
		{
			// check for matching event
			if( ( *i ).event == event && ( *i ).sender == sender )
			{
				// skip this event if the details do not match
				if( ! detail1.empty() &&
					( *i ).detail1 != "" &&
					( *i ).detail1 != detail1 )
					continue;

				if( ! detail2.empty() &&
					( *i ).detail2 != "" &&
					( *i ).detail2 != detail2 )
					continue;

				if( ! detail3.empty() &&
					( *i ).detail3 != "" &&
					( *i ).detail3 != detail3 )
					continue;

				// only reached if the details match too
				return true;
			}
		}
		return false;
	}
};

#endif /*GLOBALS_H_*/
