/*
	Copyright 2014 Michele "King_DuckZ" Santullo

	This file is part of CloonelJump.

	CloonelJump is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	CloonelJump is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with CloonelJump.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef idF5E41734950640CDA3C949E0D3A9A30D
#define idF5E41734950640CDA3C949E0D3A9A30D

#include <cassert>
#include <vector>
#include <algorithm>
#include <iterator>
#include <ciso646>
#include <tree.hh>
#include <boost/iterator/transform_iterator.hpp>
#include <unordered_set>

#if defined (WITH_VERBOSE_OBS_MANAGER) && defined(__GNUC__) && __GNUC__ >= 2 && !defined(NDEBUG)
#	define OBS_MANAGER_LOG
#endif

#if defined(OBS_MANAGER_LOG)
#	include <iostream>
#endif

namespace cloonel {
	template <typename T>
	class ObserversManager {
	public:
		typedef size_t TicketType;

	private:
		struct TicketedWrapper {
			TicketedWrapper ( void ) = default;
			TicketedWrapper ( T parItem, TicketType parTicket ) :
				itm(parItem),
				ticket(parTicket)
			{
			}

			T itm;
			TicketType ticket;

			bool operator== ( const TicketedWrapper& parOther ) const { return parOther.ticket == ticket; }
			bool operator== ( TicketType parOther ) const { return parOther == ticket; }
		};
		static T& TicketedWrapperToItm ( TicketedWrapper& parWrapper ) { return parWrapper.itm; }

		typedef tree<TicketedWrapper> TreeType;
		typedef typename tree<TicketedWrapper>::iterator TreeIteratorType;

	public:
		enum {
			Ticket_Null = 0
		};
		typedef boost::transform_iterator<std::function<T&(TicketedWrapper&)>, TreeIteratorType> iterator;

		ObserversManager ( void );
		~ObserversManager ( void ) noexcept;

		TicketType Add ( T parObserver, TicketType parParent=Ticket_Null );
		void Remove ( TicketType parTicket ) noexcept;
		void RemoveAll ( void ) noexcept;
		void Update ( TicketType parTicket, T parObserver );
		std::size_t size ( void ) const { return m_tree.size(); }
		iterator begin ( void ) { return iterator(m_tree.begin(), &TicketedWrapperToItm); }
		iterator end ( void ) { return iterator(m_tree.end(), &TicketedWrapperToItm); }
		void CopyObservers ( std::unordered_set<T>& parOut );

	private:
		TreeIteratorType GetByTicket_AssertPresent ( TicketType parTicket );

		TreeType m_tree;
		TicketType m_nextTicket;
	};

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	template <typename T>
	ObserversManager<T>::ObserversManager() :
		m_nextTicket(1)
	{
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	template <typename T>
	ObserversManager<T>::~ObserversManager() noexcept {
		assert(m_tree.empty());
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	template <typename T>
	typename ObserversManager<T>::TicketType ObserversManager<T>::Add (T parObserver, TicketType parParent) {
		const auto currTicket = m_nextTicket;
		if (Ticket_Null == parParent)
			m_tree.insert(m_tree.begin(), TicketedWrapper(parObserver, currTicket));
		else
			m_tree.append_child(GetByTicket_AssertPresent(parParent), TicketedWrapper(parObserver, currTicket));

#if defined(OBS_MANAGER_LOG)
		std::cout << __PRETTY_FUNCTION__ << " registering " << currTicket << " as a child of " << parParent << "\n";
#endif
		++m_nextTicket;
		return currTicket;
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	template <typename T>
	void ObserversManager<T>::Remove (TicketType parTicket) noexcept {
		auto deleme = GetByTicket_AssertPresent(parTicket);
#if defined(OBS_MANAGER_LOG)
		for (auto it(deleme); it != m_tree.end(); ++it) {
			std::cout << __PRETTY_FUNCTION__ << " unregistering " << it->ticket << "\n";
		}
#endif
		m_tree.erase(deleme);
		if (parTicket == m_nextTicket - 1)
			--m_nextTicket;
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	template <typename T>
	void ObserversManager<T>::RemoveAll() noexcept {
		if (not m_tree.empty()) {
			for (typename TreeType::sibling_iterator it(m_tree.begin()), itEND(m_tree.end()); it != itEND; ++it) {
				const TicketType ticket = it->ticket;
				this->Remove(ticket);
			}
			assert(m_tree.empty());
		}
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	template <typename T>
	void ObserversManager<T>::Update (TicketType parTicket, T parObserver) {
		std::swap(GetByTicket_AssertPresent(parTicket)->itm, parObserver);
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	template <typename T>
	typename ObserversManager<T>::TreeIteratorType ObserversManager<T>::GetByTicket_AssertPresent (TicketType parTicket) {
		auto ret = std::find(m_tree.begin(), m_tree.end(), parTicket);
		assert(m_tree.end() != ret);
		return ret;
	}

	///--------------------------------------------------------------------------
	///--------------------------------------------------------------------------
	template <typename T>
	void ObserversManager<T>::CopyObservers (std::unordered_set<T>& parOut) {
		for (const auto& itm : m_tree) {
			parOut.insert(itm.itm);
		}
	}
} //namespace cloonel

#if defined(OBS_MANAGER_LOG)
#	undef OBS_MANAGER_LOG
#endif
#endif
