#pragma once
#ifndef sd_counting_iterator_h
#define sd_counting_iterator_h

#include <iterator>
#include <type_traits>

namespace sd
{

template <typename T, T delta = T(1)>
class counting_iterator
{
    static_assert(::std::is_integral<T>::value, "integral type is required for counting");

public:
    typedef T                                    value_type;
    typedef const T*                             pointer;
    typedef T                                    reference;
    typedef ::std::random_access_iterator_tag    iterator_category;
    typedef typename ::std::make_signed<T>::type difference_type;

    counting_iterator() : _counter() {}
    explicit counting_iterator(T init) : _counter(init) {}

    reference operator*() const { return _counter; }
    reference operator[](difference_type n) const { return *(*this + n); }
    counting_iterator& operator++() { return *this += delta; }
    counting_iterator& operator--() { return *this -= delta; }
    counting_iterator operator++(int)
    {
        counting_iterator other(*this);
        ++(*this);
        return other;
    }
    counting_iterator operator--(int)
    {
        counting_iterator other(*this);
        --(*this);
        return other;
    }
    counting_iterator& operator-=(difference_type n) { return *this += -n; }
    counting_iterator& operator+=(difference_type n)
    {
        _counter += n;
        return *this;
    }    
    difference_type operator-(const counting_iterator& other) const
    {
        return _counter - other._counter;
    }
    counting_iterator operator-(difference_type n) const
    {
        return counting_iterator(_counter - n);
    }
    counting_iterator operator+(difference_type n) const
    {
        return counting_iterator(_counter + n);
    }
    friend counting_iterator operator+(difference_type n, const counting_iterator other)
    {
        return other + n;
    }

    bool operator==(const counting_iterator& other) const { return *this - other == 0; }
    bool operator!=(const counting_iterator& other) const { return !(*this == other); }
    bool operator<(const counting_iterator& other) const { return *this - other < 0; }
    bool operator>(const counting_iterator& other) const { return other < *this; }
    bool operator<=(const counting_iterator& other) const { return !(*this > other); }
    bool operator>=(const counting_iterator& other) const { return !(*this < other); }

private:
    T _counter;
};

template <typename T>
class strided_counting_iterator
{
    static_assert(::std::is_integral<T>::value, "integral type is required for counting");

public:
    typedef T                                    value_type;
    typedef const T*                             pointer;
    typedef T                                    reference;
    typedef ::std::random_access_iterator_tag    iterator_category;
    typedef typename ::std::make_signed<T>::type difference_type;

    strided_counting_iterator() : _counter(), _delta() {}
    explicit strided_counting_iterator(T init, T delta) : _counter(init), _delta(delta) {}

    reference operator*() const { return _counter; }
    reference operator[](difference_type n) const { return *(*this + n); }
    strided_counting_iterator& operator++() { return *this += _delta; }
    strided_counting_iterator& operator--() { return *this -= _delta; }
    strided_counting_iterator operator++(int)
    {
        strided_counting_iterator other(*this);
        ++(*this);
        return other;
    }
    strided_counting_iterator operator--(int)
    {
        strided_counting_iterator other(*this);
        --(*this);
        return other;
    }
    strided_counting_iterator& operator-=(difference_type n) { return *this += -n; }
    strided_counting_iterator& operator+=(difference_type n)
    {
        _counter += n;
        return *this;
    }    
    difference_type operator-(const strided_counting_iterator& other) const
    {
        return _counter - other._counter;
    }
    strided_counting_iterator operator-(difference_type n) const
    {
        return strided_counting_iterator(_counter - n);
    }
    strided_counting_iterator operator+(difference_type n) const
    {
        return strided_counting_iterator(_counter + n);
    }
    friend strided_counting_iterator operator+(difference_type n, const strided_counting_iterator other)
    {
        return other + n;
    }

    bool operator==(const strided_counting_iterator& other) const { return *this - other == 0; }
    bool operator!=(const strided_counting_iterator& other) const { return !(*this == other); }
    bool operator<(const strided_counting_iterator& other) const { return *this - other < 0; }
    bool operator>(const strided_counting_iterator& other) const { return other < *this; }
    bool operator<=(const strided_counting_iterator& other) const { return !(*this > other); }
    bool operator>=(const strided_counting_iterator& other) const { return !(*this < other); }

private:
    T _counter;
    T _delta;
};


} // namespace sd

#endif