/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed 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 <memory>
#include <new>
#include <type_traits>

#include <folly/ScopeGuard.h>

namespace folly {
namespace coro {
namespace detail {

// Helper class for a variable with manually-controlled lifetime.
//
// You must explicitly call .construct() to construct/initialise the value.
//
// If it has been initialised then you must explicitly call .destruct() before
// the ManualLifetime object is destroyed to ensure the destructor is run.
template <typename T>
class ManualLifetime {
 public:
  ManualLifetime() noexcept {}
  ~ManualLifetime() {}

  template <
      typename... Args,
      std::enable_if_t<std::is_constructible<T, Args...>::value, int> = 0>
  void construct(Args&&... args) noexcept(
      noexcept(std::is_nothrow_constructible<T, Args...>::value)) {
    ::new (static_cast<void*>(std::addressof(value_)))
        T(static_cast<Args&&>(args)...);
  }

  void destruct() noexcept { value_.~T(); }

  const T& get() const& { return value_; }
  T& get() & { return value_; }
  const T&& get() const&& { return static_cast<const T&&>(value_); }
  T&& get() && { return static_cast<T&&>(value_); }

 private:
  union {
    std::remove_const_t<T> value_;
  };
};

template <typename T>
class ManualLifetime<T&> {
 public:
  ManualLifetime() noexcept : ptr_(nullptr) {}
  ~ManualLifetime() {}

  void construct(T& value) noexcept { ptr_ = std::addressof(value); }

  void destruct() noexcept { ptr_ = nullptr; }

  T& get() const noexcept { return *ptr_; }

 private:
  T* ptr_;
};

template <typename T>
class ManualLifetime<T&&> {
 public:
  ManualLifetime() noexcept : ptr_(nullptr) {}
  ~ManualLifetime() {}

  void construct(T&& value) noexcept { ptr_ = std::addressof(value); }

  void destruct() noexcept { ptr_ = nullptr; }

  T&& get() const noexcept { return static_cast<T&&>(*ptr_); }

 private:
  T* ptr_;
};

template <>
class ManualLifetime<void> {
 public:
  void construct() noexcept {}

  void destruct() noexcept {}

  void get() const noexcept {}
};

// For use when the ManualLifetime is a member of a union. First,
// it in-place constructs the ManualLifetime, making it the active
// member of the union. Then it calls 'construct' on it to construct
// the value inside it.
template <
    typename T,
    typename... Args,
    std::enable_if_t<std::is_constructible<T, Args...>::value, int> = 0>
void activate(ManualLifetime<T>& box, Args&&... args) noexcept(
    std::is_nothrow_constructible<T, Args...>::value) {
  auto* p = ::new (&box) ManualLifetime<T>{};
  // Use ScopeGuard to destruct the ManualLifetime if the 'construct' throws.
  auto guard = makeGuard([p]() noexcept { p->~ManualLifetime(); });
  p->construct(static_cast<Args&&>(args)...);
  guard.dismiss();
}

// For use when the ManualLifetime is a member of a union. First,
// it calls 'destruct' on the ManualLifetime to destroy the value
// inside it. Then it calls the destructor of the ManualLifetime
// object itself.
template <typename T>
void deactivate(ManualLifetime<T>& box) noexcept {
  box.destruct();
  box.~ManualLifetime();
}

} // namespace detail
} // namespace coro
} // namespace folly
