/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#pragma once

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <new>
#include <utility>

#include "paimon/macros.h"
#include "paimon/visibility.h"

namespace paimon {

class MemoryPool;

/// Create a default implementation of memory pool.
/// @return Unique pointer to a newly created `MemoryPool` instance.
PAIMON_EXPORT std::unique_ptr<MemoryPool> GetMemoryPool();

/// Get a system-wide singleton memory pool.
/// @return Shared pointer to the singleton `MemoryPool` instance.
PAIMON_EXPORT std::shared_ptr<MemoryPool> GetDefaultPool();

/// Abstract base class for memory pool implementations that provides controlled memory management.
class PAIMON_EXPORT MemoryPool {
 public:
    virtual ~MemoryPool() = default;
    MemoryPool& operator=(const MemoryPool& other) = delete;
    MemoryPool& operator=(MemoryPool& other) = delete;
    MemoryPool& operator=(MemoryPool&& other) = delete;

    /// Allocate memory from the pool.
    ///
    /// Allocates a block of memory with the specified size and alignment.
    /// The returned memory is uninitialized and must be properly constructed
    /// before use.
    ///
    /// @param size Number of bytes to allocate.
    /// @param alignment Memory alignment requirement (0 for default alignment).
    /// @return Pointer to allocated memory.
    /// @throws std::bad_alloc if the allocation fails.
    virtual void* Malloc(uint64_t size, uint64_t alignment = 0) = 0;

    /// Reallocate memory to a new size.
    ///
    /// Changes the size of a previously allocated memory block. The contents
    /// of the memory block are preserved up to the minimum of the old and new sizes.
    ///
    /// @param p Pointer to the memory block to reallocate.
    /// @param old_size Current size of the memory block.
    /// @param new_size New desired size of the memory block.
    /// @param alignment Memory alignment requirement (0 for default alignment).
    /// @return Pointer to the reallocated memory block.
    virtual void* Realloc(void* p, size_t old_size, size_t new_size, uint64_t alignment = 0) = 0;

    /// Deallocate memory back to the pool.
    ///
    /// Releases a previously allocated memory block back to the pool.
    /// The size must match the size used during allocation.
    /// The alignment used during allocation is not provided, subclass should store it
    /// by themselves if needed.
    ///
    /// @param p Pointer to the memory block to deallocate.
    /// @param size Size of the memory block (must match allocation size).
    virtual void Free(void* p, uint64_t size) = 0;

    /// Deallocate memory back to the pool with specified alignment.
    ///
    /// Releases a previously allocated memory block back to the pool.
    /// The size and alignment must match the values used during allocation.
    /// Subclass can override this method to optimize deallocation based on alignment.
    ///
    /// @param p Pointer to the memory block to deallocate.
    /// @param size Size of the memory block (must match allocation size).
    /// @param alignment Alignment of the memory block (must match allocation alignment).
    virtual void Free(void* p, uint64_t size, uint64_t alignment) {
        Free(p, size);
    }

    /// Get current memory usage.
    ///
    /// Returns the amount of memory currently allocated from this pool.
    /// This includes all outstanding allocations that have not been freed.
    ///
    /// @return Current memory usage in bytes.
    virtual uint64_t CurrentUsage() const = 0;

    /// Get peak memory usage.
    ///
    /// Returns the maximum amount of memory that has been allocated from
    /// this pool at any point in time since its creation.
    ///
    /// @return Peak memory usage in bytes.
    virtual uint64_t MaxMemoryUsage() const = 0;

    /// Custom deleter for use with std::unique_ptr that integrates with memory pools.
    ///
    /// AllocatorDelete provides automatic memory deallocation through the memory pool
    /// when used with std::unique_ptr. It stores both the pool reference and the
    /// allocation size to ensure proper cleanup.
    ///
    /// @tparam T Type of object being managed.
    template <typename T>
    class AllocatorDelete {
     private:
        std::reference_wrapper<MemoryPool> pool;
        size_t size{0};

     public:
        AllocatorDelete() : pool(*GetMemoryPool()) {}
        AllocatorDelete(MemoryPool& _pool, size_t _size) : pool(_pool), size(_size) {}
        AllocatorDelete(AllocatorDelete const&) = default;

        template <typename U>
        AllocatorDelete& operator=(const AllocatorDelete<U>& other) {
            pool = other.GetPool();
            size = other.GetSize();
            return *this;
        }

        AllocatorDelete& operator=(AllocatorDelete const& other) {
            pool = other.GetPool();
            size = other.GetSize();
            return *this;
        }

        AllocatorDelete& operator=(AllocatorDelete&& other) {
            pool = other.GetPool();
            size = other.GetSize();
            return *this;
        }

        template <typename U>
        AllocatorDelete(const AllocatorDelete<U>& other)  // NOLINT(google-explicit-constructor)
            : pool(other.GetPool()), size(other.GetSize()) {}

        MemoryPool& GetPool() const {
            return pool;
        }

        size_t GetSize() const {
            return size;
        }

        void operator()(T* p) const {
            if (p) {
                if (PAIMON_UNLIKELY(size == 0)) {
                    // This indicates a programming error where the deleter was not properly
                    // initialized with the correct allocation size. In debug builds, this
                    // will trigger an assertion. In release builds, we cannot safely
                    // deallocate without knowing the size, which may lead to undefined behavior.
                    assert(false &&
                           "Mismatched pointer allocation detected - deleter size is zero");
                    return;  // Cannot safely deallocate without size information
                }
                p->~T();
                pool.get().Free(reinterpret_cast<void*>(p), size);
            }
        }
    };

    /// Allocate an object on the pool and return a unique_ptr with custom deleter.
    ///
    /// This method provides a convenient way to allocate objects that are automatically
    /// managed by the memory pool. The returned unique_ptr will automatically deallocate
    /// the memory through the pool when it goes out of scope.
    ///
    /// @tparam T Type of object to allocate.
    /// @tparam Args Types of constructor arguments.
    /// @param args Constructor arguments to forward to T's constructor.
    /// @return unique_ptr managing the allocated object with pool-aware deleter.
    template <typename T, typename... Args>
    std::unique_ptr<T, AllocatorDelete<T>> AllocateUnique(Args&&... args) {
        struct DeferCondDeallocate {
            bool& cond;
            MemoryPool& pool;
            void* p;
            ~DeferCondDeallocate() {
                if (PAIMON_UNLIKELY(!cond)) {
                    pool.Free(p, sizeof(T));
                }
            }
        };
        auto p = Malloc(sizeof(T));
        {
            bool constructed = false;
            DeferCondDeallocate handler{constructed, *this, p};
            new (p) T(std::forward<Args>(args)...);
            constructed = true;
        }
        return std::unique_ptr<T, AllocatorDelete<T>>(reinterpret_cast<T*>(p),
                                                      AllocatorDelete<T>(*this, sizeof(T)));
    }
};

template <class T>
using pooled_unique_ptr = std::unique_ptr<T, MemoryPool::AllocatorDelete<T>>;

#define PAIMON_UNIQUE_PTR pooled_unique_ptr

}  // namespace paimon
