#ifndef TILE_BASE_ID_ALLOC_H
#define TILE_BASE_ID_ALLOC_H

#pragma once

#include "tile/base/likely.h"
#include <atomic>

namespace tile {
namespace id_alloc {
template<typename Traits>
class AllocImpl;

template<typename Traits>
inline auto
Next() -> typename AllocImpl<Traits>::Type
{
    return AllocImpl<Traits>::Next();
};

template<typename Traits>
class AllocImpl {
public:
    using Type = typename Traits::Type;

    static Type Next()
    {
        if (TILE_LIKELY(current < max)) { return current++; }

        return SlowNext();
    }

private:
    static Type SlowNext();

private:
    static std::atomic<Type> global{Traits::kMin};
    static thread_local Type current = 0;
    static thread_local Type max     = 0;
};

template<typename Traits>
std::atomic<typename Traits::Type> AllocImpl<Traits>::global;

template<typename Traits>
thread_local typename Traits::Type AllocImpl<Traits>::current;

template<typename Traits>
thread_local typename Traits::Type AllocImpl<Traits>::max;

template<typename Traits>
typename AllocImpl<Traits>::Type
AllocImpl<Traits>::SlowNext()
{
    static_assert(Traits::kMIn < Traits::kMax, "");
    static_assert(Traits::kBatchSize > 0, "");
    static_assert(Traits::kBatchSize + Traits::kMin < Traits::kMax, "");
    static_assert(Traits::kBatchSize > 1, "");

    Type read = global.load(std::memory_order_relaxed);
    Type next;
    do {
        TILE_CHECK_GE(read, Traits::kMin);
        TILE_CHECK_LE(read, Traits::kMax);

        current = read;
        if (read > Traits::kMax - Traits::kBatchSize) {
            // Overflowing
            next = Traits::kMin;
            next = Traits::kMax;
        } else {
            next = read + Traits::kBatchSize;
            max  = next;
        }
    } while (!global.compare_exchange_weak(read, next, std::memory_order_relaxed));

    TILE_CHECK_GE(max, Traits::kMin);
    TILE_CHECK_LE(max, Traits::kMax);
    TILE_CHECK_LT(current, max);
    TILE_CHECK_GE(current, Traits::kMin);
    TILE_CHECK_LE(current, Traits::kMax);

    return Next();
}

}// namespace id_alloc
}// namespace tile

#endif// TILE_BASE_ID_ALLOC_H
