// Bug 564005 - Valgrind errors and crash on exit with Gtk::UIManager
// Bug 154498 - Unnecessary warning on console: signalproxy_connectionnode.cc

#include <glibmm.h>
#include <iostream>
#include <sigc++/sigc++.h>
#include <stdlib.h>

#define ACTIVATE_BUG 1

// A class with its own reference-count, for use with RefPtr.
class Something
{
public:
  Something() : ref_count_(1), max_ref_count_(ref_count_) {}

  void reference()
  {
    ++ref_count_;

    // Track the highest-ever max count.
    if (max_ref_count_ < ref_count_)
      max_ref_count_ = ref_count_;
  }

  void unreference()
  {
    if (--ref_count_ <= 0)
      delete this;
  }

  // Just so we can check it in our test.
  int ref_count() { return ref_count_; }

  // Just so we can check it in our test.
  int max_ref_count() { return max_ref_count_; }

private:
  int ref_count_;
  int max_ref_count_;
};

class SomethingDerived : public Something
{
};

class Parent
{
public:
  explicit Parent(const Glib::RefPtr<Something>& something)
  : something_(something),
    was_constructed_via_copy_constructor_(true),
    was_constructed_via_move_constructor_(false)
  {
  }

  explicit Parent(Glib::RefPtr<Something>&& something)
  : something_(std::move(something)),
    was_constructed_via_copy_constructor_(false),
    was_constructed_via_move_constructor_(true)
  {
  }

  // Non copyable
  Parent(const Parent& src) = delete;
  Parent& operator=(const Parent& src) = delete;

  bool was_constructed_via_copy_constructor() const
  {
    return was_constructed_via_copy_constructor_;
  }

  bool was_constructed_via_move_constructor() const
  {
    return was_constructed_via_move_constructor_;
  }

  int something_ref_count() const { return something_->ref_count(); }

  int something_max_ref_count() const { return something_->max_ref_count(); }

private:
  Glib::RefPtr<Something> something_;
  bool was_constructed_via_copy_constructor_;
  bool was_constructed_via_move_constructor_;
};

static void
test_initial_refcount()
{
  Glib::RefPtr<Something> refSomething(new Something());
  g_assert_cmpint(refSomething->ref_count(), ==, 1);
  g_assert_cmpint(refSomething->max_ref_count(), ==, 1);

  // Test the get() method:
  g_assert_cmpint(refSomething.get()->ref_count(), ==, 1);
  refSomething.reset();
  g_assert(refSomething.get() == nullptr);
}

static void
test_refptr_copy_constructor()
{
  Glib::RefPtr<Something> refSomething(new Something());
  g_assert_cmpint(refSomething->ref_count(), ==, 1);
  g_assert_cmpint(refSomething->max_ref_count(), ==, 1);

  {
    Glib::RefPtr<Something> refSomething2(refSomething);
    g_assert_cmpint(refSomething->ref_count(), ==, 2);
    g_assert_cmpint(refSomething2->ref_count(), ==, 2);
    g_assert_cmpint(refSomething->max_ref_count(), ==, 2);
  }

  // Test the refcount after other references should have been released
  // when other RefPtrs went out of scope:
  g_assert_cmpint(refSomething->ref_count(), ==, 1);
  g_assert_cmpint(refSomething->max_ref_count(), ==, 2);
}

static void
test_refptr_assignment_operator()
{
  Glib::RefPtr<Something> refSomething(new Something());
  g_assert_cmpint(refSomething->ref_count(), ==, 1);
  g_assert_cmpint(refSomething->max_ref_count(), ==, 1);

  {
    Glib::RefPtr<Something> refSomething2 = refSomething;
    g_assert_cmpint(refSomething->ref_count(), ==, 2);
    g_assert_cmpint(refSomething2->ref_count(), ==, 2);
    g_assert_cmpint(refSomething->max_ref_count(), ==, 2);
  }

  // Test the refcount after other references should have been released
  // when other RefPtrs went out of scope:
  g_assert_cmpint(refSomething->ref_count(), ==, 1);
  g_assert_cmpint(refSomething->max_ref_count(), ==, 2);
}

static Glib::RefPtr<Something>
get_something()
{
  static Glib::RefPtr<Something> something_to_get;

  // Reinitialize it each time:
  something_to_get = Glib::RefPtr<Something>(new Something());

  return something_to_get;
}

static void
test_refptr_with_parent_copy_constructor()
{
  // We use get_something() because test_refptr_with_parent_move_constructor() does.
  Glib::RefPtr<Something> refSomething = get_something();
  g_assert_cmpint(refSomething->ref_count(), ==, 2); // 1 here and 1 inside get_something()
  g_assert_cmpint(refSomething->max_ref_count(), ==, 2);

  {
    Parent parent(refSomething);
    g_assert(!parent.was_constructed_via_move_constructor());
    g_assert(parent.was_constructed_via_copy_constructor());
    g_assert_cmpint(
      parent.something_ref_count(), ==, 3); // 1 here, 1 in parent, and 1 inside get_something()
    g_assert_cmpint(parent.something_max_ref_count(), ==, 3);
  }

  // Test the refcount after other references should have been released
  // when other RefPtrs went out of scope:
  g_assert_cmpint(refSomething->ref_count(), ==, 2); // 1 here and 1 inside get_something()
  g_assert_cmpint(refSomething->max_ref_count(), ==, 3);
}

static void
test_refptr_with_parent_move_constructor()
{
  Parent parent(get_something());
  g_assert(parent.was_constructed_via_move_constructor());
  g_assert(!parent.was_constructed_via_copy_constructor());
  g_assert_cmpint(parent.something_ref_count(), ==, 2); // 1 in parent and 1 inside get_something()
  g_assert_cmpint(parent.something_max_ref_count(), ==, 2);
}

static void
test_refptr_move_constructor()
{
  Glib::RefPtr<Something> refSomething(new Something());
  Glib::RefPtr<Something> refSomething2(std::move(refSomething));
  g_assert_cmpint(refSomething2->ref_count(), ==, 1);
  g_assert(!refSomething);
  g_assert_cmpint(refSomething2->max_ref_count(), ==, 1);
}

static void
test_refptr_move_assignment_operator()
{
  Glib::RefPtr<Something> refSomething(new Something());
  Glib::RefPtr<Something> refSomething2;
  refSomething2 = std::move(refSomething);
  g_assert_cmpint(refSomething2->ref_count(), ==, 1);
  g_assert(!refSomething);
  g_assert_cmpint(refSomething2->max_ref_count(), ==, 1);
}

static void
test_refptr_universal_reference_move_constructor()
{
  Glib::RefPtr<SomethingDerived> refSomethingDerived(new SomethingDerived());
  Glib::RefPtr<Something> refSomething(std::move(refSomethingDerived));
  g_assert_cmpint(refSomething->ref_count(), ==, 1);
  g_assert(!refSomethingDerived);
  g_assert_cmpint(refSomething->max_ref_count(), ==, 1);
}

static void
test_refptr_universal_reference_asignment_operator()
{
  Glib::RefPtr<SomethingDerived> refSomethingDerived(new SomethingDerived());
  Glib::RefPtr<Something> refSomething;
  refSomething = std::move(refSomethingDerived);
  g_assert_cmpint(refSomething->ref_count(), ==, 1);
  g_assert(!refSomethingDerived);
  g_assert_cmpint(refSomething->max_ref_count(), ==, 1);
}

int
main(int, char**)
{
  // Test initial refcount:
  test_initial_refcount();

  // Test refcount when using the RefPtr copy constructor:
  test_refptr_copy_constructor();

  // Test refcount when using the RefPtr assignment operator (operator=):
  test_refptr_assignment_operator();

  // Test the refcount when using the RefPtr move constuctor:
  test_refptr_move_constructor();

  // Test the refcount when using the RefPtr move asignment operator (operator=):
  test_refptr_move_assignment_operator();

  // Test the refcount when another class makes a copy via its constructor:
  test_refptr_with_parent_copy_constructor();

  // Test the refcount when another class makes a copy via its
  //(perfect-forwarding) move constructor, which should not involve a temporary
  // instance:
  test_refptr_with_parent_move_constructor();

  // Test the refcount when using the RefPtr move constructor with derived class
  // as an argument.
  test_refptr_universal_reference_move_constructor();

  // Test the refcount when using the RefPtr assignment operator (operator=)
  // with derived class as an argument.
  test_refptr_universal_reference_asignment_operator();

  return EXIT_SUCCESS;
}
