#pragma once

#include <c10/core/Storage.h>
#include <c10/macros/Export.h>
#include <c10/util/UniqueVoidPtr.h>

#include <atomic>
#include <memory>

namespace c10 {

// A RefcountedDeleterContext object is used as the `ctx` argument for DataPtr
// to implement a shared DataPtr. Normally, a DataPtr is unique, but we use
// this custom context and the `refcounted_deleter` function below to make the
// DataPtr act like a non-unique DataPtr. This context object holds onto an
// inner context and deleter function which handle the actual deletion of the
// data when the refcount reaches 0.
//
// This shared DataPtr feature is only used when storages are shared between
// multiple Python interpreters in MultiPy. Before storages had PyObject
// preservation, interpreters could just share the same StorageImpl instance.
// But now a StorageImpl can only be associated with one interpreter in order
// to properly manage a zombie PyObject. So we share storages across Python
// interpreters by creating a different StorageImpl instance for each one, but
// they all point to the same data.
struct C10_API RefcountedDeleterContext {
  RefcountedDeleterContext(void* other_ctx, c10::DeleterFnPtr other_deleter)
      : other_ctx(other_ctx, other_deleter), refcount(1) {}

  std::unique_ptr<void, c10::DeleterFnPtr> other_ctx;
  std::atomic_int refcount;
};

// `refcounted_deleter` is used as the `ctx_deleter` for DataPtr to implement
// a shared DataPtr.
//
// Warning: This should only be called on a pointer to
// a RefcountedDeleterContext that was allocated on the heap with `new`,
// because when the refcount reaches 0, the context is deleted with `delete`
C10_API void refcounted_deleter(void* ctx_);

// If the storage's DataPtr does not use `refcounted_deleter`, replace it with
// a DataPtr that does, so it can be shared between multiple StorageImpls
C10_API void maybeApplyRefcountedDeleter(const c10::Storage& storage);

// Create a new StorageImpl that points to the same data. If the original
// StorageImpl's DataPtr does not use `refcounted_deleter`, it will be replaced
// with one that does
C10_API c10::Storage newStorageImplFromRefcountedDataPtr(
    const c10::Storage& storage);

} // namespace c10
