/*
 * iterator_range.hpp as proposed http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3350.html by Jeffrey Yasskin <jyasskin@google.com>
 *
 * Created on: May 28, 2015
 * Author: Jan Travnicek
 */

#ifndef __RANGE_HPP_
#define __RANGE_HPP_

#include <utility>
#include "iterator.hpp"

namespace ext {

/**
 * \brief
 * Implementation of iterator_range, i.e. pair of iterators. The class provides most notably begin and end methods to allow the class be used in foreach context.
 *
 * \tparam Iterator the type of wrapped pair of iterators
 */
template<typename Iterator>
class iterator_range {
	/**
	 * \brief
	 * The begin iterator
	 */
	Iterator m_begin;

	/**
	 * \brief
	 * The end iterator
	 */
	Iterator m_end;
public:
	/**
	 * \brief
	 * Copy of iterator_category from the wrapped iterators
	 */
	typedef typename std::iterator_traits< Iterator >::iterator_category iterator_category;

	/**
	 * \brief
	 * Copy of value_type from the wrapped iterators
	 */
	typedef typename std::iterator_traits< Iterator >::value_type value_type;

	/**
	 * \brief
	 * Copy of difference_type from the wrapped iterators
	 */
	typedef typename std::iterator_traits< Iterator >::difference_type difference_type;

	/**
	 * \brief
	 * Copy of reference from the wrapped iterators
	 */
	typedef typename std::iterator_traits< Iterator >::reference reference;

	/**
	 * \brief
	 * Copy of pointer from the wrapped iterators
	 */
	typedef typename std::iterator_traits< Iterator >::pointer pointer;

	/**
	 * \brief
	 * Constructor of empty iterator_range. Both iterators are initialized to default (same) value.
	 */
	iterator_range ( ) {
	}

	/**
	 * \brief
	 * Constructor to make iterator_range from pair of iterators
	 *
	 * \param begin the begining of the wrapped iterator_range
	 * \param end the end of the wrapped iterator_range
	 */
	constexpr iterator_range(Iterator begin, Iterator end) : m_begin ( begin ), m_end ( end ) {
	}

	/**
	 * \brief
	 * Accessor of the iterator to the begining.
	 *
	 * \return the begin iterator
	 */
	constexpr Iterator begin() const {
		return m_begin;
	}

	/**
	 * \brief
	 * Accessor of the iterator to the end.
	 *
	 * \return the end iterator
	 */
	constexpr Iterator end() const {
		return m_end;
	}

	/**
	 * \brief
	 * Getter of the first value in the iterator_range.
	 *
	 * \return reference to the first value
	 */
	constexpr reference front() const {
		return * m_begin;
	}

	/**
	 * \brief
	 * Getter of the last value in the iterator_range.
	 *
	 * \return reference to the last value
	 */
	constexpr reference back() const {
		return * ( m_end - 1 );
	}

	/**
	 * \brief
	 * Array subscript operator implementation.
	 *
	 * \return reference to the value at given distance from the begining
	 */
	constexpr reference operator[](difference_type index) const {
		return m_begin [ index ];
	}

	/**
	 * \brief
	 * Test whether the iterator_range is empty.
	 *
	 * \return true of the two iterators representing iterator_range are equal.
	 */
	constexpr bool empty() const {
		return m_begin == m_end;
	}

	/**
	 * \brief
	 * Getter of the distance between begin and end iterators.
	 *
	 * \return the distance between begin and end iterators
	 */
	constexpr size_t size() const {
		return std::distance ( m_begin, m_end );
	}

	/**
	 * \brief
	 * Advances the begin iterator.
	 */
	void pop_front() {
		++ m_begin;
	}

	/**
	 * \brief
	 * Advances the begin iterator \p n times.
	 */
	void pop_front(difference_type n) {
		m_begin = m_begin + n;
	}

	/**
	 * \brief
	 * Retracts the end iterator.
	 */
	void pop_back() {
		-- m_end;
	}

	/**
	 * \brief
	 * Retracts the end iterator \p n times.
	 */
	void pop_back(difference_type n) {
		m_end = m_end - n;
	}

	/**
	 * \brief
	 * Creates two sub ranges based on middle position. The element at the middle position is included in the second iterator_range.
	 *
	 * \param index the place where to cut the iterator_range in two
	 *
	 * \return two subranges
	 */
	std::pair< iterator_range, iterator_range > split(difference_type index) const {
		return std::make_pair ( slice ( 0, index ), slice ( index, 0 ) );
	}

	/**
	 * \brief
	 * Creates a subrange of the iterator_range representing interaval of values from \p start to \p stop.
	 *
	 * If the \p start is positive or zero, the actual cut position is calcuated relative to the begining of the iterator_range.
	 * If the \p start is negative, the actual cut position is calculated relative to the end of the iterator_range.
	 *
	 * If the \p end is positive, the actual cut position is calcuated relative to the begining of the iterator_range.
	 * If the \p start is negative or zero, the actual cut position is calculated relative to the end of the iterator_range.
	 *
	 * Example iterator_range ( 0, 1, 2, 3, 4, 5 ).slice ( 1, 3 ) produces iterator_range ( 1, 2 );
	 * Example iterator_range ( 0, 1, 2, 3, 4, 5 ).slice ( -4, -1 ) produces iterator_range ( 2, 3, 4 );
	 *
	 * \param start the begin position of the resulting subrange
	 * \param end the end position of the resulting subrange
	 *
	 * \return subrange of the iterator_range.
	 */
	iterator_range slice(difference_type start, difference_type stop) const {
		return iterator_range ( ( start >= 0 ? m_begin : m_end ) + start, ( stop > 0 ? m_begin : m_end ) + stop );
	}

	/**
	 * \brief
	 * Creates a subrange of the iterator_range representing interaval of values from \p start to the end of the iterator_range.
	 *
	 * If the \p start is positive or zero, the actual cut position is calcuated relative to the begining of the iterator_range.
	 * If the \p start is negative, the actual cut position is calculated relative to the end of the iterator_range.
	 *
	 * The call is equivalent to slice ( begin, 0 ).
	 *
	 * Example iterator_range ( 0, 1, 2, 3, 4, 5 ).slice ( 1 ) produces iterator_range ( 1, 2, 3, 4, 5 );
	 * Example iterator_range ( 0, 1, 2, 3, 4, 5 ).slice ( -4 ) produces iterator_range ( 2, 3, 4, 5 );
	 *
	 * \param start the begin position of the resulting subrange
	 *
	 * \return subrange of the iterator_range.
	 */
	iterator_range slice(difference_type start) const {
		return slice ( start, 0 );
	}
};

/**
 * \brief
 * Helper to create iterator_range from two iterators.
 *
 * \return the iterator_range instance from two iterators
 */
template < typename Iter >
iterator_range < Iter > make_iterator_range ( Iter begin, Iter end ) {
	return iterator_range < Iter > ( begin, end );
}

/**
 * Generalization of range for universaly referenced containers.
 *
 * \param cont the container to call end on
 *
 * \result the range over all elements of the array
 */
template <class Container>
auto range ( Container && cont ) -> decltype ( std::forward < Container > ( cont ).range ( ) ) {
	return ext::make_iterator_range ( std::forward < Container > ( cont ).begin ( ), std::forward < Container > ( cont ).end ( ) );
}

/**
 * Specialization of range for static array.
 *
 * \param arr the static array
 *
 * \result the range over all elements of the array
 */
template < class T, size_t N >
constexpr ext::iterator_range < T * > range ( T ( & arr ) [ N ] ) noexcept {
	return ext::make_iterator_range ( begin ( arr ), end ( arr ) );
}

} /* namespace ext */

#endif /* __RANGE_HPP_ */