// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#ifndef LIB_JXL_MEMORY_MANAGER_INTERNAL_H_
#define LIB_JXL_MEMORY_MANAGER_INTERNAL_H_

// Memory allocator with support for alignment + misalignment.

#include <jxl/memory_manager.h>

#include <cstddef>
#include <memory>
#include <utility>

#include "lib/jxl/base/common.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/status.h"

namespace jxl {

namespace memory_manager_internal {

// To avoid RFOs, match L2 fill size (pairs of lines); 2 x cache line size.
static constexpr size_t kAlignment = 2 * 64;
static_assert((kAlignment & (kAlignment - 1)) == 0,
              "kAlignment must be a power of 2");

// Minimum multiple for which cache set conflicts and/or loads blocked by
// preceding stores can occur.
static constexpr size_t kNumAlignmentGroups = 16;
static constexpr size_t kAlias = kNumAlignmentGroups * kAlignment;
static_assert((kNumAlignmentGroups & (kNumAlignmentGroups - 1)) == 0,
              "kNumAlignmentGroups must be a power of 2");

}  // namespace memory_manager_internal

// Initializes the memory manager instance with the passed one. The
// MemoryManager passed in |memory_manager| may be NULL or contain NULL
// functions which will be initialized with the default ones. If either alloc
// or free are NULL, then both must be NULL, otherwise this function returns an
// error.
Status MemoryManagerInit(JxlMemoryManager* self,
                         const JxlMemoryManager* memory_manager);

void* MemoryManagerAlloc(const JxlMemoryManager* memory_manager, size_t size);
void MemoryManagerFree(const JxlMemoryManager* memory_manager, void* address);

// Helper class to be used as a deleter in a unique_ptr<T> call.
class MemoryManagerDeleteHelper {
 public:
  explicit MemoryManagerDeleteHelper(const JxlMemoryManager* memory_manager)
      : memory_manager_(memory_manager) {}

  // Delete and free the passed pointer using the memory_manager.
  template <typename T>
  void operator()(T* address) const {
    if (!address) {
      return;
    }
    address->~T();
    return memory_manager_->free(memory_manager_->opaque, address);
  }

 private:
  const JxlMemoryManager* memory_manager_;
};

template <typename T>
using MemoryManagerUniquePtr = std::unique_ptr<T, MemoryManagerDeleteHelper>;

// Creates a new object T allocating it with the memory allocator into a
// unique_ptr; not to be used outside JXL_MEMORY_MANAGER_MAKE_UNIQUE_OR_RETURN.
template <typename T, typename... Args>
JXL_INLINE MemoryManagerUniquePtr<T> MemoryManagerMakeUniquePrivate(
    const JxlMemoryManager* memory_manager, Args&&... args) {
  T* mem =
      static_cast<T*>(memory_manager->alloc(memory_manager->opaque, sizeof(T)));
  if (!mem) {
    // Allocation error case.
    return MemoryManagerUniquePtr<T>(nullptr,
                                     MemoryManagerDeleteHelper(memory_manager));
  }
  return MemoryManagerUniquePtr<T>(new (mem) T(std::forward<Args>(args)...),
                                   MemoryManagerDeleteHelper(memory_manager));
}

// NOLINTBEGIN(bugprone-macro-parentheses)
// NB: ARGS should be in parentheses on instantiation side; it contains
//     arguments for MemoryManagerMakeUniquePrivate, including `memory_manager`.
#define JXL_MEMORY_MANAGER_MAKE_UNIQUE_OR_RETURN(NAME, TYPE, ARGS, RETURN) \
  auto NAME = ::jxl::MemoryManagerMakeUniquePrivate<TYPE> ARGS;            \
  if (!NAME) {                                                             \
    return RETURN;                                                         \
  }
// NOLINTEND(bugprone-macro-parentheses)

// Returns recommended distance in bytes between the start of two consecutive
// rows.
StatusOr<size_t> BytesPerRow(size_t xsize, size_t sizeof_t);

class AlignedMemory {
 public:
  AlignedMemory()
      : allocation_(nullptr), memory_manager_(nullptr), address_(nullptr) {}

  // Copy disallowed.
  AlignedMemory(const AlignedMemory& other) = delete;
  AlignedMemory& operator=(const AlignedMemory& other) = delete;

  // Custom move.
  AlignedMemory(AlignedMemory&& other) noexcept;
  AlignedMemory& operator=(AlignedMemory&& other) noexcept;

  ~AlignedMemory();

  static StatusOr<AlignedMemory> Create(JxlMemoryManager* memory_manager,
                                        size_t size, size_t pre_padding = 0);

  explicit operator bool() const noexcept { return (address_ != nullptr); }

  template <typename T>
  T* address() const {
    return reinterpret_cast<T*>(address_);
  }
  JxlMemoryManager* memory_manager() const { return memory_manager_; }

  // TODO(eustas): we can offer "actually accessible" size; it is 0-2KiB bigger
  //               than requested size, due to generous alignment;
  //               might be useful for resizeable containers (e.g. PaddedBytes)

 private:
  AlignedMemory(JxlMemoryManager* memory_manager, void* allocation,
                size_t pre_padding);

  void* allocation_;
  JxlMemoryManager* memory_manager_;
  void* address_;
};

template <typename T>
class AlignedArray {
 public:
  AlignedArray() : size_(0) {}

  static StatusOr<AlignedArray> Create(JxlMemoryManager* memory_manager,
                                       size_t size) {
    size_t storage_size;
    if (!SafeMul(size, sizeof(T), storage_size)) {
      return JXL_FAILURE("Allocation too large");
    }
    JXL_ASSIGN_OR_RETURN(AlignedMemory storage,
                         AlignedMemory::Create(memory_manager, storage_size));
    T* items = storage.address<T>();
    for (size_t i = 0; i < size; ++i) {
      new (items + i) T();
    }
    return AlignedArray<T>(std::move(storage), size);
  }

  // Copy disallowed.
  AlignedArray(const AlignedArray& other) = delete;
  AlignedArray& operator=(const AlignedArray& other) = delete;

  // Custom move.
  AlignedArray(AlignedArray&& other) noexcept {
    size_ = other.size_;
    storage_ = std::move(other.storage_);
    other.size_ = 0;
  }

  AlignedArray& operator=(AlignedArray&& other) noexcept {
    if (this == &other) return *this;
    size_ = other.size_;
    storage_ = std::move(other.storage_);
    other.size_ = 0;
    return *this;
  }

  ~AlignedArray() {
    if (!size_) return;
    T* items = storage_.address<T>();
    for (size_t i = 0; i < size_; ++i) {
      items[i].~T();
    }
  }

  T& operator[](const size_t i) {
    JXL_DASSERT(i < size_);
    return *(storage_.address<T>() + i);
  }
  const T& operator[](const size_t i) const {
    JXL_DASSERT(i < size_);
    return *(storage_.address<T>() + i);
  }

 private:
  explicit AlignedArray(AlignedMemory&& storage, size_t size)
      : size_(size), storage_(std::move(storage)) {}
  size_t size_;
  AlignedMemory storage_;
};

}  // namespace jxl

#endif  // LIB_JXL_MEMORY_MANAGER_INTERNAL_H_
