/*
 * managed_linear_set.hpp
 *
 * This file is part of Algorithms library toolkit.
 * Copyright (C) 2017 Jan Travnicek (jan.travnicek@fit.cvut.cz)

 * Algorithms library toolkit 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.

 * Algorithms library toolkit 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 Algorithms library toolkit.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Created on: Sep 13, 2016
 * Author: Jan Travnicek
 */

#pragma once

#include <extensions/container/linear_set.hpp>
#include <extensions/algorithm.hpp>

namespace ext {

/**
 * \brief
 * Implementation of set mimicking the iterface of the standard library set. The inner representation is using sorted vector of unique value.
 *
 * \tparam T the type of stored values
 * \tparam Compare the less comparator used to determinine order between elements - defaults to std::less < T >
 * \tparam Alloc the allocator of elements - defaults to std::allocator < T >
 */
template < class T, class Compare = std::less<T>, class Alloc = std::allocator<T> >
class managed_linear_set {
	/**
	 * \brief
	 * The inner values holder.
	 */
	ext::linear_set < T, Compare, Alloc > m_data;

	std::vector < std::function < void ( const T & ) > > insertCallbacks;

	std::vector < std::function < void ( const T & ) > > removeCallbacks;

	void fireInsert ( const T & element ) const {
		for ( const std::function < void ( const T & ) > & callback : insertCallbacks ) {
			callback ( element );
		}
	}

	void fireRemove ( const T & element ) const {
		for ( const std::function < void ( const T & ) > & callback : removeCallbacks ) {
			callback ( element );
		}
	}

	/**
	 * \brief
	 * Equality test helper function
	 *
	 * \param first the first value
	 * \param second the second value
	 */
	bool eq ( const T & first, const T & second ) {
		return ! m_data.value_comp ( ) ( first, second ) && ! m_data.value_comp ( ) ( second, first );
	}

public:
	void addInsertCallback ( const std::function < void ( const T & ) > & callback ) {
		insertCallbacks.push_back ( callback );
	}

	void addRemoveCallback ( const std::function < void ( const T & ) > & callback ) {
		removeCallbacks.push_back ( callback );
	}

	/**
	 * \brief
	 * The type of values in the set.
	 */
	typedef T value_type;

	/**
	 * \brief
	 * The type of iterator over values in the set. It is the same as the underling vector's const iterator.
	 */
	typedef typename ext::vector < T, Alloc >::const_iterator iterator;

	/**
	 * \brief
	 * The type of const iterator over values in the set. It is the same as the underling vector's const iterator.
	 */
	typedef typename ext::vector < T, Alloc >::const_iterator const_iterator;

	/**
	 * \brief
	 * The type of reverse iterator over values in the set. It is the same as the underling vector's const reverse iterator.
	 */
	typedef typename ext::vector < T, Alloc >::const_reverse_iterator reverse_iterator;

	/**
	 * \brief
	 * The type of const reverse iterator over values in the set. It is the same as the underling vector's const reverse iterator.
	 */
	typedef typename ext::vector < T, Alloc >::const_reverse_iterator const_reverse_iterator;

	/**
	 * \brief
	 * Default constructor of the empty set.
	 *
	 * \param comp the instance of custom comparator or defaultly constructed comparator based on the comparator type
	 * \param alloc the instance of custom allocator or defaultly constructed allocator based on the comparator type
	 */
	explicit managed_linear_set (const Compare& comp = Compare(), const Alloc& alloc = Alloc()) : m_data ( comp, alloc ) {
	}

	/**
	 * \brief
	 * Constructor of the empty set with specified allocator.
	 *
	 * \param alloc the instance of custom allocator
	 */
	explicit managed_linear_set (const Alloc& alloc) : m_data ( alloc, Compare ( ) ) {
	}

	/**
	 * \brief
	 * Set constructor from a range of values.
	 *
	 * \param first the begining of the range of values
	 * \param last the end of the range of values
	 * \param comp the instance of custom comparator or defaultly constructed comparator based on the comparator type
	 * \param alloc the instance of custom allocator or defaultly constructed allocator based on the comparator type
	 */
	template <class InputIterator>
	managed_linear_set (InputIterator first, InputIterator last, const Compare& comp = Compare(), const Alloc& alloc = Alloc()) : m_data ( first, last, comp, alloc ) {
	}

	/**
	 * Constructor from range of values.
	 *
	 * \tparam Iterator the type of range iterator
	 *
	 * \param range the source range
	 */
	template < class Iterator >
	managed_linear_set ( const ext::iterator_range < Iterator > & range ) : managed_linear_set ( range.begin ( ), range.end ( ) ) {
	}

	/**
	 * \brief
	 * Copy constructor.
	 *
	 * \param x the other linear set instance
	 */
	managed_linear_set (const managed_linear_set& x) : m_data ( x.m_data ) {
	}

	/**
	 * \brief
	 * Copy constructor including allocator specification.
	 *
	 * \param x the other linear set instance
	 * \param alloc the new allocator instance
	 */
	managed_linear_set ( const managed_linear_set & x, const Alloc & alloc ) : m_data ( x.m_data, x.value_comp ( ), alloc ) {
	}

	/**
	 * \brief
	 * Move constructor.
	 *
	 * \param x the other linear set instance
	 */
	managed_linear_set ( managed_linear_set && x ) {
		for ( const T & elem : x ) {
			x.fireRemove ( elem );
		}

		m_data = std::move ( x.m_data );
	}

	/**
	 * \brief
	 * Move constructor including allocator specification.
	 *
	 * \param x the other linear set instance
	 * \param alloc the new allocator instance
	 */
	managed_linear_set (managed_linear_set&& x, const Alloc& alloc) : m_data ( x.value_comp ( ), alloc ) {
		for ( const T & elem : x ) {
			x.fireRemove ( elem );
		}

		m_data = std::move ( x.m_data );
	}

	/**
	 * \brief
	 * Set constructor from initializer list.
	 *
	 * \param il the source of values represented by initializer list
	 * \param comp the instance of custom comparator or defaultly constructed comparator based on the comparator type
	 * \param alloc the instance of custom allocator or defaultly constructed allocator based on the comparator type
	 */
	managed_linear_set (std::initializer_list<T> il, const Compare& comp = Compare(), const Alloc& alloc = Alloc()) : m_data ( std::move ( il ), comp, alloc ) {
	}

	/**
	 * \brief
	 * The destructor of the linear set.
	 */
	~managed_linear_set ( ) {
	}

	/**
	 * \brief
	 * Getter of an iterator to the begining of range of values in the container.
	 *
	 * \return begin iterator
	 */
	iterator begin() & noexcept {
		return m_data.begin ( );
	}

	/**
	 * \brief
	 * Getter of a const iterator to the begining of range of values in the container.
	 *
	 * \return begin const iterator
	 */
	const_iterator begin() const & noexcept {
		return m_data.begin ( );
	}

	/**
	 * \brief
	 * Getter of a move iterator to the begining of range of values in the container.
	 *
	 * \return begin move iterator
	 */
	auto begin ( ) && noexcept {
		return std::move ( * this ).begin ( );
	}

	/**
	 * \brief
	 * Getter of a const iterator to the begining of range of values in the container.
	 *
	 * \return begin const iterator
	 */
	const_iterator cbegin() const noexcept {
		return m_data.cbegin ( );
	}

	/**
	 * \brief
	 * Getter of an iterator to the end of range of values in the container.
	 *
	 * \return end iterator
	 */
	iterator end() & noexcept {
		return m_data.end ( );
	}

	/**
	 * \brief
	 * Getter of a const iterator to the end of range of values in the container.
	 *
	 * \return end const iterator
	 */
	const_iterator end() const & noexcept {
		return m_data.end ( );
	}

	/**
	 * \brief
	 * Getter of a move iterator to the end of range of values in the container.
	 *
	 * \return end move iterator
	 */
	auto end ( ) && noexcept {
		return std::move ( * this ).end ( );
	}

	/**
	 * \brief
	 * Getter of a const iterator to the end of range of values in the container.
	 *
	 * \return end const iterator
	 */
	const_iterator cend() const noexcept {
		return m_data.cend ( );
	}

	/**
	 * \brief
	 * Make range of non-const begin to end iterators.
	 *
	 * \return full range over container values
	 */
	auto range ( ) & {
		auto endIter = end ( );
		auto beginIter = begin ( );
		return ext::iterator_range < decltype ( endIter ) > ( beginIter, endIter );
	}

	/**
	 * \brief
	 * Make range of non-const begin to end iterators.
	 *
	 * \return full range over container values
	 */
	auto range ( ) const & {
		auto endIter = end ( );
		auto beginIter = begin ( );
		return ext::iterator_range < decltype ( endIter ) > ( beginIter, endIter );
	}

	/**
	 * \brief
	 * Make range of move begin to end iterators.
	 *
	 * \return full range over container values
	 */
	auto range ( ) && {
		auto endIter = std::move ( * this ).end ( );
		auto beginIter = std::move ( * this ).begin ( );
		return ext::iterator_range < decltype ( endIter ) > ( beginIter, endIter );
	}

	/**
	 * \brief
	 * Removes all values from the conainer,.
	 *
	 */
	void clear() noexcept {
		for ( const T & elem : m_data ) {
			fireRemove ( elem );
		}

		m_data.clear ( );
	}

	/**
	 * \brief
	 * Computes the number of values in the container.
	 *
	 * \return the number of values in the container.
	 */
	size_t count ( const T& value ) const {
		return m_data.count ( value );
	}

	/**
	 * \brief
	 * Getter of a const revese iterator to the begining of reverse range of values in the container.
	 *
	 * \return begin const reverse iterator
	 */
	const_reverse_iterator crbegin() const noexcept {
		return m_data.crbegin ( );
	}

	/**
	 * \brief
	 * Getter of a const revese iterator to the end of reverse range of values in the container.
	 *
	 * \return end const reverse iterator
	 */
	const_reverse_iterator crend() const noexcept {
		return m_data.crend ( );
	}

	/**
	 * \brief
	 * Emplace a value to the container. Internaly the method uses insert since the place to put the value requires call to comparator and that needs the value.
	 *
	 * \tparam Args ... types of arguments of constructor of T
	 *
	 * \param args ... actual parameters pased to constructor of T
	 *
	 * \return pair of iterator and bool, where the iterator points to newly inserted value (or already existing one) and the bool is true of the value was inserted, or false if the value was alread inside the container
	 */
	template <class... Args>
	std::pair < iterator, bool > emplace ( Args && ... args ) {
		return insert ( T ( std::forward ( args ) ... ) );
	}

	/**
	 * \brief
	 * Emplace a value to the container with provided position as a hint. Internaly the method uses insert since the place to put the value requires call to comparator and that needs the value.
	 *
	 * \tparam Args ... types of arguments of constructor of T
	 *
	 * \param position the position hint where to place the new value
	 * \param args ... actual parameters pased to constructor of T
	 *
	 * \return pair of iterator and bool, where the iterator points to newly inserted value (or already existing one) and the bool is true of the value was inserted, or false if the value was alread inside the container
	 */
	template <class... Args>
	iterator emplace_hint ( const_iterator position, Args && ... args ) {
		return insert ( position, T ( std::forward ( args ) ... ) );
	}

	/**
	 * \brief
	 * Tests whether the container is empty.
	 *
	 * \return true if the container is empty, false othervise
	 */
	bool empty ( ) const noexcept {
		return m_data.empty ( );
	}

	/**
	 * \brief
	 * Returns a range of values equal to the \p val. The range is specified by const iterators.
	 *
	 * \param val the queried value
	 *
	 * \returns pair of iterators representing the range
	 */
	std::pair < const_iterator, const_iterator > equal_range ( const T & val ) const {
		return std::make_pair ( lower_bound ( val ), upper_bound ( val ) );
	}

	/**
	 * \brief
	 * Returns a range of values equal to the \p val. The range is specified by iterators.
	 *
	 * \param val the queried value
	 *
	 * \returns pair of iterators representing the range
	 */
	std::pair < iterator, iterator > equal_range ( const T & val ) {
		return std::make_pair ( lower_bound ( val ), upper_bound ( val ) );
	}

	/**
	 * \brief
	 * Removes value from the container based on the position given by iterator.
	 *
	 * \param position the iterator pointing to the value to remove.
	 *
	 * \return updated iterator pointing to the next value
	 */
	iterator erase ( const_iterator position ) {
		fireRemove ( * position );
		return m_data.erase ( position );
	}

	/**
	 * \brief
	 * Removes value from the container.
	 *
	 * \param val the value to remove.
	 *
	 * \return the number of removed values
	 */
	size_t erase ( const T & val ) {
		const_iterator position = lower_bound ( val );
		if ( position != end ( ) && eq ( * position, val ) ) {
			fireRemove ( * position );
			m_data.erase ( position );
			return 1;
		} else {
			return 0;
		}
	}

	/**
	 * \brief
	 * Removes values in the specified range. The range is specified by pair of iterators.
	 *
	 * \param first the begining of removed range of values
	 * \param last the end of removed range of values
	 *
	 * \return iterator to value after last removed value
	 */
	iterator erase (const_iterator first, const_iterator last) {
		for ( const T & elem : ext::make_iterator_range ( first, last ) )
			fireRemove ( elem );

		return m_data.erase ( first, last );
	}

	/**
	 * \brief
	 * Function to binary search for given value.
	 *
	 * \param val the value to search for
	 *
	 * \return iterator pointing to searched value or iterator to end
	 */
	const_iterator find ( const T & val ) const {
		m_data.find ( val );
	}

	/**
	 * \brief
	 * Function to binary search for given value.
	 *
	 * \param val the value to search for
	 *
	 * \return iterator pointing to searched value or iterator to end
	 */
	iterator find ( const T & val ) {
		m_data.find ( val );
	}

	/**
	 * \brief
	 * Getter of the allocator.
	 *
	 * \return the allocator
	 */
	Alloc get_allocator() const noexcept {
		return m_data.get_allocator ( );
	}

	/**
	 * \brief
	 * Inserts a new value to the container.
	 *
	 * \param val the value to insert
	 *
	 * \return pair of iterator and bool, where the iterator points to newly inserted value (or already existing one) and the bool is true of the value was inserted, or false if the value was alread inside the container
	 */
	std::pair < iterator, bool > insert ( const T & val ) {
		return insert ( T ( val ) );
	}

	/**
	 * \brief
	 * Inserts a new value to the container.
	 *
	 * \param val the value to insert
	 *
	 * \return pair of iterator and bool, where the iterator points to newly inserted value (or already existing one) and the bool is true of the value was inserted, or false if the value was alread inside the container
	 */
	std::pair < iterator, bool > insert ( T && val ) {
		const_iterator position = lower_bound ( val );
		if ( position != end ( ) && eq ( * position, val ) )
			return std::make_pair ( position, false );
		else {
			fireInsert ( val );
			return std::make_pair ( m_data.insert ( position, std::move ( val ) ), true );
		}
	}

	/**
	 * \brief
	 * Inserts a new value to the container. The method accepts a position hint where to place the new value.
	 *
	 * \param position the hint where to insert
	 * \param val the value to insert
	 *
	 * \return iterator pointing to newly inserted value (or already existing one).
	 */
	iterator insert ( const_iterator position, const T & val ) {
		return insert ( position, T ( val ) );
	}

	/**
	 * \brief
	 * Inserts a new value to the container. The method accepts a position hint where to place the new value.
	 *
	 * \param position the hint where to insert
	 * \param val the value to insert
	 *
	 * \return iterator pointing to newly inserted value (or already existing one).
	 */
	iterator insert (const_iterator position, T&& val) {
		if ( position != end ( ) && eq ( *  position, val ) )
			return position;

		if ( position != end ( ) && m_comp ( val, * position ) && ( position == begin ( ) || m_comp ( * std::prev ( position ), val ) ) ) {
			fireInsert ( val );
			return m_data.emplace ( position, std::move ( val ) );
		}

		return insert ( std::move ( val ) ).first;
	}

	/**
	 * \brief
	 * Insert values from a range speified by pair of iterators.
	 *
	 * \param first the begining of removed range of values
	 * \param last the end of removed range of values
	 */
	template <class InputIterator>
	void insert (InputIterator first, InputIterator last) {
		for ( const T & elem : ext::make_iterator_range ( first, last ) )
			fireInsert ( elem );

		m_data.insert ( first, last );
	}

	/**
	 * \brief
	 * Insert values from a range speified by initializer list.
	 *
	 * \param il the source of values represented by initializer list
	 */
	void insert (std::initializer_list<T> il) {
		insert ( il.begin ( ), il.end ( ) );
	}

	/**
	 * \brief
	 * Getter of the key comparator instance.
	 *
	 * \return the key comparator instance
	 */
	Compare key_comp() const {
		return m_data.key_comp();
	}

	/**
	 * \brief
	 * Returns an iterator pointing to the first element in the range [first,last) which does not compare less than val.
	 *
	 * \param val the border value
	 *
	 * \return the iterator meeting lower_bound criteria
	 */
	iterator lower_bound ( const T & val ) {
		return m_data.lower_bound ( val );
	}

	/**
	 * \brief
	 * Returns an iterator pointing to the first element in the range [first,last) which does not compare less than val.
	 *
	 * \param val the border value
	 *
	 * \return the iterator meeting lower_bound criteria
	 */
	const_iterator lower_bound (const T& val) const {
		return m_data.lower_bound ( val );
	}

	/**
	 * \brief
	 * Returns the maximal number of values possible to store inside the container.
	 *
	 * \return the maximal number of values
	 */
	size_t max_size() const noexcept {
		return m_data.max_size ( );
	}

	/**
	 * \brief
	 * Copy operator of assignmet.
	 *
	 * \param x the other instance
	 *
	 * \return the modified instance
	 */
	managed_linear_set & operator= ( const managed_linear_set & data ) {
		ext::set_symmetric_difference ( m_data.begin ( ), m_data.end ( ), data.begin ( ), data.end ( ), ext::make_callback_iterator ( std::bind ( & managed_linear_set::fireRemove, this, std::placeholders::_1 ) ), ext::make_callback_iterator ( std::bind ( & managed_linear_set::fireInsert, this, std::placeholders::_1 ) ) );

		m_data = data.m_data;
		return *this;
	}

	/**
	 * \brief
	 * Move operator of assignmet.
	 *
	 * \param x the other instance
	 *
	 * \return the modified instance
	 */
	managed_linear_set & operator= ( managed_linear_set && data ) {
		ext::set_symmetric_difference ( m_data.begin ( ), m_data.end ( ), data.begin ( ), data.end ( ), ext::make_callback_iterator ( std::bind ( & managed_linear_set::fireRemove, this, std::placeholders::_1 ) ), ext::make_callback_iterator ( std::bind ( & managed_linear_set::fireInsert, this, std::placeholders::_1 ) ) );

		for ( const T & elem : data ) {
			data.fireRemove ( elem );
		}

		m_data = std::move ( data.m_data );
		return *this;
	}

	/**
	 * \brief
	 * Asignment from the initializer list.
	 *
	 * \param il the source initializer list
	 *
	 * \return the modified instance
	 */
	managed_linear_set & operator= ( std::initializer_list < T > il ) {
		linear_set < T > data ( il );
		ext::set_symmetric_difference ( m_data.begin ( ), m_data.end ( ), data.begin ( ), data.end ( ), ext::make_callback_iterator ( std::bind ( & managed_linear_set::fireRemove, this, std::placeholders::_1 ) ), ext::make_callback_iterator ( std::bind ( & managed_linear_set::fireInsert, this, std::placeholders::_1 ) ) );

		m_data = std::move ( data );
		return *this;
	}

	/**
	 * \brief
	 * Getter of a reverse iterator to the begining of range of values in the container.
	 *
	 * \return begin reverse iterator
	 */
	reverse_iterator rbegin() noexcept {
		return m_data.rbegin ( );
	}

	/**
	 * \brief
	 * Getter of a const reverse iterator to the begining of range of values in the container.
	 *
	 * \return begin const reverse iterator
	 */
	const_reverse_iterator rbegin() const noexcept {
		return m_data.rbegin ( );
	}

	/**
	 * \brief
	 * Getter of a reverse iterator to the end of range of values in the container.
	 *
	 * \return end reverse iterator
	 */
	reverse_iterator rend() noexcept {
		return m_data.rend ( );
	}

	/**
	 * \brief
	 * Getter of a const reverse iterator to the end of range of values in the container.
	 *
	 * \return end const reverse iterator
	 */
	const_reverse_iterator rend() const noexcept {
		return m_data.rend ( );
	}

	/**
	 * \brief
	 * Getter of the number of values inside the container.
	 *
	 * \return the number of values
	 */
	size_t size() const noexcept {
		return m_data.size ( );
	}

	/**
	 * \brief
	 * Swaps two instances of linear set
	 *
	 * \param x the other instance to swap with
	 */
	void swap ( managed_linear_set & data ) {
		auto thisToOther = ext::make_callback_iterator ( [ this, & data ] ( const T & element ) { this->fireRemove ( element ); data.fireInsert ( element ); } );
		auto otherToThis = ext::make_callback_iterator ( [ this, & data ] ( const T & element ) { data.fireRemove ( element ); this->fireInsert ( element ); } );
		ext::set_symmetric_difference ( m_data.begin ( ), m_data.end ( ), data.begin ( ), data.end ( ), thisToOther, otherToThis );

		m_data.swap ( data.m_data );
	}

	/**
	 * \brief
	 * Returns an iterator pointing to the first element in the range [first,last) which compares greater than val.
	 *
	 * \param val the border value
	 *
	 * \return the iterator meeting upper_bound criteria
	 */
	iterator upper_bound ( const T & val) {
		return m_data.upper_bound ( val );
	}

	/**
	 * \brief
	 * Returns an iterator pointing to the first element in the range [first,last) which compares greater than val.
	 *
	 * \param val the border value
	 *
	 * \return the iterator meeting upper_bound criteria
	 */
	const_iterator upper_bound (const T& val) const {
		return m_data.upper_bound ( val );
	}

	/**
	 * \brief
	 * Getter of the value comparator instance. Actually the value_type is the same as key_type so this is an alias to key_comp method
	 *
	 * \return the key comparator instance
	 */
	Compare value_comp() const {
		return m_data.value_comp ( );
	}

	/**
	 * \brief
	 * Compares two set instances by less relation.
	 *
	 * \param other the second instance to compare
	 *
	 * \return instance of compare category given by the type T representing the comparison of the value in this and value in other instance
	 */
	auto operator <=> ( const managed_linear_set < T, Compare, Alloc > & other ) const = default;

};

/**
 * \brief
 * Operator to print the set to the output stream.
 *
 * \param out the output stream
 * \param value the array to print
 *
 * \tparam T the type of values inside the array
 * \tparam Ts ... remaining unimportant template parameters of the set
 *
 * \return the output stream from the \p out
 */
template< class T, class ... Ts >
std::ostream& operator<<(std::ostream& out, const ext::managed_linear_set<T, Ts ...>& value) {
	out << "{";

	bool first = true;
	for(const T& item : value) {
		if(!first) out << ", ";
		first = false;
		out << item;
	}

	out << "}";
	return out;
}

/**
 * \brief
 * Overload of to_string function.
 *
 * \param value the set to be converted to string
 *
 * \tparam T the type of values inside the set
 * \tparam Ts ... remaining unimportant template parameters of the set
 *
 * \return string representation
 */
template < class T, class ... Ts >
std::string to_string ( const ext::managed_linear_set < T, Ts ... > & value ) {
	std::stringstream ss;
	ss << value;
	return ss.str();
}

} /* namespace ext */

namespace std {

/**
 * \brief
 * Specialisation of swap for linear set
 *
 * \tparam T the value of the set
 * \tpatam Compare the comparator
 * \tparam Alloc the values Allocator
 *
 * \param x the first instance
 * \param y the second instance
 */
template <class T, class Compare, class Alloc>
void swap ( ext::managed_linear_set < T, Compare, Alloc > & x, ext::managed_linear_set < T, Compare, Alloc > & y ) {
	x.swap ( y );
}

}

