#ifndef TILE_BASE_THREAD_THREAD_LOCAL_OBJECT_ARRAY_H
#define TILE_BASE_THREAD_THREAD_LOCAL_OBJECT_ARRAY_H

#pragma once

#include "tile/base/align.h"
#include "tile/base/logging.h"
#include "tile/base/thread/mutex.h"
#include "tile/base/thread/scoped_lock.h"

namespace tile {
namespace tls {
namespace detail {
template<typename T>
struct Entry {
    std::aligned_storage<sizeof(T), alignof(T)> storage;
    static_assert(sizeof(T) == sizeof(storage), "");
};

template<typename T>
struct ObjectArrayCache {
    std::size_t limit{};
    T *objects{};
};

template<typename T>
struct ObjectArray;

template<typename T>
class LazyInitObjectArray {
    struct EntryDeleter {
        void operator()(Entry<T> *ptr) noexcept { operator delete(ptr); }
    };

    using AlignedArray = std::unique_ptr<Entry<T>[], EntryDeleter>;

public:
    LazyInitObjectArray() = default;

    ~LazyInitObjectArray()
    {
        for (int i = 0; i != initialized_.size(); ++i) {
            if (initialized_[i]) { reinterpret_cast<T *>(&objects_[i])->~T(); }
        }
    }

    LazyInitObjectArray(LazyInitObjectArray &&)            = default;
    LazyInitObjectArray &operator=(LazyInitObjectArray &&) = default;

    template<typename F>
    void InitializeAt(std::size_t index, F &&f)
    {
        TILE_CHECK_LT(index, initialized_.size());
        TILE_CHECK(!initialized_[index]);

        initialized_[index] = true;
        std::forward<F>(f)(&objects_[index]);
    }

    void DestroyAt(std::size_t index)
    {
        TILE_CHECK_LT(index, initialized_.size());
        TILE_CHECK(initialized_[index]);

        initialized_[index] = false;
        reinterpret_cast<T *>(&objects_[index])->~T();
    }

    bool IsInitializedAt(std::size_t index) const noexcept
    {
        TILE_CHECK_LT(index, initialized_.size());
        return initialized_[index];
    }

    T *GetAt(std::size_t index) noexcept
    {
        TILE_CHECK_LT(index, initialized_.size());
        TILE_CHECK(initialized_[index]);
        return reinterpret_cast<T *>(&objects_[index]);
    }

    T *GetObjectsMaybeUninitialized() noexcept
    {
        static_assert(sizeof(T) == sizeof(Entry<T>), "");
        return reinterpret_cast<T *>(objects_.get());
    }

    std::size_t size() const noexcept { return initialized_.size(); }

private:
    AlignedArray objects_;
    std::vector<bool> initialized_;
};

template<typename T>
class ObjectArrayRegistry {
public:
    static ObjectArrayRegistry *Instance()
    {
        static NeverDestroyedSingleton<ObjectArrayRegistry> instance;
        return instance.get();
    }

    void Register(ObjectArray<T> *array)
    {
        ScopedLock _(lock_);
        TILE_DCHECK(std::find(arrays_.begin(), arrays_.end(), array) == arrays_.end());
        arrays_.push_back(array);
    }

    void Deregister(ObjectArray<T> *array)
    {
        ScopedLock _(lock_);
        auto iter = std::find(arrays_.begin(), arrays_.end(), array);
        TILE_CHECK(iter != arrays_.end());
        arrays_.erase(iter);
    }

    template<typename F>
    void ForEachLocked(std::size_t index, F &&f)
    {
        ScopedLock _(lock_);
        for (auto &&e : arrays_) {
            ScopedLock _(e->lock);
            if (index < e->objects.size()) { f(e); }
        }
    }

private:
    Mutex lock_;
    std::vector<ObjectArray<T> *> arrays_;
};

template<typename T>
struct ObjectArray {
    Mutex lock;
    LazyInitObjectArray<T> objects_;

    ObjectArray() { ObjectArrayRegistry<T>::Instance()->Register(this); }

    ~ObjectArray() { ObjectArrayRegistry<T>::Instance()->Deregister(this); }
};

}// namespace detail
}// namespace tls
}// namespace tile

#endif// TILE_BASE_THREAD_THREAD_LOCAL_OBJECT_ARRAY_H
