/*
	This file is part of Fennix Kernel.

	Fennix Kernel 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.

	Fennix Kernel 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 Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

#include <initializer_list>
#include <functional>
#include <algorithm>
#include <compare>
#include <memory>

#warning "std::set not implemented; Do not use"

namespace std
{
	template <class Key, class Compare = std::less<Key>, class Allocator = std::allocator<Key>>
	class multiset;

	template <class Key, class Compare = std::less<Key>, class Allocator = std::allocator<Key>>
	class set
	{
	public:
		template <class Iter, class NodeType>
		struct __set_return_type
		{
			Iter position;
			bool inserted;
			NodeType node;
		};

		using key_type = Key;
		using value_type = Key;
		using size_type = std::size_t;
		using difference_type = std::ptrdiff_t;
		using key_compare = Compare;
		using value_compare = Compare;
		using allocator_type = Allocator;
		using reference = value_type &;
		using const_reference = const value_type &;
		using pointer = std::allocator_traits<Allocator>::pointer;
		using const_pointer = std::allocator_traits<Allocator>::const_pointer;
		using iterator = value_type;
		using const_iterator = const value_type;
		using reverse_iterator = std::reverse_iterator<iterator>;
		using const_reverse_iterator = std::reverse_iterator<const_iterator>;
		using node_type = std::unique_ptr<value_type>;
		using insert_return_type = __set_return_type<iterator, node_type>;

	private:
	public:
#pragma region Constructors

		set() : set(Compare()) {}

		explicit set(const Compare &comp, const Allocator &alloc = Allocator());

		explicit set(const Allocator &alloc);

		template <class InputIt>
		set(InputIt first, InputIt last, const Compare &comp = Compare(), const Allocator &alloc = Allocator());

		template <class InputIt>
		set(InputIt first, InputIt last, const Allocator &alloc) : set(first, last, Compare(), alloc) {}

		set(const set &other);

		set(const set &other, const Allocator &alloc);

		set(set &&other);

		set(set &&other, const Allocator &alloc);

		set(std::initializer_list<value_type> init, const Compare &comp = Compare(), const Allocator &alloc = Allocator());

		set(std::initializer_list<value_type> init, const Allocator &alloc) : set(init, Compare(), alloc) {}

		~set();

#pragma endregion Constructors

#pragma region Assignment

		set &operator=(const set &other);

		set &operator=(set &&other) noexcept(std::allocator_traits<Allocator>::is_always_equal::value && std::is_nothrow_move_assignable<Compare>::value);

		set &operator=(std::initializer_list<value_type> ilist);

		allocator_type get_allocator() const noexcept;

#pragma endregion Assignment

#pragma region Iterators

		iterator begin() noexcept;

		const_iterator begin() const noexcept;

		const_iterator cbegin() const noexcept;

		iterator end() noexcept;

		const_iterator end() const noexcept;

		const_iterator cend() const noexcept;

		reverse_iterator rbegin() noexcept;

		const_reverse_iterator rbegin() const noexcept;

		const_reverse_iterator crbegin() const noexcept;

		reverse_iterator rend() noexcept;

		const_reverse_iterator rend() const noexcept;

		const_reverse_iterator crend() const noexcept;

#pragma endregion Iterators

#pragma region Capacity

		bool empty() const noexcept;

		size_type size() const noexcept;

		size_type max_size() const noexcept;

#pragma endregion Capacity

#pragma region Modifiers

		void clear() noexcept;

		std::pair<iterator, bool> insert(const value_type &value);

		std::pair<iterator, bool> insert(value_type &&value);

		iterator insert(const_iterator pos, const value_type &value);

		iterator insert(const_iterator pos, value_type &&value);

		template <class InputIt>
		void insert(InputIt first, InputIt last);

		void insert(std::initializer_list<value_type> ilist);

		insert_return_type insert(node_type &&nh);

		iterator insert(const_iterator pos, node_type &&nh);

		template <class... Args>
		std::pair<iterator, bool> emplace(Args &&...args);

		template <class... Args>
		iterator emplace_hint(const_iterator hint, Args &&...args);

		iterator erase(const_iterator pos);

		iterator erase(const_iterator first, const_iterator last);

		size_type erase(const Key &key);

		void swap(set &other) noexcept(std::allocator_traits<Allocator>::is_always_equal::value && std::is_nothrow_swappable<Compare>::value);

		node_type extract(const_iterator position);

		node_type extract(const Key &k);

		template <class C2>
		void merge(std::set<Key, C2, Allocator> &source);

		template <class C2>
		void merge(std::set<Key, C2, Allocator> &&source);

		template <class C2>
		void merge(std::multiset<Key, C2, Allocator> &source);

		template <class C2>
		void merge(std::multiset<Key, C2, Allocator> &&source);

#pragma endregion Modifiers

#pragma region Lookup

		size_type count(const Key &key) const;

		template <class K>
		size_type count(const K &x) const;

		iterator find(const Key &key);

		const_iterator find(const Key &key) const;

		template <class K>
		iterator find(const K &x);

		template <class K>
		const_iterator find(const K &x) const;

		bool contains(const Key &key) const;

		template <class K>
		bool contains(const K &x) const;

		std::pair<iterator, iterator> equal_range(const Key &key);

		std::pair<const_iterator, const_iterator> equal_range(const Key &key) const;

		template <class K>
		std::pair<iterator, iterator> equal_range(const K &x);

		template <class K>
		std::pair<const_iterator, const_iterator> equal_range(const K &x) const;

		iterator lower_bound(const Key &key);

		const_iterator lower_bound(const Key &key) const;

		template <class K>
		iterator lower_bound(const K &x);

		template <class K>
		const_iterator lower_bound(const K &x) const;

		iterator upper_bound(const Key &key);

		const_iterator upper_bound(const Key &key) const;

		template <class K>
		iterator upper_bound(const K &x);

		template <class K>
		const_iterator upper_bound(const K &x) const;

#pragma endregion Lookup

#pragma region Observers

		key_compare key_comp() const;

		set::value_compare value_comp() const;

#pragma endregion Observers
	};

	template <class Key, class Compare, class Alloc>
	bool operator==(const std::set<Key, Compare, Alloc> &lhs, const std::set<Key, Compare, Alloc> &rhs)
	{
		return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
	}

	template <class Key, class Compare, class Alloc>
	std::strong_ordering operator<=>(const std::set<Key, Compare, Alloc> &lhs, const std::set<Key, Compare, Alloc> &rhs)
	{
		// return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), __synth_three_way);

		auto it1 = lhs.begin();
		auto it2 = rhs.begin();
		auto end1 = lhs.end();
		auto end2 = rhs.end();

		while (it1 != end1 && it2 != end2)
		{
			if (*it1 < *it2)
				return std::strong_ordering::less;
			if (*it2 < *it1)
				return std::strong_ordering::greater;
			++it1;
			++it2;
		}

		if (it1 == end1 && it2 == end2)
			return std::strong_ordering::equal;
		return (it1 == end1) ? std::strong_ordering::less : std::strong_ordering::greater;
	}

	template <class Key, class Compare, class Alloc>
	void swap(std::set<Key, Compare, Alloc> &lhs, std::set<Key, Compare, Alloc> &rhs) noexcept(noexcept(lhs.swap(rhs)))
	{
		lhs.swap(rhs);
	}

	template <class Key, class Compare, class Alloc, class Pred>
	std::set<Key, Compare, Alloc>::size_type erase_if(std::set<Key, Compare, Alloc> &c, Pred pred)
	{
		auto old_size = c.size();
		for (auto first = c.begin(), last = c.end(); first != last;)
		{
			if (pred(*first))
				first = c.erase(first);
			else
				++first;
		}
		return old_size - c.size();
	}
}
