#ifndef FZ_FILTERED_VIEW_HPP
#define FZ_FILTERED_VIEW_HPP

#include <iterator>
#include <array>

namespace fz {

template <typename Range, typename F>
class filtered_view
{
	using range_iterator = decltype(std::begin(std::declval<Range>()));
	using range_sentinel = decltype(std::end(std::declval<Range>()));

public:
	using value_type = typename std::iterator_traits<range_iterator>::value_type;
	using size_type = std::size_t;

private:
	struct iterator;
	struct sentinel
	{
		sentinel(range_sentinel s={})
			: s_(std::move(s))
		{}

	private:
		friend iterator;

public:
		range_sentinel s_;
	};

	struct iterator
	{
		using iterator_category = std::input_iterator_tag;
		using difference_type = typename std::iterator_traits<range_iterator>::difference_type;
		using value_type = typename std::iterator_traits<range_iterator>::value_type;
		using pointer = typename std::iterator_traits<range_iterator>::pointer;
		using reference = typename std::iterator_traits<range_iterator>::reference;

		friend constexpr bool operator !=(const iterator &it, const sentinel &s) {
			return it.it_ != s.s_;
		}

		friend constexpr bool operator ==(const iterator &it, const sentinel &s) {
			return it.it_ == s.s_;
		}

		constexpr iterator &operator ++() {
			++it_;
			find_next_valid_it();
			return *this;
		}

		constexpr iterator operator ++(int) {
			iterator copy(*this);
			operator++();
			return copy;
		}

		constexpr value_type operator *() const {
			return *it_;
		}

		constexpr pointer operator->() const {
			return std::addressof(*it_);
		}

	private:
		friend filtered_view;

		constexpr iterator(const filtered_view &v)
			: it_(std::begin(v.r_))
			, v_(&v)
		{
			find_next_valid_it();
		}

		constexpr void find_next_valid_it()
		{
			while (it_ != std::end(v_->r_) && !v_->f_(*it_)) {
				++it_;
			}
		}

		range_iterator it_;
		const filtered_view *v_;
	};

	using const_iterator = iterator;

public:
	constexpr filtered_view(Range &&r, F &&f)
		: r_(std::forward<Range>(r))
		, f_(std::forward<F>(f))
	{}

	constexpr iterator begin() const noexcept
	{
		return {*this};
	}

	constexpr sentinel end() const noexcept
	{
		return {std::end(r_)};
	}

private:
	Range r_;
	F f_;
};

template <typename Range, typename F>
filtered_view(Range &&r, F &&f) -> filtered_view<Range, F>;

template <typename T, std::size_t N, typename F>
filtered_view(T(&&v)[N], F &&f) -> filtered_view<std::array<T, N>, F>;

}

#endif // FZ_FILTERED_VIEW_HPP
