#pragma once

#include <util/generic/deque.h>
#include <util/generic/noncopyable.h>
#include <util/generic/strbuf.h>
#include <util/generic/yexception.h>
#include <util/system/align.h>
#include <library/cpp/deprecated/atomic/atomic.h>
#include <util/system/filemap.h>
#include <util/system/info.h>
#include <util/system/mutex.h>
#include <util/generic/hash.h>
#include <util/generic/ymath.h>

namespace NSrvKernel {
    class TSharedAllocator : public TNonCopyable {
    private:
        struct TAllocatedChunk {
            void* Address = nullptr;
            void* Data = nullptr;
            size_t MappingSize = 0;
            size_t AllocatedSize = 0;

            TAllocatedChunk() = default;

            TAllocatedChunk(void* addr, void* data, size_t size) noexcept;

            ~TAllocatedChunk();
        };

    public:
        explicit TSharedAllocator(size_t minAllocationChunkSize = 0);

        template <typename T, typename... Arg>
        T* Allocate(Arg&&... args) /* throws */ {
            return new (AllocateImpl(GetTypeId<T>(), sizeof(T), alignof(T))) T(std::forward<Arg>(args)...);
        }

        template <typename T, size_t Alignment, typename... Arg>
        T* AllocateAligned(Arg&&... args) /* throws */ {
            return new (AllocateImpl(GetTypeId<T>(), sizeof(T), Alignment)) T(std::forward<Arg>(args)...);
        }

        template <typename T, size_t Alignment>
        T* AllocateAlignedRawArray(size_t elemsCount) /* throws */ {
            return reinterpret_cast<T*>(AllocateImpl(GetTypeId<T>(), elemsCount * sizeof(T), Alignment));
        }

        void Freeze() noexcept {
            Frozen_ = true;
        }

        bool Frozen() const noexcept {
            return Frozen_;
        }

        size_t MappedBytes() const noexcept;
        size_t AllocatedBytes() const noexcept;

    private:
        void* AllocateImpl(size_t typeId, size_t size, size_t alignment) /* throws */;

        TAllocatedChunk& AllocateNewChunk(TDeque<TAllocatedChunk>& allocatedChunks, size_t size) /* throws */;

        static void* AllocateInChunk(TAllocatedChunk& chunk, size_t size, size_t alignment) /* throws */;

        template <class T>
        size_t GetTypeId() noexcept {
            static const size_t id = TypeIdCounter++;
            return id;
        }

    private:
        static size_t TypeIdCounter;

        TMutex Lock_;
        THashMap<size_t, TDeque<TAllocatedChunk>> AllocatedChunks_;
        const size_t PageSize_ = 0;
        size_t NextChunkSize_ = 0;
        bool Frozen_ = false;
    };

    class TSharedBool {
    public:
        static_assert(sizeof(ui64) == sizeof(TAtomic));

        TSharedBool(TSharedAllocator& alloc) noexcept
            : Value_(alloc.Allocate<TAtomic>(0))
        {
            Y_ASSERT(Value_);
        }

        explicit operator bool() const noexcept {
            return Value();
        }

        TSharedBool& operator=(bool value) noexcept {
            Y_ASSERT(Value_);
            AtomicSet(*Value_, value ? 1 : 0);
            return *this;
        }

        bool Value() const noexcept {
            Y_ASSERT(Value_);
            return AtomicGet(*Value_);
        }

    private:
        TAtomic* Value_ = nullptr;
    };
}
