//
// 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 ChannelManager.h
 * @author Ronald Kluth
 * @date created at 2009/06/05
 * @brief Declaration and implementation of class template ChannelManager
 * @since 0.1
 */

#ifndef LOG_CHANNELMANAGER_INCLUDED
#define LOG_CHANNELMANAGER_INCLUDED

#include "ChannelId.h"
#include "NameScope.h"

#include <cassert>
#include <map>

#ifdef _MSC_VER
#include <memory>
#include <functional> // bind
#else
#include <tr1/memory>
#include <tr1/functional> // bind
#endif


namespace Log {

// forward declarations
template < typename > class Channel;
template < typename > class Consumer;
template < typename > class Filter;
class Record;

/**
 * @brief Manager template type that creates and manages log channels for producers
 * @tparam RecordT Type of the log records the channels can use
 *
 * A channel manager serves two purposes. Firstly, it can be used to
 * create multiple channels of the same type (i.e. channels using the
 * same record type) and simplify their consumer and filter management. 
 * Secondly, it may serve as name scope and channel provider for producer
 * classes.
 * 
 * Usage:
 * @code
 * ChannelManager< std::string > mgr;
 * shared_ptr< Channel< std::string > chan1 = mgr.getChannel( 1 );
 * shared_ptr< Channel< std::string > chan2 = mgr.getChannel( 2 );
 * mgr.addConsumer( con );
 * chan1 << std::string( "Channel 1 message" );
 * chan2 << std::string( "Channel 2 message" );
 * mgr.removeConsumer( 2, con );
 * @endcode
 * 
 * The above snippet shows the creation of a manager and the retrieval of two
 * pointers to log channels via @c getChannel. Since the manager does not yet
 * own channels with IDs 1 and 2, it creates them at the first request. Now 
 * that the channels exist, we can register a consumer with the manager, which
 * will pass them along to all of its currently owned channels. Hence, both
 * subsequently sent log records will be passed on to the consumer @c con.
 * The manager also allows to add or remove consumers for specific channels, 
 * as is shown in the last line.
 * 
 * @note Pairing incompatible producer and channel manager types
 * will most likely result in compile errors.
 * @see Producer, Channel, ChannelId, Consumer, Filter
 */
template < typename RecordT = Record >
class ChannelManager
:	public NameScope
{
public:
	/// The record type used by the log channels
	typedef RecordT RecordType;
	/// The channel type of the managed channels
	typedef Channel< RecordType > ChannelType;
	/// Pointer type to manage channels
	typedef std::tr1::shared_ptr< ChannelType > ChannelPtr;
	/// Pointer type to manage log consumers
	typedef std::tr1::shared_ptr< Consumer< RecordType > > ConsumerPtr;
	/// Pointer type to manage record filters
	typedef std::tr1::shared_ptr< Filter< RecordType > > FilterPtr;
	/// Map type to associate channel IDs with channel pointers
	typedef std::map< ChannelId, ChannelPtr > ChannelMap;

	/**
	 * @brief Default construction
	 *
	 * This constructor uses the default name and default space character 
	 * provided by class @c NameScope.
	 */
	ChannelManager()
	:	NameScope()
	,	channels_()
	{
	}

	/** 
	 * @brief Construction with user-defined default name and space
	 * 
	 * This constructor initializes the @c NameScope base class with a
	 * different default name and space character to use with named elements
	 * like producers.
	 */
	ChannelManager( std::string const& defLabel, char const defSpace )
	:	NameScope( defLabel, defSpace )
	,	channels_()
	{
	}

	/// Destruction
	virtual ~ChannelManager()
	{
	}

	/**
	 * @brief Get a pointer to a managed log channel by providing its ID
	 * @param id The ID of the requested channel
	 * 
	 * The manager first tries to find the requested channel in its map.
	 * If the channel does not exist yet, a new @c Channel object with the
	 * requested ID will be created and stored in the manager's map so that
	 * subsequent calls always return the same channel pointer.
	 */
	ChannelPtr getChannel( ChannelId const id )
	{
		// get the entry or create a new one
		typename ChannelMap::iterator found = channels_.find( id );

		// if the channel is set return it
		if( found != channels_.end() )
		{
			return found->second;
		}

		// create new channel object and make sure it's in the map
		std::pair< typename ChannelMap::iterator, bool > result =
				channels_.insert( typename ChannelMap::value_type(
						id, ChannelPtr( new ChannelType( id ) ) ) );
		assert( result.second );

		// return the new pointer
		return result.first->second;
	}

	/**
	 * @brief Add a log consumer to a managed log channel
	 * @param id ID of the channel where the consumer shall be registered
	 * @param consumer Pointer to the new consumer, must be valid
	 * 
	 * The manager will try to find the channel and register the new
	 * consumer.
	 * 
	 * @return @c true if the channel was found and the consumer registered
	 */
	bool addConsumer( ChannelId const id, ConsumerPtr consumer )
	{
		typename ChannelMap::iterator found = channels_.find( id );
		if( found != channels_.end() )
		{
			return found->second->addConsumer( consumer );
		}
		return false;
	}

	/**
	 * @brief Add a log consumer to all managed channels
	 * @param consumer Pointer to the new consumer, must be valid
	 * 
	 * The manager will iterate over all channels currently stored in its map
	 * and register the consumer with them.
	 */
	void addConsumer( ConsumerPtr consumer )
	{
		using namespace std::tr1::placeholders;
		iterateChannels(
				std::tr1::bind( &ChannelType::addConsumer, _1, consumer ) );
	}

	/**
	 * @brief Remove a log consumer from a managed channel
	 * @param id ID of the channel from which to remove the consumer
	 * @param consumer Pointer to the consumer
	 *
	 * The manager will try to find the channel and remove the existing
	 * consumer.
	 * 
	 * @return @c true if the channel was found and the consumer removed
	 */
	bool removeConsumer( ChannelId const id, ConsumerPtr consumer )
	{
		typename ChannelMap::iterator found = channels_.find( id );
		if( found != channels_.end() )
		{
			return found->second->removeConsumer( consumer );
		}
		return false;
	}

	/**
	 * @brief Remove a log consumer from all managed channels
	 * @param consumer Pointer to the consumer
	 *
	 * The manager will iterate over all channels currently stored in its map
	 * and remove the given consumer.
	 */
	void removeConsumer( ConsumerPtr consumer )
	{
		using namespace std::tr1::placeholders;
		iterateChannels(
				std::tr1::bind( &ChannelType::removeConsumer, _1, consumer ) );
	}

	/**
	 * @brief Set a record filter for a managed channel
	 * @param id ID of the channel on which to set the filter
	 * @param filter Pointer to the new filter
	 *
	 * The manager will try to find the channel and set the filter.
	 *
	 * @note This will replace the current filter of the channel.
	 * @return @c true if the channel was found and the filter set
	 */
	bool setFilter( ChannelId const id, FilterPtr filter )
	{
		typename ChannelMap::iterator found = channels_.find( id );
		if( found != channels_.end() )
		{
			found->second->setFilter( filter );
			return true;
		}
		return false;
	}

	/**
	 * @brief Set a record filter for all managed channels
	 * @param filter Pointer to the new filter
	 *
	 * The manager will iterate over all channels currently stored in its map
	 * and set the given filter.
	 *
	 * @note This will replace any filters set on the channels
	 */
	void setFilter( FilterPtr filter )
	{
		assert( filter );
		using namespace std::tr1::placeholders;
		iterateChannels(
				std::tr1::bind( &ChannelType::setFilter, _1, filter ) );
	}

	/**
	 * @brief Reset the record filter of a managed channel
	 * @param id ID of the channel on which to reset the filter
	 *
	 * The manager will try to find the channel and reset the filter.
	 *
	 * @return @c true if the channel was found and the filter reset
	 */
	bool resetFilter( ChannelId const id )
	{
		typename ChannelMap::iterator found = channels_.find( id );
		if( found != channels_.end() )
		{
			found->second->resetFilter();
			return true;
		}
		return false;
	}

	/**
	 * @brief Reset the record filter for all managed channels
	 *
	 * The manager will iterate over all channels currently stored in its map
	 * and reset their filters.
	 */
	void resetFilter()
	{
		using namespace std::tr1::placeholders;
		iterateChannels(
				std::tr1::bind( &ChannelType::resetFilter, _1 ) );
	}

private:
	/// Storage for channel pointers, indexed by the channel IDs
	ChannelMap channels_;

private:
	/// Non-copyable
	ChannelManager( ChannelManager const& );
	/// Non-assignable
	ChannelManager& operator=( ChannelManager const& );

	/**
	 * @brief Execute a callable entity on all managed channels
	 * @param func The callable entity to execute for all channels
	 * 
	 * This method template takes any callable entity as parameter, iterates 
	 * over the map of channels and calls @c func for each channel. Typically,
	 * @c func is a functor created by @c bind that calls a member function of 
	 * the channel.
	 */
	template < typename FuncT >
	void iterateChannels( FuncT const& func )
	{
		typename ChannelMap::iterator iter;
		for( iter = channels_.begin(); iter != channels_.end(); ++iter )
		{
			assert( iter->second );
			func( iter->second );
		}
	}
};

} // namespace Log

#endif /* LOG_CHANNELMANAGER_INCLUDED */
