#pragma once

#include <util/system/atexit.h>

#include <atomic>
#include <new>
#include <utility>

template <class T>
struct TSingletonTraits {
    static constexpr size_t Priority = 65536;
};

namespace NPrivate {
    void FillWithTrash(void* ptr, size_t len);

    void LockRecursive(std::atomic<size_t>& lock) noexcept;
    void UnlockRecursive(std::atomic<size_t>& lock) noexcept;

    template <class T>
    void Destroyer(void* ptr) {
        ((T*)ptr)->~T();
        FillWithTrash(ptr, sizeof(T));
    }

    template <class T, size_t P, class... TArgs>
    Y_NO_INLINE T* SingletonBase(std::atomic<T*>& ptr, TArgs&&... args) {
        alignas(T) static char buf[sizeof(T)];
        static std::atomic<size_t> lock;

        LockRecursive(lock);

        auto ret = ptr.load();

        try {
            if (!ret) {
                ret = ::new (buf) T(std::forward<TArgs>(args)...);

                try {
                    AtExit(Destroyer<T>, ret, P);
                } catch (...) {
                    Destroyer<T>(ret);

                    throw;
                }

                ptr.store(ret);
            }
        } catch (...) {
            UnlockRecursive(lock);

            throw;
        }

        UnlockRecursive(lock);

        return ret;
    }

    template <class T, size_t P, class... TArgs>
    T* SingletonInt(TArgs&&... args) {
        static_assert(sizeof(T) < 32000, "use HugeSingleton instead");

        static std::atomic<T*> ptr;
        auto ret = ptr.load();

        if (Y_UNLIKELY(!ret)) {
            ret = SingletonBase<T, P>(ptr, std::forward<TArgs>(args)...);
        }

        return ret;
    }

    template <class T>
    class TDefault {
    public:
        template <class... TArgs>
        inline TDefault(TArgs&&... args)
            : T_(std::forward<TArgs>(args)...)
        {
        }

        inline const T* Get() const noexcept {
            return &T_;
        }

    private:
        T T_;
    };

    template <class T>
    struct THeapStore {
        template <class... TArgs>
        inline THeapStore(TArgs&&... args)
            : D(new T(std::forward<TArgs>(args)...))
        {
        }

        inline ~THeapStore() {
            delete D;
        }

        T* D;
    };
}

#define Y_DECLARE_SINGLETON_FRIEND()                \
    template <class T, size_t P, class... TArgs>    \
    friend T* ::NPrivate::SingletonInt(TArgs&&...); \
    template <class T, size_t P, class... TArgs>    \
    friend T* ::NPrivate::SingletonBase(std::atomic<T*>&, TArgs&&...);

template <class T, class... TArgs>
T* Singleton(TArgs&&... args) {
    return ::NPrivate::SingletonInt<T, TSingletonTraits<T>::Priority>(std::forward<TArgs>(args)...);
}

template <class T, class... TArgs>
T* HugeSingleton(TArgs&&... args) {
    return Singleton<::NPrivate::THeapStore<T>>(std::forward<TArgs>(args)...)->D;
}

template <class T, size_t P, class... TArgs>
T* SingletonWithPriority(TArgs&&... args) {
    return ::NPrivate::SingletonInt<T, P>(std::forward<TArgs>(args)...);
}

template <class T, size_t P, class... TArgs>
T* HugeSingletonWithPriority(TArgs&&... args) {
    return SingletonWithPriority<::NPrivate::THeapStore<T>, P>(std::forward<TArgs>(args)...)->D;
}

template <class T>
const T& Default() {
    return *(::NPrivate::SingletonInt<typename ::NPrivate::TDefault<T>, TSingletonTraits<T>::Priority>()->Get());
}