//! \addtogroup Containers
//! \{
//! \~\file piset.h
//! \~\brief
//! \~english Declares \a PISet
//! \~russian Объявление \a PISet
//! \~\}
/*
    PIP - Platform Independent Primitives
    Set container
    Ivan Pelipenko peri4ko@yandex.ru

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

    This program 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 Lesser General Public License for more details.

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

#ifndef PISET_H
#define PISET_H

#include "pimap.h"

//! \addtogroup Containers
//! \{
//! \class PISet
//! \~\brief
//! \~english Set of unique values.
//! \~russian Множество уникальных значений.
//! \~\}
//! \~\details
//! \~english
//! This class is used to store a collection of unique elements of any type.
//! You can add values to the set using \a operator<< or the \a insert() function.
//! You can check if a value already exists in the set using \a operator[] or the \a contains() function.
//! These operations have logarithmic complexity.
//! To iterate over all elements, use STL-style iterators \a begin() and \a end().
//!
//! The set is implemented as a wrapper around \a PIMap, where keys are the elements
//! and values are dummy byte values (used only for storage).
//! \~russian
//! Этот класс используется для хранения коллекции уникальных элементов любого типа.
//! Вы можете добавлять значения в множество с помощью \a operator<< или функции \a insert().
//! Вы можете проверить, существует ли значение в множестве, используя \a operator[] или функцию \a contains().
//! Эти операции имеют логарифмическую сложность.
//! Для перебора всех элементов используйте итераторы в стиле STL \a begin() и \a end().
//!
//! Множество реализовано как обёртка над \a PIMap, где ключами являются элементы,
//! а значениями являются фиктивные байтовые значения (используются только для хранения).
//! \~\sa \a PIMap, \a PIVector
template<typename T>
class PISet: public PIMap<T, uchar> {
	typedef PIMap<T, uchar> _CSet;
	template<typename P, typename T1>
	friend PIBinaryStream<P> & operator<<(PIBinaryStream<P> & s, const PISet<T1> & v);
	template<typename P, typename T1>
	friend PIBinaryStream<P> & operator>>(PIBinaryStream<P> & s, PISet<T1> & v);

public:
	//! \~english Constructs an empty set.
	//! \~russian Создает пустое множество.
	PISet() {}

	//! \~english Constructs a set containing `value`.
	//! \~russian Создает множество, содержащее `value`.
	explicit PISet(const T & value) { _CSet::insert(value, 0); }

	//! \~english Constructs a set containing `v0` and `v1`.
	//! \~russian Создает множество, содержащее `v0` и `v1`.
	PISet(const T & v0, const T & v1) {
		_CSet::insert(v0, 0);
		_CSet::insert(v1, 0);
	}

	//! \~english Constructs a set containing `v0`, `v1` and `v2`.
	//! \~russian Создает множество, содержащее `v0`, `v1` и `v2`.
	PISet(const T & v0, const T & v1, const T & v2) {
		_CSet::insert(v0, 0);
		_CSet::insert(v1, 0);
		_CSet::insert(v2, 0);
	}

	//! \~english Constructs a set containing `v0`, `v1`, `v2` and `v3`.
	//! \~russian Создает множество, содержащее `v0`, `v1`, `v2` и `v3`.
	PISet(const T & v0, const T & v1, const T & v2, const T & v3) {
		_CSet::insert(v0, 0);
		_CSet::insert(v1, 0);
		_CSet::insert(v2, 0);
		_CSet::insert(v3, 0);
	}


	//! \~\brief
	//! \~english Constant iterator over \a PISet elements.
	//! \~russian Константный итератор по элементам \a PISet.
	class const_iterator {
		friend class PISet<T>;

	private:
		inline const_iterator(const PISet<T> * v, ssize_t p): parent(v), pos(p) {}
		const PISet<T> * parent;
		ssize_t pos;

	public:
		typedef T value_type;
		typedef T * pointer;
		typedef T & reference;
		typedef std::ptrdiff_t difference_type;
		typedef std::random_access_iterator_tag iterator_category;

		//! \~english Constructs an invalid iterator.
		//! \~russian Создает недействительный итератор.
		inline const_iterator(): parent(0), pos(0) {}

		//! \~english Returns the current element.
		//! \~russian Возвращает текущий элемент.
		inline const T & operator*() const { return parent->pim_index[pos].key; }

		//! \~english Provides access to the current element.
		//! \~russian Предоставляет доступ к текущему элементу.
		inline const T & operator->() const { return parent->pim_index[pos].key; }

		//! \~english Moves iterator to the next element.
		//! \~russian Перемещает итератор к следующему элементу.
		inline const_iterator & operator++() {
			++pos;
			return *this;
		}

		//! \~english Returns iterator before incrementing.
		//! \~russian Возвращает итератор до увеличения.
		inline const_iterator operator++(int) {
			const auto tmp = *this;
			++*this;
			return tmp;
		}

		//! \~english Moves iterator to the previous element.
		//! \~russian Перемещает итератор к предыдущему элементу.
		inline const_iterator & operator--() {
			--pos;
			return *this;
		}

		//! \~english Returns iterator before decrementing.
		//! \~russian Возвращает итератор до уменьшения.
		inline const_iterator operator--(int) {
			const auto tmp = *this;
			--*this;
			return tmp;
		}

		//! \~english Adds offset of iterator `it`.
		//! \~russian Добавляет смещение итератора `it`.
		inline const_iterator & operator+=(const const_iterator & it) {
			pos += it.pos;
			return *this;
		}

		//! \~english Advances iterator by `p` elements.
		//! \~russian Сдвигает итератор вперед на `p` элементов.
		inline const_iterator & operator+=(size_t p) {
			pos += p;
			return *this;
		}

		//! \~english Subtracts offset of iterator `it`.
		//! \~russian Вычитает смещение итератора `it`.
		inline const_iterator & operator-=(const const_iterator & it) {
			pos -= it.pos;
			return *this;
		}

		//! \~english Moves iterator back by `p` elements.
		//! \~russian Сдвигает итератор назад на `p` элементов.
		inline const_iterator & operator-=(size_t p) {
			pos -= p;
			return *this;
		}

		//! \~english Returns iterator shifted back by `p`.
		//! \~russian Возвращает итератор, сдвинутый назад на `p`.
		friend inline const_iterator operator-(size_t p, const const_iterator & it) { return it - p; }

		//! \~english Returns iterator shifted back by `p`.
		//! \~russian Возвращает итератор, сдвинутый назад на `p`.
		friend inline const_iterator operator-(const const_iterator & it, size_t p) {
			auto tmp = it;
			tmp -= p;
			return tmp;
		}

		//! \~english Returns distance between iterators.
		//! \~russian Возвращает расстояние между итераторами.
		friend inline std::ptrdiff_t operator-(const const_iterator & it1, const const_iterator & it2) { return it1.pos - it2.pos; }

		//! \~english Returns iterator shifted forward by `p`.
		//! \~russian Возвращает итератор, сдвинутый вперед на `p`.
		friend inline const_iterator operator+(size_t p, const const_iterator & it) { return it + p; }

		//! \~english Returns iterator shifted forward by `p`.
		//! \~russian Возвращает итератор, сдвинутый вперед на `p`.
		friend inline const_iterator operator+(const const_iterator & it, size_t p) {
			auto tmp = it;
			tmp += p;
			return tmp;
		}

		//! \~english Checks iterator equality.
		//! \~russian Проверяет равенство итераторов.
		inline bool operator==(const const_iterator & it) const { return (pos == it.pos); }

		//! \~english Checks iterator inequality.
		//! \~russian Проверяет неравенство итераторов.
		inline bool operator!=(const const_iterator & it) const { return (pos != it.pos); }

		//! \~english Checks whether `it1` is before `it2`.
		//! \~russian Проверяет, находится ли `it1` перед `it2`.
		friend inline bool operator<(const const_iterator & it1, const const_iterator & it2) { return it1.pos < it2.pos; }

		//! \~english Checks whether `it1` is before or equal to `it2`.
		//! \~russian Проверяет, находится ли `it1` перед `it2` или совпадает с ним.
		friend inline bool operator<=(const const_iterator & it1, const const_iterator & it2) { return it1.pos <= it2.pos; }

		//! \~english Checks whether `it1` is after `it2`.
		//! \~russian Проверяет, находится ли `it1` после `it2`.
		friend inline bool operator>(const const_iterator & it1, const const_iterator & it2) { return it1.pos > it2.pos; }

		//! \~english Checks whether `it1` is after or equal to `it2`.
		//! \~russian Проверяет, находится ли `it1` после `it2` или совпадает с ним.
		friend inline bool operator>=(const const_iterator & it1, const const_iterator & it2) { return it1.pos >= it2.pos; }
	};


	//! \~english Returns iterator to the first element.
	//! \~russian Возвращает итератор на первый элемент.
	inline const_iterator begin() const { return const_iterator(this, 0); }

	//! \~english Returns iterator following the last element.
	//! \~russian Возвращает итератор на элемент, следующий за последним.
	inline const_iterator end() const { return const_iterator(this, _CSet::size()); }

	//! \~english Constructs a set from vector `values`.
	//! \~russian Создает множество из вектора `values`.
	explicit PISet(const PIVector<T> & values) {
		if (values.isEmpty()) return;
		for (int i = 0; i < values.size_s(); ++i) {
			_CSet::insert(values[i], 0);
		}
	}

	//! \~english Constructs a set from deque `values`.
	//! \~russian Создает множество из дека `values`.
	explicit PISet(const PIDeque<T> & values) {
		if (values.isEmpty()) return;
		for (int i = 0; i < values.size_s(); ++i) {
			_CSet::insert(values[i], 0);
		}
	}

	typedef T key_type;

	//! \~english Inserts `t` into the set.
	//! \~russian Добавляет `t` в множество.
	PISet<T> & operator<<(const T & t) {
		_CSet::insert(t, 0);
		return *this;
	}

	//! \~english Moves `t` into the set.
	//! \~russian Перемещает `t` в множество.
	PISet<T> & operator<<(T && t) {
		_CSet::insert(std::move(t), 0);
		return *this;
	}

	//! \~english Inserts all elements from `other`.
	//! \~russian Добавляет все элементы из `other`.
	PISet<T> & operator<<(const PISet<T> & other) {
		(*(_CSet *)this) << *((_CSet *)&other);
		return *this;
	}

	//! \~english Tests whether element `t` exists in the set.
	//! \~russian Проверяет наличие элемента `t` в множестве.
	inline bool contains(const T & t) const { return _CSet::contains(t); }

	//! \~english Checks whether element `t` exists in the set.
	//! \~russian Проверяет наличие элемента `t` в множестве.
	bool operator[](const T & t) const { return _CSet::contains(t); }

	//! \~english Removes element `t` from the set.
	//! \~russian Удаляет элемент `t` из множества.
	PISet<T> & remove(const T & t) {
		_CSet::remove(t);
		return *this;
	}

	//! \~english Unites the set with `v`.
	//! \~russian Объединяет множество с `v`.
	PISet<T> & unite(const PISet<T> & v) {
		for (const auto & i: v)
			_CSet::insert(i, 0);
		return *this;
	}

	//! \~english Removes all elements present in `v`.
	//! \~russian Удаляет все элементы, присутствующие в `v`.
	PISet<T> & subtract(const PISet<T> & v) {
		for (const auto & i: v)
			_CSet::remove(i);
		return *this;
	}

	//! \~english Leaves only elements also present in `v`.
	//! \~russian Оставляет только элементы, которые есть и в `v`.
	PISet<T> & intersect(const PISet<T> & v) {
		_CSet::removeWhere([&v](const T & k, uchar) { return !v.contains(k); });
		return *this;
	}

	//! \~english Same as \a unite().
	//! \~russian Синоним \a unite().
	PISet<T> & operator+=(const PISet<T> & v) { return unite(v); }

	//! \~english Same as \a unite().
	//! \~russian Синоним \a unite().
	PISet<T> & operator|=(const PISet<T> & v) { return unite(v); }

	//! \~english Same as \a subtract().
	//! \~russian Синоним \a subtract().
	PISet<T> & operator-=(const PISet<T> & v) { return subtract(v); }

	//! \~english Same as \a intersect().
	//! \~russian Синоним \a intersect().
	PISet<T> & operator&=(const PISet<T> & v) { return intersect(v); }

	//! \~english Returns set contents as \a PIVector.
	//! \~russian Возвращает содержимое множества в виде \a PIVector.
	PIVector<T> toVector() const {
		PIVector<T> ret;
		for (const auto & i: *this)
			ret << i;
		return ret;
	}

	//! \~english Returns set contents as \a PIDeque.
	//! \~russian Возвращает содержимое множества в виде \a PIDeque.
	PIDeque<T> toDeque() const {
		PIDeque<T> ret;
		for (const auto & i: *this)
			ret << i;
		return ret;
	}
};


//! \relatesalso PISet
//! \~english Returns union of two sets.
//! \~russian Возвращает объединение двух множеств.
template<typename T>
PISet<T> operator+(const PISet<T> & v0, const PISet<T> & v1) {
	PISet<T> ret(v0);
	ret.unite(v1);
	return ret;
}

//! \relatesalso PISet
//! \~english Returns difference of two sets.
//! \~russian Возвращает разность двух множеств.
template<typename T>
PISet<T> operator-(const PISet<T> & v0, const PISet<T> & v1) {
	PISet<T> ret(v0);
	ret.subtract(v1);
	return ret;
}

//! \relatesalso PISet
//! \~english Returns union of two sets.
//! \~russian Возвращает объединение двух множеств.
template<typename T>
PISet<T> operator|(const PISet<T> & v0, const PISet<T> & v1) {
	PISet<T> ret(v0);
	ret.unite(v1);
	return ret;
}

//! \relatesalso PISet
//! \~english Returns intersection of two sets.
//! \~russian Возвращает пересечение двух множеств.
template<typename T>
PISet<T> operator&(const PISet<T> & v0, const PISet<T> & v1) {
	PISet<T> ret(v0);
	ret.intersect(v1);
	return ret;
}


//! \relatesalso PISet
//! \~english Output operator to \a PICout.
//! \~russian Оператор вывода в \a PICout.
template<typename Type>
inline PICout operator<<(PICout s, const PISet<Type> & v) {
	s.space();
	s.saveAndSetControls(0);
	s << "{";
	bool first = true;
	for (const auto & i: v) {
		if (!first) s << ", ";
		first = false;
		s << i;
	}
	s << "}";
	s.restoreControls();
	return s;
}

#endif // PISET_H
