// Copyright (c) 2025 GeometryFactory Sarl (France).
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1.1/STL_Extension/include/CGAL/Random_allocator.h $
// $Id: include/CGAL/Random_allocator.h 08b27d3db14 $
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s)     : Laurent Rineau

#ifndef CGAL_RANDOM_ALLOCATOR_H
#define CGAL_RANDOM_ALLOCATOR_H

#include <boost/container/static_vector.hpp>
#include <boost/dynamic_bitset.hpp>
#include <memory>
#include <vector>
#include <random>

#if CGAL_DEBUG_RANDOM_ALLOCATOR
#  if __has_include(<format>)
#    include <iostream>
#    include <format>
#    if __cpp_lib_format >= 201907L
#      define CGAL_DEBUG_RANDOM_ALLOCATOR_OKAY 1
#    endif
#  endif
#  if ! CGAL_DEBUG_RANDOM_ALLOCATOR_OKAY
#    error "CGAL_DEBUG_RANDOM_ALLOCATOR requires <format> and C++20 std::format"
#  endif
#endif

namespace CGAL {

// A random allocator that allocates blocks of memory and returns random
// locations. To use only for debugging purposes. That allocator is:
//   - not efficient,
//   - not thread-safe,
//   - and increases memory-fragmentation and non-locality.
template <typename T, typename Upstream_allocator = std::allocator<T>> class Random_allocator
{
  constexpr static std::size_t minimal_block_size = 1024;
  constexpr static std::size_t random_size = 64;

  struct Blocks
  {
    struct Block
    {
      T* data = nullptr;
      boost::dynamic_bitset<> available;
      std::size_t maximal_continuous_free_space = 0;

      auto size() const { return available.size(); }
    };

    Upstream_allocator alloc;
    std::vector<Block> blocks; // List of allocated blocks

    void allocate_new_block(std::size_t block_size = minimal_block_size)
    {
      auto& block = blocks.emplace_back();
      block.data = alloc.allocate(block_size);
      block.available.resize(block_size, true);
      block.maximal_continuous_free_space = block_size;
    }

    Blocks(const Upstream_allocator& alloc) : alloc(alloc) { allocate_new_block(); }

    ~Blocks()
    {
      for(auto& block : blocks) {
        alloc.deallocate(block.data, block.size());
      }
    }
  };
  using Block = typename Blocks::Block;
  using Ptr = std::shared_ptr<Blocks>;

  Ptr ptr_;
  std::mt19937 gen;

public:
  using value_type = T;
  using size_type = std::size_t;
  using difference_type = std::ptrdiff_t;
  using pointer = T*;
  using const_pointer = const T*;
  using reference = T&;
  using const_reference = const T&;
  using allocator_type = Upstream_allocator;

  Random_allocator(const Upstream_allocator& alloc = {}) noexcept;

  pointer allocate(size_type n, const void* hint = 0);
  void deallocate(pointer p, size_type n);

  template <typename U, typename... Args> void construct(U* p, Args&&... args);
  template <typename U> void destroy(U* p);

  size_type max_size() const noexcept;

  template <typename U> struct rebind
  {
    using upstream_traits = std::allocator_traits<Upstream_allocator>;
    using other_upstream_allocator = typename upstream_traits::template rebind_alloc<U>;
    using other = Random_allocator<U, other_upstream_allocator>;
  };

  bool operator==(const Random_allocator&) const noexcept;
  bool operator!=(const Random_allocator&) const noexcept;
};

// Implementation of Random_allocator methods
template <typename T, typename Upstream_allocator>
Random_allocator<T, Upstream_allocator>::Random_allocator(const Upstream_allocator& alloc) noexcept
    : ptr_(std::make_shared<Blocks>(alloc)), gen(std::random_device()())
{
}

template <typename T, typename Upstream_allocator>
typename Random_allocator<T, Upstream_allocator>::pointer
Random_allocator<T, Upstream_allocator>::allocate(size_type n, const void* hint)
{
  boost::container::static_vector<std::pair<Block*, size_type>, random_size> found_spaces;
  for(auto it = ptr_->blocks.rbegin(), end = ptr_->blocks.rend(); it != end; ++it) {
    auto& block = *it;
    if(block.maximal_continuous_free_space < n)
      continue;
    auto& available = block.available;
    const auto block_size = block.size();
    size_type found_max_free_space = 0;
    auto index = available.find_first();
    while(index != boost::dynamic_bitset<>::npos) {
      available.flip();
      const auto end_of_free_block = (std::min)(available.find_next(index), block_size);
      available.flip();
      auto free_space = end_of_free_block - index;
      found_max_free_space = (std::max)(found_max_free_space, free_space);
      while(free_space > n && found_spaces.size() < found_spaces.capacity()) {
        found_spaces.emplace_back(std::addressof(block), index);
        free_space -= n;
        index += n;
      }
      index = block.available.find_next(end_of_free_block);
    }
    block.maximal_continuous_free_space = found_max_free_space;
    if(found_spaces.size() == found_spaces.capacity())
      break;
  }
  if(!found_spaces.empty()) {
    std::uniform_int_distribution<> dis(0, found_spaces.size() - 1);
    auto i = dis(gen);
    auto [block, index] = found_spaces[i];
    block->available.set(index, n, false);
#if CGAL_DEBUG_RANDOM_ALLOCATOR
    std::clog << std::format("CGAL::Random_allocator debug info: n = {}, found_spaces.size() = {}, i = {},"
                             " block id = {}, block size = {}, index = {}\n",
                             n, found_spaces.size(), i, block - ptr_->blocks.data(), block->size(), index);
#endif // CGAL_DEBUG_RANDOM_ALLOCATOR
    return block->data + index;
  }
  size_type block_size = (std::max)(n * random_size, minimal_block_size);
  ptr_->allocate_new_block(block_size);
  return allocate(n, hint);
}

template <typename T, typename Upstream_allocator>
void Random_allocator<T, Upstream_allocator>::deallocate(pointer p, size_type n)
{
  for(auto& block : ptr_->blocks) {
    if(block.data <= p && p < block.data + block.size()) {
      block.available.set(p - block.data, n, true);
      return;
    }
  }
}

template <typename T, typename Upstream_allocator>
template <typename U, typename... Args>
void Random_allocator<T, Upstream_allocator>::construct(U* p, Args&&... args)
{
  ::new((void*)p) U(std::forward<Args>(args)...);
}

template <typename T, typename Upstream_allocator>
template <typename U>
void Random_allocator<T, Upstream_allocator>::destroy(U* p)
{
  p->~U();
}

template <typename T, typename Upstream_allocator>
typename Random_allocator<T, Upstream_allocator>::size_type
Random_allocator<T, Upstream_allocator>::max_size() const noexcept
{
  return (std::numeric_limits<size_type>::max)() / sizeof(T);
}

} // namespace CGAL

#endif // CGAL_RANDOM_ALLOCATOR_H
