#ifndef FZ_WITH_LAST_VIEW_HPP
#define FZ_WITH_LAST_VIEW_HPP

#include <iterator>
#include <array>

namespace fz {

template <typename Range>
class with_last_view
{
	using range_iterator = decltype(std::cbegin(std::declval<Range>()));
	using range_sentinel = decltype(std::cend(std::declval<Range>()));

	static_assert(
		std::is_base_of_v<
			std::forward_iterator_tag,
			typename std::iterator_traits<range_iterator>::iterator_category
		>,
		"fz::with_last_view requires at least ForwardIterator"
	);

public:
	using value_type = std::pair<decltype(*std::declval<range_iterator>()), bool>;
	using size_type = std::size_t;

private:
	struct sentinel{};

	struct iterator
	{
		using iterator_category = std::forward_iterator_tag;
		using difference_type = ptrdiff_t;
		using value_type = with_last_view::value_type;
		using pointer = value_type*;
		using reference = value_type&;

		constexpr bool operator !=(sentinel) const {
			return cur_ != end_;
		}

		constexpr bool operator ==(sentinel) const {
			return cur_ == end_;
		}

		constexpr iterator &operator ++() {
			cur_ = next_;
			is_last_ = ++next_ == end_;
			return *this;
		}

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

		constexpr value_type operator *() const {
			return {*cur_, is_last_};
		}

		struct value_type_proxy {
			constexpr value_type_proxy(value_type &&v):
				value_(std::move(v))
			{}

			constexpr const value_type *operator->() const {
				return &value_;
			}

			constexpr value_type *operator->() {
				return &value_;
			}

		private:
			value_type value_;
		};

		constexpr value_type_proxy operator->() const {
			return value_type_proxy(operator *());
		}

	private:
		friend with_last_view;

		constexpr iterator(range_iterator cur, range_sentinel end)
			: cur_(std::move(cur))
			, next_(cur != end ? std::next(cur) : end)
			, end_(end)
			, is_last_(next_ == end)
		{}

		range_iterator cur_;
		range_iterator next_;
		range_sentinel end_;
		bool is_last_;
	};

	using const_iterator = iterator;

public:
	constexpr with_last_view(Range &&r)
		: r_(std::forward<Range>(r))
	{}

	constexpr iterator begin() const noexcept
	{
		return {std::cbegin(r_), std::cend(r_)};
	}

	constexpr sentinel end() const noexcept
	{
		return {};
	}

private:
	Range r_;
};

template <typename Range>
with_last_view(Range &&r) -> with_last_view<Range>;

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

}

#endif // FZ_WITH_LAST_VIEW_HPP
