// Copyright 2016 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "core/fxcrt/maybe_owned.h"

#include <memory>
#include <utility>

#include "core/fxcrt/unowned_ptr.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace fxcrt {
namespace {

class PseudoDeletable {
 public:
  explicit PseudoDeletable(int id, int* count_location)
      : id_(id), count_location_(count_location) {}
  ~PseudoDeletable() { ++(*count_location_); }
  int GetID() const { return id_; }

 private:
  int id_;
  int* count_location_;
};

}  // namespace

TEST(MaybeOwned, Null) {
  MaybeOwned<PseudoDeletable> ptr1;
  EXPECT_FALSE(ptr1.IsOwned());
  EXPECT_FALSE(ptr1);
  EXPECT_FALSE(ptr1.Get());

  MaybeOwned<PseudoDeletable> ptr2;
  EXPECT_TRUE(ptr1 == ptr2);
  EXPECT_FALSE(ptr1 != ptr2);
}

TEST(MaybeOwned, NotOwned) {
  int delete_count = 0;
  PseudoDeletable thing1(100, &delete_count);
  {
    MaybeOwned<PseudoDeletable> ptr(&thing1);
    EXPECT_FALSE(ptr.IsOwned());
    EXPECT_EQ(ptr.Get(), &thing1);
    EXPECT_EQ(100, ptr->GetID());
    EXPECT_TRUE(ptr == &thing1);
    EXPECT_FALSE(ptr != &thing1);

    MaybeOwned<PseudoDeletable> empty;
    EXPECT_FALSE(ptr == empty);
    EXPECT_TRUE(ptr != empty);
  }
  EXPECT_EQ(0, delete_count);

  delete_count = 0;
  PseudoDeletable thing2(200, &delete_count);
  {
    MaybeOwned<PseudoDeletable> ptr(&thing1);
    ptr = &thing2;
    EXPECT_FALSE(ptr.IsOwned());
    EXPECT_EQ(ptr.Get(), &thing2);
    EXPECT_EQ(200, ptr->GetID());
  }
  EXPECT_EQ(0, delete_count);

  delete_count = 0;
  int owned_delete_count = 0;
  {
    MaybeOwned<PseudoDeletable> ptr(&thing1);
    EXPECT_EQ(100, ptr->GetID());
    ptr = std::make_unique<PseudoDeletable>(300, &owned_delete_count);
    EXPECT_TRUE(ptr.IsOwned());
    EXPECT_EQ(300, ptr->GetID());
  }
  EXPECT_EQ(0, delete_count);
  EXPECT_EQ(1, owned_delete_count);
}

TEST(MaybeOwned, UnownedPtr) {
  int delete_count = 0;
  PseudoDeletable thing1(100, &delete_count);
  PseudoDeletable thing2(200, &delete_count);
  UnownedPtr<PseudoDeletable> unowned1(&thing1);
  UnownedPtr<PseudoDeletable> unowned2(&thing2);
  {
    MaybeOwned<PseudoDeletable> ptr1(unowned1);
    MaybeOwned<PseudoDeletable> ptr2(unowned2);
    ptr2 = unowned1;
    ptr1 = unowned2;
  }
  EXPECT_EQ(0, delete_count);
}

TEST(MaybeOwned, Owned) {
  int delete_count = 0;
  {
    MaybeOwned<PseudoDeletable> ptr(
        std::make_unique<PseudoDeletable>(100, &delete_count));
    EXPECT_TRUE(ptr.IsOwned());
    EXPECT_EQ(100, ptr->GetID());

    MaybeOwned<PseudoDeletable> empty;
    EXPECT_FALSE(ptr == empty);
    EXPECT_TRUE(ptr != empty);
  }
  EXPECT_EQ(1, delete_count);

  delete_count = 0;
  {
    MaybeOwned<PseudoDeletable> ptr(
        std::make_unique<PseudoDeletable>(200, &delete_count));
    ptr = std::make_unique<PseudoDeletable>(300, &delete_count);
    EXPECT_TRUE(ptr.IsOwned());
    EXPECT_EQ(300, ptr->GetID());
    EXPECT_EQ(1, delete_count);
  }
  EXPECT_EQ(2, delete_count);

  delete_count = 0;
  int unowned_delete_count = 0;
  PseudoDeletable thing2(400, &unowned_delete_count);
  {
    MaybeOwned<PseudoDeletable> ptr(
        std::make_unique<PseudoDeletable>(500, &delete_count));
    ptr = &thing2;
    EXPECT_FALSE(ptr.IsOwned());
    EXPECT_EQ(400, ptr->GetID());
    EXPECT_EQ(1, delete_count);
    EXPECT_EQ(0, unowned_delete_count);
  }
  EXPECT_EQ(1, delete_count);
  EXPECT_EQ(0, unowned_delete_count);
}

TEST(MaybeOwned, Release) {
  int delete_count = 0;
  {
    std::unique_ptr<PseudoDeletable> stolen;
    {
      MaybeOwned<PseudoDeletable> ptr(
          std::make_unique<PseudoDeletable>(100, &delete_count));
      EXPECT_TRUE(ptr.IsOwned());
      stolen = ptr.Release();
      EXPECT_FALSE(ptr.IsOwned());
      EXPECT_EQ(ptr, stolen);
      EXPECT_EQ(0, delete_count);
    }
    EXPECT_EQ(0, delete_count);
  }
  EXPECT_EQ(1, delete_count);
}

TEST(MaybeOwned, Move) {
  int delete_count = 0;
  PseudoDeletable thing1(100, &delete_count);
  {
    MaybeOwned<PseudoDeletable> ptr1(&thing1);
    MaybeOwned<PseudoDeletable> ptr2(
        std::make_unique<PseudoDeletable>(200, &delete_count));
    EXPECT_FALSE(ptr1.IsOwned());
    EXPECT_TRUE(ptr2.IsOwned());

    MaybeOwned<PseudoDeletable> ptr3(std::move(ptr1));
    MaybeOwned<PseudoDeletable> ptr4(std::move(ptr2));
    EXPECT_FALSE(ptr1.IsOwned());  // Unowned and null.
    EXPECT_FALSE(ptr1.Get());
    EXPECT_TRUE(ptr2.IsOwned());  // Owned but null.
    EXPECT_FALSE(ptr2.Get());
    EXPECT_FALSE(ptr3.IsOwned());
    EXPECT_TRUE(ptr4.IsOwned());
    EXPECT_EQ(0, delete_count);
    EXPECT_EQ(100, ptr3->GetID());
    EXPECT_EQ(200, ptr4->GetID());

    MaybeOwned<PseudoDeletable> ptr5;
    MaybeOwned<PseudoDeletable> ptr6;
    ptr5 = std::move(ptr3);
    ptr6 = std::move(ptr4);
    EXPECT_FALSE(ptr3.IsOwned());  // Unowned and null.
    EXPECT_FALSE(ptr3.Get());
    EXPECT_TRUE(ptr4.IsOwned());  // Owned but null.
    EXPECT_FALSE(ptr4.Get());
    EXPECT_FALSE(ptr5.IsOwned());
    EXPECT_TRUE(ptr6.IsOwned());
    EXPECT_EQ(0, delete_count);
    EXPECT_EQ(100, ptr5->GetID());
    EXPECT_EQ(200, ptr6->GetID());
  }
  EXPECT_EQ(1, delete_count);
}

namespace {

class Thing {
 public:
  int x = 42;
};

class Owner {
 public:
  explicit Owner(std::unique_ptr<Thing> thing) : thing_(std::move(thing)) {}

 private:
  std::unique_ptr<Thing> thing_;
};

class Manager {
 public:
  Manager()
      : transient_(std::make_unique<Thing>()),
        owner_(std::make_unique<Owner>(transient_.Release())),
        thing_(std::move(transient_).Get()) {}

  bool has_transient() const { return !!transient_.Get(); }

 private:
  MaybeOwned<Thing> transient_;         // For initializng next two members.
  const std::unique_ptr<Owner> owner_;  // Must outlive thing_.
  const UnownedPtr<Thing> thing_;
};

}  // namespace

TEST(MaybeOwned, MoveElisionThwarted) {
  // Test fails if the std::move() in Manager::Manager() is elided.
  Manager manager;
  EXPECT_FALSE(manager.has_transient());
}

}  // namespace fxcrt
