#pragma once

#include <util/generic/iterator_range.h>
#include <boost/iterator/iterator_facade.hpp>

#include <vector>

namespace maps::mrc::common {

template<typename Iterator>
class JoinedRangesIterator :
    public boost::iterator_facade<
        JoinedRangesIterator<Iterator>,
        typename std::iterator_traits<Iterator>::value_type,
        std::forward_iterator_tag,
        typename std::iterator_traits<Iterator>::reference>
{
public:

    JoinedRangesIterator()
    : rangeIdx_(0)
    {}

    JoinedRangesIterator(std::vector<TIteratorRange<Iterator>> iteratorRanges)
    : iteratorRanges_(std::move(iteratorRanges))
    , rangeIdx_(0)
    {
        rewindRangeIdxToNextNonEmptyRange();
        if (rangeIdx_ < iteratorRanges_.size()) {
            currentItInRange_ = iteratorRanges_[rangeIdx_].begin();
        }
    }

    bool equal(const JoinedRangesIterator<Iterator>& other) const
    {
        return this->isAtTheEndOfTheRanges() && other.isAtTheEndOfTheRanges() ||
            this->rangeIdx_ == other.rangeIdx_ &&
            this->currentItInRange_ == other.currentItInRange_;
    }

    typename std::iterator_traits<Iterator>::reference dereference() const
    {
        return *currentItInRange_;
    }

    void increment()
    {
        if (rangeIdx_ >= iteratorRanges_.size()) {
            // do nothing, iterator is at the end
            return;
        }

        ++currentItInRange_;

        if (currentItInRange_ == iteratorRanges_[rangeIdx_].end())
        {
            ++rangeIdx_;
            rewindRangeIdxToNextNonEmptyRange();
            if (rangeIdx_ != iteratorRanges_.size()) {
                currentItInRange_ = iteratorRanges_[rangeIdx_].begin();
            }
        }
    }

private:

    bool isAtTheEndOfTheRanges() const {
        return rangeIdx_ == iteratorRanges_.size();
    }

    size_t rewindRangeIdxToNextNonEmptyRange() {
        while (rangeIdx_ < iteratorRanges_.size() &&
            iteratorRanges_[rangeIdx_].begin() == iteratorRanges_[rangeIdx_].end())
        {
            ++rangeIdx_;
        }
        return rangeIdx_;
    }

    std::vector<TIteratorRange<Iterator>> iteratorRanges_;
    size_t rangeIdx_;
    /// Iterator at iteratorRanges_[rangeIdx_]
    Iterator currentItInRange_;
};

}  // namespace maps::mrc::common
