#pragma once

#include <util/datetime/base.h>
#include <util/generic/algorithm.h>
#include <util/generic/vector.h>
#include <util/string/cast.h>
#include <util/string/split.h>

#include <algorithm>
#include <utility>

namespace NSrvKernel {
    template <class T>
    class TRanges {
        // Check whether type T is always have minimum value equal to zero
        static_assert(std::is_unsigned<T>::value || std::is_same<T, TDuration>::value, "");
    public:
        template <class S>
        TRanges(const S& s, T firstValue = {})
            : Self_(1, firstValue)
        {
            for (const auto& i: StringSplitter(s).Split(',')) {
                Self_.push_back(FromString<T>(i.Token()));
            }

            Sort(begin(), end());
            Self_.erase(Unique(begin(), end()), end());
        }

        TRanges(const TRanges&) = default;
        TRanges(TRanges&&) = default;

        TRanges& operator=(const TRanges&) = default;
        TRanges& operator=(TRanges&&) = default;

        const auto& Base() const noexcept {
            return Self_;
        }

        auto size() const noexcept {
            return Self_.size();
        }

        auto begin() noexcept {
            return Self_.begin();
        }

        auto end() noexcept {
            return Self_.end();
        }

        auto begin() const noexcept {
            return Self_.begin();
        }

        auto end() const noexcept {
            return Self_.end();
        }

        bool operator==(const TRanges<T>& rhs) const noexcept {
            return Self_ == rhs.Self_;
        }

        bool operator!=(const TRanges<T>& rhs) const noexcept {
            return Self_ != rhs.Self_;
        }

    private:
        TVector<T> Self_;
    };
    using TDurationRanges = TRanges<TDuration>;
    using TSizeRanges = TRanges<size_t>;

    template<typename R, typename T>
    class TRangedData {
    public:
        TRangedData(TRanges<R> ranges)
            : Ranges_(std::move(ranges))
        {
            Y_ENSURE(Ranges_.size() > 0);
            Values_.resize(Ranges_.size());
        }
        template<typename S>
        TRangedData(const S& s)
            : Ranges_(s)
        {
            Y_ENSURE(Ranges_.size() > 0);
            Values_.resize(Ranges_.size());
        }

        TRangedData(const TRangedData&) = default;
        TRangedData(TRangedData&&) = default;

        TRangedData& operator=(const TRangedData&) = default;
        TRangedData& operator=(TRangedData&&) = default;

        T& operator[](R range) noexcept {
            auto it = UpperBound(Ranges_.begin(), Ranges_.end(), range);
            return Values_[std::distance(Ranges_.begin(), --it)];
        }

        const T& operator[](R range) const noexcept {
            auto it = UpperBound(Ranges_.begin(), Ranges_.end(), range);
            return Values_[std::distance(Ranges_.begin(), --it)];
        }

        template<typename Op, typename LastOp>
        void ForEach(Op&& op, LastOp&& last_op) const {
            const size_t endIdx = Ranges_.size();
            for (size_t i = 0; i < endIdx - 1; ++i) {
                op(Ranges_.Base()[i], Ranges_.Base()[i + 1], Values_[i]);
            }
            // We assume that size of Ranges_ is at least 1
            last_op(Ranges_.Base()[endIdx - 1], Values_[endIdx - 1]);
        }

        auto Size() const noexcept {
            return Ranges_.size();
        }

        auto ValuesBegin() const noexcept {
            return Values_.begin();
        }

        auto ValuesEnd() const noexcept {
            return Values_.end();
        }

        auto ValuesBegin() noexcept {
            return Values_.begin();
        }

        auto ValuesEnd() noexcept {
            return Values_.end();
        }

        auto RangesBegin() const noexcept {
            return Ranges_.Begin();
        }

        auto RangesEnd() const noexcept {
            return Ranges_.End();
        }

        const TRanges<R>& Ranges() const noexcept {
            return Ranges_;
        }

    private:
        TRanges<R> Ranges_;
        TVector<T> Values_;
    };
    template <class T>
    using TDurationRangedData = TRangedData<TDuration, T>;
    template <class T>
    using TSizeRangedData = TRangedData<size_t, T>;

    template <typename T>
    TVector<ui64> RangesToIntervals(const TRanges<T>& ranges);
}
