/*
 * Copyright (c) Meta Platforms, Inc. and 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.
 */

#include <folly/Indestructible.h>

#include <functional>
#include <map>
#include <memory>
#include <string>
#include <tuple>

#include <folly/Memory.h>
#include <folly/portability/GTest.h>

using namespace std;
using namespace folly;

namespace {

struct Magic {
  function<void()> dtor_;
  function<void()> move_;
  Magic(function<void()> ctor, function<void()> dtor, function<void()> move)
      : dtor_(std::move(dtor)), move_(std::move(move)) {
    ctor();
  }
  Magic(Magic&& other) /* may throw */ { *this = std::move(other); }
  Magic& operator=(Magic&& other) {
    dtor_ = std::move(other.dtor_);
    move_ = std::move(other.move_);
    move_();
    return *this;
  }
  ~Magic() { dtor_(); }
};

template <typename T>
struct DeferredDtor {
  folly::Indestructible<T>& obj_;
  explicit constexpr DeferredDtor(folly::Indestructible<T>& obj) noexcept
      : obj_{obj} {}
  ~DeferredDtor() { obj_->~T(); }
};

class IndestructibleTest : public testing::Test {};
} // namespace

TEST_F(IndestructibleTest, access) {
  Indestructible<map<string, int>> data{
      map<string, int>{{"key1", 17}, {"key2", 19}, {"key3", 23}}};
  DeferredDtor s{data};

  auto& m = *data;
  EXPECT_EQ(19, m.at("key2"));
}

TEST_F(IndestructibleTest, no_destruction) {
  int state = 0;
  int value = 0;

  Indestructible<Magic> sing(
      [&] {
        ++state;
        value = 7;
      },
      [&] { state = -1; },
      [] {});
  EXPECT_EQ(1, state);
  EXPECT_EQ(7, value);

  sing.~Indestructible();
  EXPECT_EQ(1, state);
}

TEST_F(IndestructibleTest, empty) {
  const Indestructible<map<string, int>> data;
  auto& m = *data;
  EXPECT_EQ(0, m.size());
}

TEST_F(IndestructibleTest, disabled_default_ctor) {
  EXPECT_TRUE((std::is_constructible<Indestructible<int>>::value)) << "sanity";

  struct Foo {
    Foo(int) {}
  };
  EXPECT_FALSE((std::is_constructible<Indestructible<Foo>>::value));
  EXPECT_FALSE((std::is_constructible<Indestructible<Foo>, Magic>::value));
  EXPECT_TRUE((std::is_constructible<Indestructible<Foo>, int>::value));
}

TEST_F(IndestructibleTest, list_initialization) {
  folly::Indestructible<std::map<int, int>> map{{{1, 2}}};
  DeferredDtor s{map};

  EXPECT_EQ(map->at(1), 2);
}

namespace {
class InitializerListConstructible {
 public:
  InitializerListConstructible(InitializerListConstructible&&) = default;
  explicit InitializerListConstructible(std::initializer_list<int>) {}
  InitializerListConstructible(std::initializer_list<double>, double) {}
};
} // namespace

TEST_F(IndestructibleTest, initializer_list_in_place_initialization) {
  using I = InitializerListConstructible;
  std::ignore = Indestructible<I>{{1, 2, 3, 4}};
  std::ignore = Indestructible<I>{{1.2}, 4.2};
}

namespace {
class ExplicitlyMoveConstructible {
 public:
  ExplicitlyMoveConstructible() = default;
  explicit ExplicitlyMoveConstructible(ExplicitlyMoveConstructible&&) = default;
};
} // namespace

TEST_F(IndestructibleTest, list_initialization_explicit_implicit) {
  using E = ExplicitlyMoveConstructible;
  using I = std::map<int, int>;
  EXPECT_TRUE((!std::is_convertible<E, Indestructible<E>>::value));
  EXPECT_TRUE((std::is_convertible<I, Indestructible<I>>::value));
}

TEST_F(IndestructibleTest, conversion) {
  using I = std::map<string, string>;
  folly::Indestructible<I> map{I{{"foo", "bar"}}};
  DeferredDtor s{map};
  I& r = map;
  EXPECT_EQ(1, r.count("foo"));
  I const& cr = std::as_const(map);
  EXPECT_EQ(1, cr.count("foo"));
}

TEST_F(IndestructibleTest, factory_method) {
  struct Foo {
    Foo(int x) : value(x) {}
    Foo(const Foo&) = delete;

    const int value;
  };

  auto factory = [] { return Foo(42); };

  folly::Indestructible<Foo> foo(folly::factory_constructor, factory);
  EXPECT_EQ(42, foo->value);
}
