// Copyright 2019 The Chromium 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 BASE_CONTAINERS_ANY_INTERNAL_H_
#define BASE_CONTAINERS_ANY_INTERNAL_H_

#include <memory>
#include <utility>

#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/template_util.h"
#include "base/type_id.h"

namespace base {

namespace internal {

// Common non-templated implementation details for base::unique_any. If we ever
// want to support base::any this, file could be easily modified to do so
// (TypeOps would need to support copying).
class BASE_EXPORT AnyInternal {
 public:
  constexpr AnyInternal() noexcept : type_ops_(nullptr), union_({}) {}

  AnyInternal(AnyInternal&& other) noexcept
      : type_ops_(other.type_ops_), union_({}) {
    if (other.type_ops_)
      other.type_ops_->move_fn_ptr(&other, this);
  }

  ~AnyInternal();

  struct TypeOps;

  constexpr explicit AnyInternal(const TypeOps* type_ops)
      : type_ops_(type_ops), union_({}) {}

  void operator=(AnyInternal&& other) noexcept {
    reset();
    if (other.type_ops_)
      other.type_ops_->move_fn_ptr(&other, this);
    type_ops_ = other.type_ops_;
  }

  bool has_value() const noexcept { return !!type_ops_; }

  TypeId type() const noexcept {
    if (!type_ops_)
      return TypeId::From<void>();

    return type_ops_->type_fn_ptr();
  }

  void reset() noexcept;

  template <typename T, bool UseInlineStorage>
  struct ConstructHelper;

  template <bool UseInlineStorage>
  struct GetStorageHelper;

  template <typename T, bool UseInlineStorage, bool HasMoveConstructor>
  struct MoveHelper;

  template <typename T, bool UseInlineStorage>
  struct DeleteHelper;

  // Where possible we use the small object allocation optimization to avoid
  // heap allocations.
  struct OutlineAlloc {
    void* value;  // Holds a T

    template <typename T>
    T& value_as() {
      return *static_cast<T*>(value);
    }

    template <typename T>
    const T& value_as() const {
      return *static_cast<const T*>(value);
    }
  };

  struct alignas(sizeof(void*)) InlineAlloc {
    // Holds a T if small. Tweaked to hold a promise executor inline.
    char bytes[sizeof(void*) * 3];

    template <typename T>
    T& value_as() {
      return *reinterpret_cast<T*>(bytes);
    }

    template <typename T>
    const T& value_as() const {
      return *reinterpret_cast<const T*>(bytes);
    }
  };

  template <typename T>
  struct InlineStorageHelper {
    static constexpr bool kUseInlineStorage =
        (sizeof(T) <= sizeof(InlineAlloc));

    static_assert(
        std::alignment_of<T>::value <= sizeof(T),
        "Type T has alignment requirements that preclude it's storage inline.");
  };

  template <typename T>
  constexpr T* GetStorage() {
    return static_cast<T*>(
        GetStorageHelper<InlineStorageHelper<T>::kUseInlineStorage>::GetStorage(
            *this));
  }

  template <typename T>
  constexpr const T* GetStorage() const {
    return static_cast<const T*>(
        GetStorageHelper<InlineStorageHelper<T>::kUseInlineStorage>::GetStorage(
            *this));
  }

  using TypeIdFunctionPtr = TypeId (*)();
  using MoveFunctionPtr = void (*)(AnyInternal* src, AnyInternal* dest);
  using DeleteFunctionPtr = void (*)(AnyInternal* object);

  // Similar to a virtual function but we don't need a dynamic memory
  // allocation. One possible design alternative would be to fold these methods
  // into T and use T in InlineAlloc (which would now have to
  // be bigger to accommodate the vtable pointer).
  struct TypeOps {
    // TODO(alexclarke): If TypeId can be constexpr store TypeId here directly.
    TypeIdFunctionPtr type_fn_ptr;
    MoveFunctionPtr move_fn_ptr;
    DeleteFunctionPtr delete_fn_ptr;
  };

  template <typename T>
  struct TypeOpsHelper {
    static constexpr TypeOps type_ops = {
        &TypeId::From<T>,
        &AnyInternal::MoveHelper<T,
                                 InlineStorageHelper<T>::kUseInlineStorage,
                                 std::is_move_constructible<T>::value>::Move,
        &AnyInternal::
            DeleteHelper<T, InlineStorageHelper<T>::kUseInlineStorage>::Delete};
  };

  // Null if the instance has no value.
  const TypeOps* type_ops_;

  union {
    OutlineAlloc outline_alloc;
    InlineAlloc inline_alloc;
  } union_;
};

// static
template <typename T>
constexpr AnyInternal::TypeOps AnyInternal::TypeOpsHelper<T>::type_ops;

//static
template <typename T>
constexpr bool AnyInternal::InlineStorageHelper<T>::kUseInlineStorage;

template <typename T>
struct AnyInternal::ConstructHelper<T, /* UseInlineStorage */ true> {
  template <typename... Args>
  static void Construct(AnyInternal* dest, Args&&... args) noexcept {
    new (dest->union_.inline_alloc.bytes) T(std::forward<Args>(args)...);
  }
};

template <typename T>
struct AnyInternal::ConstructHelper<T, /* UseInlineStorage */ false> {
  template <typename... Args>
  static void Construct(AnyInternal* dest, Args&&... args) noexcept {
    dest->union_.outline_alloc.value = new T(std::forward<Args>(args)...);
  }
};

template <>
struct AnyInternal::GetStorageHelper</* UseInlineStorage */ true> {
  static void* GetStorage(AnyInternal& any) {
    return &any.union_.inline_alloc.bytes;
  }

  static const void* GetStorage(const AnyInternal& any) {
    return &any.union_.inline_alloc.bytes;
  }
};

template <>
struct AnyInternal::GetStorageHelper</* UseInlineStorage */ false> {
  static void* GetStorage(AnyInternal& any) {
    return any.union_.outline_alloc.value;
  }

  static const void* GetStorage(const AnyInternal& any) {
    return any.union_.outline_alloc.value;
  }
};

template <typename T>
struct AnyInternal::
    MoveHelper<T, /* UseInlineStorage */ true, /* HasMoveConstructor */ true> {
  static void Move(AnyInternal* src, AnyInternal* dest) {
    DCHECK_NE(src, dest);
    new (dest->union_.inline_alloc.bytes)
        T(std::move(src->union_.inline_alloc.value_as<T>()));
  }
};

template <typename T>
struct AnyInternal::
    MoveHelper<T, /* UseInlineStorage */ false, /* HasMoveConstructor */ true> {
  static void Move(AnyInternal* src, AnyInternal* dest) {
    DCHECK_NE(src, dest);
    dest->union_.outline_alloc.value =
        new T(std::move(src->union_.outline_alloc.value_as<T>()));
  }
};

template <typename T>
struct AnyInternal::
    MoveHelper<T, /* UseInlineStorage */ true, /* HasMoveConstructor */ false> {
  static void Move(AnyInternal* src, AnyInternal* dest) {
    DCHECK_NE(src, dest);
    // Fall back to the copy constructor.
    new (dest->union_.inline_alloc.bytes)
        T(src->union_.inline_alloc.value_as<T>());
  }
};

template <typename T>
struct AnyInternal::MoveHelper<T,
                               /* UseInlineStorage */ false,
                               /* HasMoveConstructor */ false> {
  static void Move(AnyInternal* src, AnyInternal* dest) {
    DCHECK_NE(src, dest);
    // Fall back to the copy constructor.
    dest->union_.outline_alloc.value =
        new T(src->union_.outline_alloc.value_as<T>());
  }
};

template <typename T>
struct AnyInternal::DeleteHelper<T, /* UseInlineStorage */ true> {
  static void Delete(AnyInternal* any) {
    reinterpret_cast<T*>(&any->union_.inline_alloc.bytes)->~T();
  }
};

template <typename T>
struct AnyInternal::DeleteHelper<T, /* UseInlineStorage */ false> {
  static void Delete(AnyInternal* any) {
    delete static_cast<T*>(any->union_.outline_alloc.value);
  }
};

}  // namespace internal
}  // namespace base

#endif  // BASE_CONTAINERS_ANY_INTERNAL_H_
