//
// Copyright (c) 2009-2010 Ronald Kluth
//
// 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)
//

/**
 * @file Channel.h
 * @author Ronald Kluth
 * @date 2009/02/08
 * @brief Declaration and implementation of class template Channel and operator<<
 * @since 0.1
 */

#ifndef LOG_CHANNEL_INCLUDED
#define LOG_CHANNEL_INCLUDED

#include "ChannelId.h"
#include "Consumer.h"
#include "Filter.h"

#include <cassert>
#include <set>
#ifdef _MSC_VER
#include <memory>
#else
#include <tr1/memory>
#endif


namespace Log {

/**
 * @brief Class template for log channels that forward records to registered consumers
 * @tparam RecordT Type of the log records to be used, the default is @c Record
 *
 * A channel provides the means to transport log records from any source to
 * any number of consumers. Channels are used in a stream-like manner via an
 * overloaded @c operator<<. Besides the registration of consumers, channels
 * also allow for a filter to be set.
 *
 * Multiple channel objects of the same type can be created and controlled
 * by a manager object to ensure they have the same lifetime, or to access
 * several channels simultaneously when adding or removing consumers and
 * record filters.
 *
 * Usage:
 * @code
 * Channel< std::string > channel;
 * channel.addConsumer( con1 );
 * channel.addConsumer( con2 );
 * channel << std::string( "example message" );
 * @endcode
 *
 * In the above usage example, the message is forwarded to both registered
 * consumers @c con1 and @c con2.
 * 
 * @note This class is not intended to be inherited.
 * @see ChannelId, Consumer, Filter, ChannelManager, Log::operator<<
 */
template < typename RecordT = Record >
class Channel
{
public:
	/// Type of the records to be logged
	typedef RecordT RecordType;
	/// Pointer type to manage registered log record consumers
	typedef std::tr1::shared_ptr< Consumer< RecordType > > ConsumerPtr;
	/// Set type to store pointers to record consumers
	typedef std::set< ConsumerPtr > ConsumerSet;
	/// Pointer type to manage RecordFilter objects
	typedef std::tr1::shared_ptr< Filter< RecordType > > FilterPtr;

	/**
	 * @brief Construction with ID, or default construction with -1
	 *
	 * Channels may specify an ID in order to allow consumers to identity
	 * which channel forwarded a record.
	 */
	explicit Channel( ChannelId const channelId = -1 )
	:	id_( channelId )
	,	filter_()
	,	consumers_()
	{
	}

	// Compiler-generated dtor o.k.

	/**
	 * @brief Get the channel's ID value set during contruction
	 *
	 * Channel IDs can be used by consumers to identify the channel that
	 * forwarded log records. The default setting is -1, meaning the channel
	 * was not given an ID.
	 *
	 * @return ID of the channel object, or -1 if not set
	 */
	ChannelId const getId() const
	{
		return id_;
	}

	/**
	 * @brief Get access to the set of registered log record consumers
	 *
	 * This method is used by @c operator<< to iterate over all registered
	 * consumers in order to forward the log record via the @c Consumer
	 * interface.
	 *
	 * @return Reference to the set of consumers
	 * @see Consumer
	 */
	ConsumerSet const& getConsumers() const
	{
		return consumers_;
	}

	/**
	 * @brief Check whether the filter is set
	 * @return @c true if the @c shared_ptr holding the filter is not empty
	 */
	bool hasFilter() const
	{
		return !!filter_;
	}

	/**
	 * @brief Get a pointer to the channel's record filter
	 * @return A copy of the @c shared_ptr holding the filter
	 */
	FilterPtr getFilter() const
	{
		return filter_;
	}

	/**
	 * @brief Set a (new) record filter for the channel
	 * @param filter @c shared_ptr to a filter, must not be empty
	 *
	 * This method resets the filter pointer to the given parameter.
	 * A channel may only hold one filter. Thus, calling this method replaces
	 * the previous filter, if one was set.
	 */
	void setFilter( FilterPtr filter )
	{
		assert( filter );
		filter_ = filter;
	}

	/**
	 * @brief Reset the record filter
	 *
	 * This method resets the @c shared_ptr managing the filter. If the channel
	 * holds the last reference to the filter, it will be automatically deleted.
	 */
	void resetFilter()
	{
		filter_.reset();
	}

	/**
	 * @brief Add another consumer to the channel
	 * @param consumer @c shared_ptr to a consumer, must not be empty
	 *
	 * This method adds a new consumer to the channel's set. Consumer objects
	 * are managed by @c shared_ptr so their existence is ensured until the last
	 * channel holding a reference to them is destroyed.
	 *
	 * @retval true if the consumer was inserted successfully
	 * @retval false if the consumer was already registered
	 */
	bool addConsumer( ConsumerPtr consumer )
	{
		assert( consumer );
		return ( consumers_.insert( consumer ).second );
	}

	/**
	 * @brief Remove a consumer from the channel's set
	 * @param consumer @c shared_ptr to a consumer, must not be empty
	 *
	 * This method removes the given consumer from the channel's set,
	 * thereby decreasing its @c shared_ptr reference count by one. If the
	 * channel held the last reference to the consumer, the object is
	 * automatically deleted.
	 *
	 * @retval true if the consumer was found and erased
	 * @retval false if the consumer was not found in the set
	 */
	bool removeConsumer( ConsumerPtr consumer )
	{
		assert( consumer );
		return ( consumers_.erase( consumer ) > 0 );
	}

	/**
	 * @brief Remove all consumers from the channel
	 *
	 * This method clears the set of registered consumers, which effectively
	 * disables a channel because no records can be forwarded via @c operator<<
	 * anymore.
	 */
	void removeConsumers()
	{
		consumers_.clear();
	}

private:
	/// Constant member to identify this channel object
	ChannelId const id_;
	/// Pointer to a record filter object associated with this channel
	FilterPtr filter_;
	/// Container of registered consumers which receive all records forwarded by this channel
	ConsumerSet consumers_;

	/// Non-copyable
	Channel( Channel const& );
	/// Non-assignable
	Channel& operator=( Channel const& );
};

/**
 * @brief Inserter overload for stream-like use of log channels
 * @tparam RecordT Type of the records to be forwarded by the channel
 *
 * This overload of the insert operator provides the means to forward
 * any kind of log records via any matching channel. The channel is checked
 * for a filter, and if one is set, the given record must pass it in order to
 * be forwarded to all the channel's consumers.
 *
 * @see Channel, Consumer
 */
template < typename RecordT >
inline
Channel< RecordT > const&
operator<<( Channel< RecordT > const& channel, RecordT const& record )
{
	// check if the channel is filtered and if the record may pass
	if( ! channel.hasFilter() || channel.getFilter()->pass( record ) )
	{
		// pass the record on to the channel's consumers
		// by calling consume on each one with channel ID and record
		for( typename Channel< RecordT >::ConsumerSet::const_iterator
			iter = channel.getConsumers().begin();
			iter != channel.getConsumers().end();
			++iter )
		{
			( *iter )->consume( channel.getId(), record );
		}
	}
	return channel;
}

/**
 * @brief Inserter overload for @c shared_ptr to channel objects
 * @tparam RecordT Type of the records to be forwarded by the channel
 *
 * This overload of the insert operator logs data only if the @c shared_ptr
 * is set. This makes it possible for record producers to enable/disable
 * logging dynamically by simply setting or resetting a @c shared_ptr to
 * the channel. Using a @c shared_ptr to manage channel access can also be
 * useful if log records are expensive to create. The pointer can be checked
 * by a condition so that the insertion statement is only executed when the
 * pointer is actually set.
 *
 * @note This operator uses the other overload for references to channels.
 * @see Channel, Consumer, Log::operator<<
 */
template < typename RecordT >
inline
std::tr1::shared_ptr< Channel< RecordT > > const
operator<<( std::tr1::shared_ptr< Channel< RecordT > > const channel, RecordT const& record )
{
	// log only if the channel is set, allows disabling logging per producer
	// because it can just reset its shared_ptr if logging is not needed
	if( channel )
	{
		*channel << record;
	}
	return channel;
}

} // namespace Log

#endif /* LOG_CHANNEL_INCLUDED */
