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

#ifndef LOG_DETAIL_INFOHOLDER_INCLUDED
#define LOG_DETAIL_INFOHOLDER_INCLUDED

#include "TypeInfo.h"

#include <cassert>
#include <map>

#ifdef _MSC_VER
#include <memory>
#include <unordered_map>
#else
#include <tr1/memory>
#include <tr1/unordered_map>
#endif

namespace Log {
namespace Detail {

// forward declarations
class InfoTypeBase;
template < typename, typename , typename > class InfoType;

/**
 * @class InfoHolder
 * @brief Base class that allows storage of any number of InfoType objects
 *
 * This class allows the storage of arbitrary data objects, they are
 * instances of InfoType classes or one if their subclasses. It works by
 * keeping a map of TypeInfo and InfoTypeBase objects. The actual InfoType
 * value is accessed by a templated method that must provide the real type
 * in order to retrieve it from the map by looking up the @c typeid.
 *
 * @note InfoType and InfoHolder are based on a technique demonstrated
 * by boost::error_info.
 */
template < typename DerivedT >
class InfoHolder
{
public:
	// NOTE: using InfoTypeBase const* as map value would require InfoHolder
	// to be non-copyable to avoid multiple deletion in the destructor,
	// another (inefficient) option would be deep copying of the map

	typedef DerivedT DerivedType;

	/// Map type to hold pointers to InfoType objects, indexed by their TypeInfo
//	typedef std::map< TypeInfo, std::tr1::shared_ptr< InfoTypeBase const > > InfoMap;
	typedef std::tr1::unordered_map< TypeInfo, std::tr1::shared_ptr< InfoTypeBase const > > InfoMap;

	// Compiler-generated ctor, copy, and assignment is o.k.

	/// Destruction
	virtual ~InfoHolder() {}

	/// Overloaded @c operator+ provides the means to add InfoType objects to InfoHolder
	template < typename InfoT >
	friend
	DerivedType const&
	operator+( DerivedType const& holder, InfoT const& value )
	{
		// copy-construct a dynamically allocated Info object from value
		// and store it in a base class pointer
		std::tr1::shared_ptr< InfoTypeBase const > data( new InfoT( value ) );
		holder.add( data, typeid( InfoT ) );
		return holder;
	}

	/// Retrieval of InfoType objects via template method, @c InfoT must be specified
	template < typename InfoT >
	typename InfoT::Type const*
	get() const
	{
		typedef InfoT Info;

 		std::tr1::shared_ptr< InfoTypeBase const > const&
 				basePtr = getInfoByType( typeid( Info ) );

		// the returned basePtr object may be empty if type not found
		if( ! basePtr )
		{
			return 0;
		}

		// retrieve the actual info type and return its value
		Info const* data = static_cast< Info const* >( basePtr.get() );
		return data->value();
	}

private:
	// HACK: declared mutable to allow use of const temporaries with op+ above
	/// Internal InfoType object storage, indexed by TypeInfo
	mutable InfoMap infos_;

private:
	/// Internal method to add an InfoType object to the InfoHolder
 	void add( std::tr1::shared_ptr< InfoTypeBase const > const& data,
 			TypeInfo const& typeInfo ) const
	{
		//assert( data );
		// existing entries will be replaced and automatically deleted
		infos_[ typeInfo ] = data;
	}

	/// Internal method for retrieval of a stored InfoType object
	std::tr1::shared_ptr< InfoTypeBase const > const&
	getInfoByType( TypeInfo const& typeInfo ) const
	{
		InfoMap::const_iterator found = infos_.find( typeInfo );
		if( found != infos_.end() )
		{
			return found->second;
		}
		// this allows us to always return a reference
		static std::tr1::shared_ptr< InfoTypeBase const > const emptyPtr;
		return emptyPtr;
	}
};

} } // namespace Log::Detail

#endif /* LOG_DETAIL_INFOHOLDER_INCLUDED */
