#ifndef PHYCPP_UTILITY_UNIQUELYLABELLEDOBJECT_HPP
#define PHYCPP_UTILITY_UNIQUELYLABELLEDOBJECT_HPP

#include <map>
#include <vector>

// ******************************************** //
//           Uniquely Labelled Object           //
// ******************************************** //

#define PHYCPP_DECLARE_UNIQUELY_LABELLED friend class PhyCpp::Detail::ObjectManagement;

namespace PhyCpp {

template <typename LabelT, typename ObjectT>
class UniquelyLabelledObjectManager;

template <typename LabelT, typename ObjectT>
class UniquelyLabelledObject {

	friend class UniquelyLabelledObjectManager<LabelT, ObjectT>;

public:

	virtual ~UniquelyLabelledObject() {
	}

	inline bool setLabel(const LabelT & label) {
		return labelManager->setLabel((ObjectT*)this, label);
	}

	inline const LabelT & label() const {
		return labelValue;
	}

protected:

	inline UniquelyLabelledObject(const LabelT & label, UniquelyLabelledObjectManager<LabelT, ObjectT> * manager)
	: labelValue(label), labelManager(manager) {
	}

private:

	LabelT labelValue;
	UniquelyLabelledObjectManager<LabelT, ObjectT> * labelManager;
};

namespace Detail {

class ObjectManagement {

	template <typename, typename> friend class PhyCpp::UniquelyLabelledObjectManager;

	template <typename LabelT, typename ObjT, typename ObjManagerT>
	static inline ObjT * construct(const LabelT & label, ObjManagerT * manager) {
		return new ObjT(label, manager);
	}
};

}

template <typename LabelT, typename ObjectT>
class UniquelyLabelledObjectManager {

	friend class UniquelyLabelledObject<LabelT, ObjectT>;

public:

	typedef typename std::map<LabelT, ObjectT *>::const_iterator const_iterator;
	typedef typename std::map<LabelT, ObjectT *>::const_iterator iterator;

	UniquelyLabelledObjectManager() {
	}

	virtual ~UniquelyLabelledObjectManager() {
		typename std::map<LabelT, ObjectT*>::iterator it = labelMap.begin();
		while (it != labelMap.end()) {
			delete it->second;
			++it;
		}
	}

	inline unsigned int size() const {
		return labelMap.size();
	}

	inline typename std::map<LabelT, ObjectT *>::const_iterator begin() const {
		return labelMap.begin();
	}

	inline typename std::map<LabelT, ObjectT *>::const_iterator end() const {
		return labelMap.end();
	}

	inline typename std::map<LabelT, ObjectT *>::iterator begin()  {
		return labelMap.begin();
	}

	inline typename std::map<LabelT, ObjectT *>::iterator end()  {
		return labelMap.end();
	}

	inline ObjectT * find(const LabelT & label) {
		typename std::map<LabelT, ObjectT*>::iterator it = labelMap.find(label);
		return (it == labelMap.end()) ? 0 : it->second;
	}

	inline const ObjectT * find(const LabelT & label) const {
		typename std::map<LabelT, ObjectT*>::const_iterator it = labelMap.find(label);
		return (it == labelMap.end()) ? 0 : it->second;
	}

	ObjectT * insert(const LabelT & label) {
		std::pair<typename std::map<LabelT, ObjectT*>::iterator, bool> r = labelMap.insert(std::make_pair<>(label, (ObjectT*)0));
		if (r.second) {
			ObjectT * obj = Detail::ObjectManagement::template construct<LabelT, ObjectT, UniquelyLabelledObjectManager<LabelT, ObjectT> >(label, this);
			r.first->second = obj;
			return obj;
		} else {
			return r.first->second;
		}
	}

private:

	bool setLabel(ObjectT * object, const LabelT & label) {
		typename std::map<LabelT, ObjectT*>::iterator it = labelMap.find(object->labelValue);
		if (it->second != object || it == labelMap.end() || labelMap.find(label) != labelMap.end())
			return false;
		labelMap.erase(it);
		labelMap.insert(std::make_pair<>(label, object));
		object->labelValue = label;
		return true;
	}

	std::map<LabelT, ObjectT *> labelMap;

};

}

// ****************************************** //
//           Simple Labelled Object           //
// ****************************************** //

namespace PhyCpp {

template <typename LabelT>
class SimpleLabelledObject {

public:

	inline SimpleLabelledObject(const LabelT & label = "")
	: labelValue(label) {
	}

	virtual ~SimpleLabelledObject() {
	}

	inline const LabelT & label() const {
		return labelValue;
	}

	inline void setLabel(const LabelT & label) {
		labelValue = label;
	}

private:

	LabelT labelValue;
};

template <>
class SimpleLabelledObject<void> {

public:

	inline SimpleLabelledObject() {
	}

	virtual ~SimpleLabelledObject() {
	}
};

}

#endif // PHYCPP_UTILITY_UNIQUELYLABELLEDOBJECT_HPP
