#ifndef _TILE_BASE_BUFFER_CIRCULAR_BUFFER_H
#define _TILE_BASE_BUFFER_CIRCULAR_BUFFER_H

#pragma once

#include <atomic>
#include <memory>
#include <utility>
#include <vector>

namespace tile {
template<typename T>
class CircularBuffer {
    class UninitializedObject;

public:
    explicit CircularBuffer(std::size_t capacity);

    template<typename... Ts>
    bool Emplace(Ts &&...args)
    {
        auto head = head_.load(std::memory_order_relaxed);
        auto next = NormalizeIndex(head + 1);

        if (next == tail_.load(std::memory_order_acquire)) { return false; }

        objects_[head].Initialize(std::forward<Ts>(args)...);
        head_.store(next, std::memory_order_release);
        return true;
    }

    void Pop(std::vector<T> *objects)
    {
        auto upto    = head_.load(std::memory_order_acquire);
        auto current = tail_.load(std::memory_order_relaxed);
        while (current != upto) {
            objects->push_back(std::move(*objects_[current].Get()));
            objects_[current].Destroy();
            current = NormalizeIndex(current + 1);
        }

        tail_.store(current, std::memory_order_release);
    }

private:
    class UninitializedObject {
    public:
        T *Get() noexcept { return reinterpret_cast<T *>(&storage_); }

        template<typename... Ts>
        void Initialize(Ts &&...args)
        {
            new (Get()) T(std::forward<Ts>(args)...);
        }

        void Destroy() { Get()->~T(); }

    private:
        std::aligned_storage<sizeof(T), alignof(T)> storage_;
    };

    std::size_t NormalizeIndex(std::size_t x) { return (x < capacity_) ? x : x - capacity_; }

private:
    std::size_t capacity_;
    std::unique_ptr<UninitializedObject[]> objects_;
    std::atomic<std::size_t> head_{}, tail_{};
};
}// namespace tile

#endif// _TILE_BASE_BUFFER_CIRCULAR_BUFFER_H
