#include <mongoc/mongoc-shared-private.h>

#include <TestSuite.h>

typedef struct {
   int value;
   int *store_value_on_dtor;
} my_value;

my_value *
my_value_new(void)
{
   my_value *p = bson_malloc0(sizeof(my_value));
   p->value = 42;
   p->store_value_on_dtor = NULL;
   return p;
}

void
my_value_free(my_value *ptr)
{
   if (ptr->store_value_on_dtor) {
      *ptr->store_value_on_dtor = ptr->value;
   }
   ptr->value = 0;
   ptr->store_value_on_dtor = NULL;
   bson_free(ptr);
}

void
my_value_free_v(void *ptr)
{
   my_value_free((my_value *)(ptr));
}

static void
test_simple(void)
{
   int destroyed_value = 0;
   mongoc_shared_ptr ptr = MONGOC_SHARED_PTR_NULL;
   mongoc_shared_ptr ptr2, valptr_s;
   my_value *valptr;

   ASSERT(mongoc_shared_ptr_is_null(ptr));
   ptr = mongoc_shared_ptr_create(my_value_new(), my_value_free_v);
   ASSERT(!mongoc_shared_ptr_is_null(ptr));

   ASSERT_CMPINT(mongoc_shared_ptr_use_count(ptr), ==, 1);

   ptr2 = mongoc_shared_ptr_copy(ptr);

   ASSERT(ptr.ptr == ptr2.ptr);
   ASSERT(ptr._aux == ptr2._aux);

   valptr_s = mongoc_shared_ptr_copy(ptr);
   valptr = valptr_s.ptr;
   valptr->store_value_on_dtor = &destroyed_value;
   valptr->value = 133;
   mongoc_shared_ptr_reset_null(&valptr_s);
   /* Value hasn't changed yet */
   ASSERT_CMPINT(destroyed_value, ==, 0);

   /* Now drop the original reference */
   mongoc_shared_ptr_reset_null(&ptr);
   /* Check that the pointer is empty */
   ASSERT(mongoc_shared_ptr_is_null(ptr));

   /* Still not yet destroyed */
   ASSERT_CMPINT(destroyed_value, ==, 0);

   /* Check that the existing pointer is okay */
   ASSERT_CMPINT(((my_value *)ptr2.ptr)->value, ==, 133);

   /* Drop the last one */
   mongoc_shared_ptr_reset_null(&ptr2);
   ASSERT(mongoc_shared_ptr_is_null(ptr2));

   /* Now it was destroyed and set */
   ASSERT_CMPINT(destroyed_value, ==, 133);
}

struct widget {
   int value;
   int *store_value_here;
};

void
widget_delete(void *w_)
{
   struct widget *w = w_;
   *w->store_value_here = w->value;
   bson_free(w);
}

static void
test_aliased(void)
{
   int destroyed_valued = 0;
   int *i;
   struct widget *w;
   mongoc_shared_ptr ptr = MONGOC_SHARED_PTR_NULL;

   ptr = mongoc_shared_ptr_create(bson_malloc0(sizeof(struct widget)), widget_delete);
   w = ptr.ptr;
   w->store_value_here = &destroyed_valued;

   /* Alias 'ptr' to the `w->value` managed sub-object */
   ptr.ptr = &w->value;
   i = ptr.ptr;
   /* We can store through it okay. */
   *i = 42;
   ASSERT_CMPINT(w->value, ==, 42);

   /* Deleting with the aliased ptr is okay */
   ASSERT_CMPINT(destroyed_valued, ==, 0);
   mongoc_shared_ptr_reset_null(&ptr);
   ASSERT_CMPINT(destroyed_valued, ==, 42);
}

void
test_shared_install(TestSuite *suite)
{
   TestSuite_Add(suite, "/shared/simple", test_simple);
   TestSuite_Add(suite, "/shared/aliased", test_aliased);
}
