/*
 * (C) Copyright 1996- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

// File ClassExtent.h
// Baudouin Raoult - ECMWF Jul 96

#ifndef eckit_ClassExtent_h
#define eckit_ClassExtent_h

#include <map>

#include "eckit/thread/AutoLock.h"
#include "eckit/exception/Exceptions.h"
#include "eckit/thread/Mutex.h"
#include "eckit/memory/NonCopyable.h"



namespace eckit {

//-----------------------------------------------------------------------------

template<class T>
class ClassExtent : private NonCopyable {
public:

// -- Contructors

	ClassExtent(T*);

// -- Destructor

	~ClassExtent();

// -- Methods

	static size_t size();

public: // methods

	static void callAll(void (T::*)());
	static void callAll(void (T::*)() const);

	template<class P>
	static void callAll(void (T::*)(P),P);

	template<class P>
	static void callAll(void (T::*)(P) const,P);

	template<class P1,class P2>
	static void callAll(void (T::*)(P1,P2),P1,P2);

	template<class P>
	static void callAll(void (T::*)(P&) const,P&);

	template<class P>
	static void callAll(void (T::*)(P&),P&);

	template<class P1,class P2>
	static void callAll(void (T::*)(P1&,P2&),P1&,P2&);

private: // members

	struct Extent {
        typedef std::map<ClassExtent<T>*,T*,std::less<ClassExtent<T>*> > Map;
		Mutex   mutex_;
		Map     map_;
		bool    inited_;
		Extent();
		~Extent();
	};

	static Extent extent_;

};

//-----------------------------------------------------------------------------

// We assume that global-initialisation is single threaded

template<class T>
typename ClassExtent<T>::Extent ClassExtent<T>::extent_;

template<class T>
ClassExtent<T>::ClassExtent(T* obj)
{
	ASSERT(extent_.inited_);
	AutoLock<Mutex> lock(extent_.mutex_);
	extent_.map_[this] = obj;
}

template<class T>
ClassExtent<T>::~ClassExtent()
{

	if(extent_.inited_)  // This can be after exit() is called
						 // I need to find a solution
	{

	ASSERT(extent_.inited_);

	AutoLock<Mutex> lock(extent_.mutex_);
	ASSERT(extent_.map_.find(this) != extent_.map_.end());
	extent_.map_.erase(this);
	}
}

template<class T>
size_t ClassExtent<T>::size()
{
	ASSERT(extent_.inited_);
	AutoLock<Mutex> lock(extent_.mutex_);
	return extent_.map_.size();
}

template<class T>
void ClassExtent<T>::callAll(void (T::*proc)())
{
	ASSERT(extent_.inited_);
	AutoLock<Mutex> lock(extent_.mutex_);

    // Make a copy to cater for object that are deleted during the loop
    typename ClassExtent<T>::Extent::Map map = extent_.map_;
	// for(ClassExtent<T>::Extent::Map::iterator i = extent_.map_.begin();
	typedef  typename ClassExtent<T>::Extent::Map map_type;
	typename map_type::iterator i;
    for(i = map.begin(); i != map.end() ; ++i)
        ((*i).second->*proc)();

}

template<class T>
void ClassExtent<T>::callAll(void (T::*proc)() const)
{
	ASSERT(extent_.inited_);
	AutoLock<Mutex> lock(extent_.mutex_);

	// for(ClassExtent<T>::Extent::Map::iterator i = extent_.map_.begin();
	typedef  typename ClassExtent<T>::Extent::Map map_type;
	typename map_type::iterator i;
	for(i = extent_.map_.begin();
		i != extent_.map_.end() ; ++i)
		((*i).second->*proc)();
}

template<class T> template<class P>
void ClassExtent<T>::callAll(void (T::*proc)(P),P arg)
{
	ASSERT(extent_.inited_);
	AutoLock<Mutex> lock(extent_.mutex_);

    // Make a copy to cater for object that are deleted during the loop
    typename ClassExtent<T>::Extent::Map map = extent_.map_;
	// for(ClassExtent<T>::Extent::Map::iterator i = extent_.map_.begin();
	typedef  typename ClassExtent<T>::Extent::Map map_type;
	typename map_type::iterator i;
    for(i = map.begin(); i != map.end() ; ++i)
		((*i).second->*proc)(arg);
}

template<class T> template<class P>
void ClassExtent<T>::callAll(void (T::*proc)(P) const,P arg)
{
	ASSERT(extent_.inited_);
	AutoLock<Mutex> lock(extent_.mutex_);

	// for(ClassExtent<T>::Extent::Map::iterator i = extent_.map_.begin();
	typedef  typename ClassExtent<T>::Extent::Map map_type;
	typename map_type::iterator i;
	for(i = extent_.map_.begin();
		i != extent_.map_.end() ; ++i)
		((*i).second->*proc)(arg);
}

template<class T> template<class P1,class P2>
void ClassExtent<T>::callAll(void (T::*proc)(P1,P2),P1 arg1,P2  arg2)
{
	ASSERT(extent_.inited_);
	AutoLock<Mutex> lock(extent_.mutex_);

    // Make a copy to cater for object that are deleted during the loop
    typename ClassExtent<T>::Extent::Map map = extent_.map_;
	// for(ClassExtent<T>::Extent::Map::iterator i = extent_.map_.begin();
	typedef  typename ClassExtent<T>::Extent::Map map_type;
	typename map_type::iterator i;
    for(i = map.begin(); i != map.end() ; ++i)
		((*i).second->*proc)(arg1,arg2);
}

template<class T> template<class P>
void ClassExtent<T>::callAll(void (T::*proc)(P&),P& arg)
{
	ASSERT(extent_.inited_);
	AutoLock<Mutex> lock(extent_.mutex_);

	// for(ClassExtent<T>::Extent::Map::iterator i = extent_.map_.begin();
	typedef  typename ClassExtent<T>::Extent::Map map_type;
	typename map_type::iterator i;
	for(i = extent_.map_.begin();
		i != extent_.map_.end() ; ++i)
		((*i).second->*proc)(arg);
}

template<class T> template<class P>
void ClassExtent<T>::callAll(void (T::*proc)(P&) const,P& arg)
{
	ASSERT(extent_.inited_);
	AutoLock<Mutex> lock(extent_.mutex_);

	// for(ClassExtent<T>::Extent::Map::iterator i = extent_.map_.begin();
	typedef  typename ClassExtent<T>::Extent::Map map_type;
	typename map_type::iterator i;
	for(i = extent_.map_.begin();
		i != extent_.map_.end() ; ++i)
		((*i).second->*proc)(arg);
}

template<class T> template<class P1,class P2>
void ClassExtent<T>::callAll(void (T::*proc)(P1&,P2&),P1& arg1,P2&  arg2)
{
	ASSERT(extent_.inited_);
	AutoLock<Mutex> lock(extent_.mutex_);

    // Make a copy to cater for object that are deleted during the loop
    typename ClassExtent<T>::Extent::Map map = extent_.map_;
	// for(ClassExtent<T>::Extent::Map::iterator i = extent_.map_.begin();
	typedef  typename ClassExtent<T>::Extent::Map map_type;
	typename map_type::iterator i;
    for(i = map.begin(); i != map.end() ; ++i)
		((*i).second->*proc)(arg1,arg2);
}

template<class T>
ClassExtent<T>::Extent::Extent():
	inited_(true)
{
}

template<class T>
ClassExtent<T>::Extent::~Extent()
{
	inited_ = false;
}

//-----------------------------------------------------------------------------

} // namespace eckit

#endif
