/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/Assertions.h"
#include "mozilla/Compiler.h"
#include "mozilla/Move.h"
#include "mozilla/TypeTraits.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Vector.h"

#include <stddef.h>

using mozilla::DefaultDelete;
using mozilla::IsSame;
using mozilla::MakeUnique;
using mozilla::Swap;
using mozilla::UniquePtr;
using mozilla::Vector;

#define CHECK(c) \
  do { \
    bool cond = !!(c); \
    MOZ_ASSERT(cond, "Failed assertion: " #c); \
    if (!cond) { \
      return false; \
    } \
  } while (false)

typedef UniquePtr<int> NewInt;
static_assert(sizeof(NewInt) == sizeof(int*), "stored most efficiently");

static size_t gADestructorCalls = 0;

struct A
{
public:
  A() : mX(0) {}
  virtual ~A() { gADestructorCalls++; }

  int mX;
};

static size_t gBDestructorCalls = 0;

struct B : public A
{
public:
  B() : mY(1) {}
  ~B() { gBDestructorCalls++; }

  int mY;
};

typedef UniquePtr<A> UniqueA;
typedef UniquePtr<B, UniqueA::DeleterType> UniqueB; // permit interconversion

static_assert(sizeof(UniqueA) == sizeof(A*), "stored most efficiently");
static_assert(sizeof(UniqueB) == sizeof(B*), "stored most efficiently");

struct DeleterSubclass : UniqueA::DeleterType {};

typedef UniquePtr<B, DeleterSubclass> UniqueC;
static_assert(sizeof(UniqueC) == sizeof(B*), "stored most efficiently");

static UniqueA
ReturnUniqueA()
{
  return UniqueA(new B);
}

static UniqueA
ReturnLocalA()
{
  UniqueA a(new A);
  return Move(a);
}

static void
TestDeleterType()
{
  // Make sure UniquePtr will use its deleter's pointer type if it defines one.
  typedef int* Ptr;
  struct Deleter {
    typedef Ptr pointer;
    Deleter() {}
    void operator()(int*p) {
      delete p;
    }
  };
  UniquePtr<Ptr, Deleter> u(new int, Deleter());
}

static bool
TestDefaultFreeGuts()
{
  static_assert(IsSame<NewInt::DeleterType, DefaultDelete<int> >::value,
                "weird deleter?");

  NewInt n1(new int);
  CHECK(n1);
  CHECK(n1.get() != nullptr);

  n1 = nullptr;
  CHECK(!n1);
  CHECK(n1.get() == nullptr);

  int* p1 = new int;
  n1.reset(p1);
  CHECK(n1);
  NewInt n2(Move(n1));
  CHECK(!n1);
  CHECK(n1.get() == nullptr);
  CHECK(n2.get() == p1);

  Swap(n1, n2);
  CHECK(n1.get() == p1);
  CHECK(n2.get() == nullptr);

  n1.swap(n2);
  CHECK(n1.get() == nullptr);
  CHECK(n2.get() == p1);
  delete n2.release();

  CHECK(n1.get() == nullptr);
  CHECK(n2 == nullptr);
  CHECK(nullptr == n2);

  int* p2 = new int;
  int* p3 = new int;
  n1.reset(p2);
  n2.reset(p3);
  CHECK(n1.get() == p2);
  CHECK(n2.get() == p3);

  n1.swap(n2);
  CHECK(n2 != nullptr);
  CHECK(nullptr != n2);
  CHECK(n2.get() == p2);
  CHECK(n1.get() == p3);

  UniqueA a1;
  CHECK(a1 == nullptr);
  a1.reset(new A);
  CHECK(gADestructorCalls == 0);
  CHECK(a1->mX == 0);

  B* bp1 = new B;
  bp1->mX = 5;
  CHECK(gBDestructorCalls == 0);
  a1.reset(bp1);
  CHECK(gADestructorCalls == 1);
  CHECK(a1->mX == 5);
  a1.reset(nullptr);
  CHECK(gADestructorCalls == 2);
  CHECK(gBDestructorCalls == 1);

  B* bp2 = new B;
  UniqueB b1(bp2);
  UniqueA a2(nullptr);
  a2 = Move(b1);
  CHECK(gADestructorCalls == 2);
  CHECK(gBDestructorCalls == 1);

  UniqueA a3(Move(a2));
  a3 = nullptr;
  CHECK(gADestructorCalls == 3);
  CHECK(gBDestructorCalls == 2);

  B* bp3 = new B;
  bp3->mX = 42;
  UniqueB b2(bp3);
  UniqueA a4(Move(b2));
  CHECK(b2.get() == nullptr);
  CHECK((*a4).mX == 42);
  CHECK(gADestructorCalls == 3);
  CHECK(gBDestructorCalls == 2);

  UniqueA a5(new A);
  UniqueB b3(new B);
  a5 = Move(b3);
  CHECK(gADestructorCalls == 4);
  CHECK(gBDestructorCalls == 2);

  ReturnUniqueA();
  CHECK(gADestructorCalls == 5);
  CHECK(gBDestructorCalls == 3);

  ReturnLocalA();
  CHECK(gADestructorCalls == 6);
  CHECK(gBDestructorCalls == 3);

  UniqueA a6(ReturnLocalA());
  a6 = nullptr;
  CHECK(gADestructorCalls == 7);
  CHECK(gBDestructorCalls == 3);

  UniqueC c1(new B);
  UniqueA a7(new B);
  a7 = Move(c1);
  CHECK(gADestructorCalls == 8);
  CHECK(gBDestructorCalls == 4);

  c1.reset(new B);

  UniqueA a8(Move(c1));
  CHECK(gADestructorCalls == 8);
  CHECK(gBDestructorCalls == 4);

  // These smart pointers still own B resources.
  CHECK(a4);
  CHECK(a5);
  CHECK(a7);
  CHECK(a8);
  return true;
}

static bool
TestDefaultFree()
{
  CHECK(TestDefaultFreeGuts());
  CHECK(gADestructorCalls == 12);
  CHECK(gBDestructorCalls == 8);
  return true;
}

static size_t FreeClassCounter = 0;

struct FreeClass
{
public:
  FreeClass() {}

  void operator()(int* aPtr)
  {
    FreeClassCounter++;
    delete aPtr;
  }
};

typedef UniquePtr<int, FreeClass> NewIntCustom;
static_assert(sizeof(NewIntCustom) == sizeof(int*),
              "stored most efficiently");

static bool
TestFreeClass()
{
  CHECK(FreeClassCounter == 0);
  {
    NewIntCustom n1(new int);
    CHECK(FreeClassCounter == 0);
  }
  CHECK(FreeClassCounter == 1);

  NewIntCustom n2;
  {
    NewIntCustom n3(new int);
    CHECK(FreeClassCounter == 1);
    n2 = Move(n3);
  }
  CHECK(FreeClassCounter == 1);
  n2 = nullptr;
  CHECK(FreeClassCounter == 2);

  n2.reset(nullptr);
  CHECK(FreeClassCounter == 2);
  n2.reset(new int);
  n2.reset();
  CHECK(FreeClassCounter == 3);

  NewIntCustom n4(new int, FreeClass());
  CHECK(FreeClassCounter == 3);
  n4.reset(new int);
  CHECK(FreeClassCounter == 4);
  n4.reset();
  CHECK(FreeClassCounter == 5);

  FreeClass f;
  NewIntCustom n5(new int, f);
  CHECK(FreeClassCounter == 5);
  int* p = n5.release();
  CHECK(FreeClassCounter == 5);
  delete p;

  return true;
}

typedef UniquePtr<int, DefaultDelete<int>&> IntDeleterRef;
typedef UniquePtr<A, DefaultDelete<A>&> ADeleterRef;
typedef UniquePtr<B, DefaultDelete<A>&> BDeleterRef;

static_assert(sizeof(IntDeleterRef) > sizeof(int*),
              "has to be heavier than an int* to store the reference");
static_assert(sizeof(ADeleterRef) > sizeof(A*),
              "has to be heavier than an A* to store the reference");
static_assert(sizeof(BDeleterRef) > sizeof(int*),
              "has to be heavier than a B* to store the reference");

static bool
TestReferenceDeleterGuts()
{
  DefaultDelete<int> delInt;
  IntDeleterRef id1(new int, delInt);

  IntDeleterRef id2(Move(id1));
  CHECK(id1 == nullptr);
  CHECK(nullptr != id2);
  CHECK(&id1.get_deleter() == &id2.get_deleter());

  IntDeleterRef id3(Move(id2));

  DefaultDelete<A> delA;
  ADeleterRef a1(new A, delA);
  a1.reset(nullptr);
  a1.reset(new B);
  a1 = nullptr;

  BDeleterRef b1(new B, delA);
  a1 = Move(b1);

  BDeleterRef b2(new B, delA);

  ADeleterRef a2(Move(b2));

  return true;
}

static bool
TestReferenceDeleter()
{
  gADestructorCalls = 0;
  gBDestructorCalls = 0;

  CHECK(TestReferenceDeleterGuts());

  CHECK(gADestructorCalls == 4);
  CHECK(gBDestructorCalls == 3);

  gADestructorCalls = 0;
  gBDestructorCalls = 0;
  return true;
}

typedef void (&FreeSignature)(void*);

static size_t DeleteIntFunctionCallCount = 0;

static void
DeleteIntFunction(void* aPtr)
{
  DeleteIntFunctionCallCount++;
  delete static_cast<int*>(aPtr);
}

static void
SetMallocedInt(UniquePtr<int, FreeSignature>& aPtr, int aI)
{
  int* newPtr = static_cast<int*>(malloc(sizeof(int)));
  *newPtr = aI;
  aPtr.reset(newPtr);
}

static UniquePtr<int, FreeSignature>
MallocedInt(int aI)
{
  UniquePtr<int, FreeSignature>
    ptr(static_cast<int*>(malloc(sizeof(int))), free);
  *ptr = aI;
  return Move(ptr);
}
static bool
TestFunctionReferenceDeleter()
{
  // Look for allocator mismatches and leaks to verify these bits
  UniquePtr<int, FreeSignature> i1(MallocedInt(17));
  CHECK(*i1 == 17);

  SetMallocedInt(i1, 42);
  CHECK(*i1 == 42);

  // These bits use a custom deleter so we can instrument deletion.
  {
    UniquePtr<int, FreeSignature> i2 =
      UniquePtr<int, FreeSignature>(new int(42), DeleteIntFunction);
    CHECK(DeleteIntFunctionCallCount == 0);

    i2.reset(new int(76));
    CHECK(DeleteIntFunctionCallCount == 1);
  }

  CHECK(DeleteIntFunctionCallCount == 2);

  return true;
}

template<typename T>
struct AppendNullptrTwice
{
  AppendNullptrTwice() {}

  bool operator()(Vector<T>& vec)
  {
    CHECK(vec.append(nullptr));
    CHECK(vec.append(nullptr));
    return true;
  }
};

static size_t AAfter;
static size_t BAfter;

static bool
TestVectorGuts()
{
  Vector<UniqueA> vec;
  CHECK(vec.append(new B));
  CHECK(vec.append(new A));
  CHECK(AppendNullptrTwice<UniqueA>()(vec));
  CHECK(vec.append(new B));

  size_t initialLength = vec.length();

  UniqueA* begin = vec.begin();
  bool appendA = true;
  do {
    CHECK(appendA ? vec.append(new A) : vec.append(new B));
    appendA = !appendA;
  } while (begin == vec.begin());

  size_t numAppended = vec.length() - initialLength;

  BAfter = numAppended / 2;
  AAfter = numAppended - numAppended / 2;

  CHECK(gADestructorCalls == 0);
  CHECK(gBDestructorCalls == 0);
  return true;
}

static bool
TestVector()
{
  gADestructorCalls = 0;
  gBDestructorCalls = 0;

  CHECK(TestVectorGuts());

  CHECK(gADestructorCalls == 3 + AAfter + BAfter);
  CHECK(gBDestructorCalls == 2 + BAfter);
  return true;
}

typedef UniquePtr<int[]> IntArray;
static_assert(sizeof(IntArray) == sizeof(int*),
              "stored most efficiently");

static bool
TestArray()
{
  static_assert(IsSame<IntArray::DeleterType, DefaultDelete<int[]> >::value,
                "weird deleter?");

  IntArray n1(new int[5]);
  CHECK(n1);
  CHECK(n1.get() != nullptr);

  n1 = nullptr;
  CHECK(!n1);
  CHECK(n1.get() == nullptr);

  int* p1 = new int[42];
  n1.reset(p1);
  CHECK(n1);
  IntArray n2(Move(n1));
  CHECK(!n1);
  CHECK(n1.get() == nullptr);
  CHECK(n2.get() == p1);

  Swap(n1, n2);
  CHECK(n1.get() == p1);
  CHECK(n2.get() == nullptr);

  n1.swap(n2);
  CHECK(n1.get() == nullptr);
  CHECK(n2.get() == p1);
  delete[] n2.release();

  CHECK(n1.get() == nullptr);
  CHECK(n2.get() == nullptr);

  int* p2 = new int[7];
  int* p3 = new int[42];
  n1.reset(p2);
  n2.reset(p3);
  CHECK(n1.get() == p2);
  CHECK(n2.get() == p3);

  n1.swap(n2);
  CHECK(n2.get() == p2);
  CHECK(n1.get() == p3);

  n1 = Move(n2);
  CHECK(n1.get() == p2);
  n1 = Move(n2);
  CHECK(n1.get() == nullptr);

  UniquePtr<A[]> a1(new A[17]);
  static_assert(sizeof(a1) == sizeof(A*),
                "stored most efficiently");

  UniquePtr<A[]> a2(new A[5], DefaultDelete<A[]>());
  a2.reset(nullptr);
  a2.reset(new A[17]);
  a2 = nullptr;

  UniquePtr<A[]> a3(nullptr);
  a3.reset(new A[7]);

  return true;
}

struct Q
{
  Q() {}
  Q(const Q&) {}

  Q(Q&, char) {}

  template<typename T>
  Q(Q, T&&, int) {}

  Q(int, long, double, void*) {}
};

static int randomInt() { return 4; }

static bool
TestMakeUnique()
{
  UniquePtr<int> a1(MakeUnique<int>());
  UniquePtr<long> a2(MakeUnique<long>(4));

  // no args, easy
  UniquePtr<Q> q0(MakeUnique<Q>());

  // temporary bound to const lval ref
  UniquePtr<Q> q1(MakeUnique<Q>(Q()));

  // passing through a non-const lval ref
  UniquePtr<Q> q2(MakeUnique<Q>(*q1, 'c'));

  // pass by copying, forward a temporary, pass by value
  UniquePtr<Q> q3(MakeUnique<Q>(Q(), UniquePtr<int>(), randomInt()));

  // various type mismatching to test "fuzzy" forwarding
  UniquePtr<Q> q4(MakeUnique<Q>('s', 66LL, 3.141592654, &q3));

  UniquePtr<char[]> c1(MakeUnique<char[]>(5));

  return true;
}

int
main()
{
  TestDeleterType();

  if (!TestDefaultFree()) {
    return 1;
  }
  if (!TestFreeClass()) {
    return 1;
  }
  if (!TestReferenceDeleter()) {
    return 1;
  }
  if (!TestFunctionReferenceDeleter()) {
    return 1;
  }
  if (!TestVector()) {
    return 1;
  }
  if (!TestArray()) {
    return 1;
  }
  if (!TestMakeUnique()) {
    return 1;
  }
  return 0;
}
