#include <string.h>
#include <glib.h>

/**
 * SECTION:shared-ptr
 * @title: Shared Pointers
 * @short_description: reference counted pointers
 *
 * Shared pointers provide an easy path to reference counted types
 * without the difficulties or overhead of GObject while allowing you
 * to preserve compatibility with existing software.
 *
 * The idea is that instead of using g_malloc() (or something similar)
 * to allocate storage for a struct, g_shared_alloc() or
 * g_shared_alloc0() would be used.  Reference counting is then
 * handled by g_shared_ref() and g_shared_unref(), and when the
 * reference count drops to zero a callback is invoked to clean up the
 * contents of the struct before the associated memory is released.
 *
 * Note that shared pointers should not be used for blobs of binary
 * data--you should use #GBytes for that.
 **/


// Public API
gpointer g_shared_alloc      (gsize size, GDestroyNotify notify);
gpointer g_shared_alloc0     (gsize size, GDestroyNotify notify);
#define g_shared_new(type,notify) \
  ((type*) g_shared_alloc(sizeof(type),notify))
#define g_shared_new0(type,notify) \
  ((type*) g_shared_alloc0(sizeof(type),notify))

gpointer g_shared_ref        (gpointer data);
void     g_shared_unref      (gpointer data);

guint    g_shared_weak_ref   (gpointer data, GFunc func, gpointer user_data, GDestroyNotify notify);
gboolean g_shared_weak_unref (gpointer data, guint weak_ref_id);

typedef struct _GSharedPtr GSharedPtr;

struct _GSharedPtr {
  gpointer data;
};

GSharedPtr* g_shared_ptr_new (gpointer data, GDestroyNotify notify);
// End public API


struct GSharedWeakRef {
  guint id;
  GFunc func;
  gpointer user_data;
  GDestroyNotify notify;
};

struct GShared {
  volatile guint ref_count;
  GDestroyNotify notify;

  guint weak_refs_count;
  GSequence* weak_refs;

  /* The data is actually at the &fake_data, not fake_data.  This is
     here so we can use G_STRUCT_OFFSET to calculate how much room we
     need in addition to what was requested.  I decided to do it this
     way instead of just omitting fake_data and using sizeof(struct
     GShared) because I think C compilers will be more likely to add
     some padding when necessary for better alignment. */
  gpointer fake_data;
};

#define G_SHARED_HEADER_OFFSET G_STRUCT_OFFSET(struct GShared, fake_data)
#define G_SHARED_DATA_TO_HEADER(data) \
  ((struct GShared*) (((guint8*) data) - G_SHARED_HEADER_OFFSET))
#define G_SHARED_HEADER_TO_DATA(header) (&(shared->fake_data))

/**
 * g_shared_alloc:
 * @size: size in bytes to allocate
 * @notify: callback to be invoked when reference count drops to zero
 *
 * Allocate a shared pointer.
 *
 * Returns: (transfer full): a new shared pointer
 */
gpointer
g_shared_alloc (gsize size, GDestroyNotify notify)
{
  struct GShared* shared;

  g_return_val_if_fail (size > 0, NULL);

  /* Note that while this currently uses g_malloc, it could easily be
     changed to g_slice_alloc().  GShared would need to be expanded to
     add a gsize to house the size argument so it could be passed to
     g_slice_free1(), but changing the GShared struct should be safe
     as long as fake_data stays at the end. */
  shared = g_malloc (size + G_SHARED_HEADER_OFFSET);
  shared->ref_count = 1;
  shared->notify = notify;
  shared->weak_refs_count = 0;
  shared->weak_refs = NULL;

  return G_SHARED_HEADER_TO_DATA (shared);
}

/**
 * g_shared_alloc0:
 * @size: size in bytes to allocate
 * @notify: callback to be invoked when reference count drops to zero
 *
 * Allocates a shared pointer via g_shared_alloc() and initializes the
 * returned memory to 0.
 *
 * Returns: (transfer full): a new shared pointer
 */
gpointer
g_shared_alloc0 (gsize size, GDestroyNotify notify)
{ 
  gpointer data = g_shared_alloc (size, notify);
  if (data != NULL)
    {
      memset (data, 0, size);
    }

  return data;
}

/**
 * g_shared_new:
 * @type: the type to allocate, typically a structure name
 * @notify: callback to be invoked when reference count drops to zero
 *
 * A convenience macro to allocate a shared pointer.
 *
 * It calls g_shared_alloc0() with <literal>sizeof (@type)</literal>
 * and casts the returned pointer to a pointer of the given type,
 * avoiding a type cast in the source code.
 */

/**
 * g_shared_new0:
 * @type: the type to allocate, typically a structure name
 * @notify: callback to be invoked when reference count drops to zero
 *
 * A convenience macro to allocate a shared pointer and set the memory
 * to 0.
 *
 * It calls g_shared_alloc0() with <literal>sizeof (@type)</literal>
 * and casts the returned pointer to a pointer of the given type,
 * avoiding a type cast in the source code.
 */

/**
 * g_shared_ref:
 * @data: a shared pointer
 *
 * Increase the reference count on a shared pointer.
 *
 * Returns: (transfer full): @data, with its reference count increased
 * by one.
 */
gpointer
g_shared_ref (gpointer data)
{
  struct GShared* shared;

  g_return_val_if_fail (data != NULL, NULL);

  shared = G_SHARED_DATA_TO_HEADER (data);
  g_atomic_int_inc (&(shared->ref_count));

  return data;
}

static void
g_shared_invoke_weak_ref (struct GSharedWeakRef* weak_ref, gpointer data)
{
  weak_ref->func (data, weak_ref->user_data);
}

/**
 * g_shared_unref:
 * @data: (transfer full): a shared pointer
 *
 * Decreases the reference count on @data.  If the reference count
 * drops to zero the destroy notify callback is invoked then the
 * associated memory is released.
 */
void
g_shared_unref (gpointer data)
{
  struct GShared* shared;

  g_return_if_fail (data != NULL);

  shared = G_SHARED_DATA_TO_HEADER (data);

  if (g_atomic_int_dec_and_test (&(shared->ref_count)))
    {
      if (shared->weak_refs != NULL)
        {
          g_sequence_foreach (shared->weak_refs, (GFunc) g_shared_invoke_weak_ref, data);

          g_sequence_free (shared->weak_refs);
          shared->weak_refs = NULL;
        }

      if (shared->notify != NULL)
        {
          shared->notify (data);
          shared->notify = NULL;
        }

      g_free (shared);
    }
}

static int
g_shared_weak_ref_compare (struct GSharedWeakRef* a, struct GSharedWeakRef* b, gpointer user_data)
{
  return (a->id - b->id);
}

static struct GSharedWeakRef*
g_shared_weak_ref_new (guint id, GFunc func, gpointer user_data, GDestroyNotify notify)
{
  struct GSharedWeakRef* weak_ref;

  weak_ref = g_slice_new (struct GSharedWeakRef);
  weak_ref->id = id;
  weak_ref->func = func;
  weak_ref->user_data = user_data;
  weak_ref->notify = notify;

  return weak_ref;
}

static void
g_shared_weak_ref_free (struct GSharedWeakRef* weak_ref)
{
  if (weak_ref->notify != NULL)
    {
      weak_ref->notify (weak_ref->user_data);
    }
  g_slice_free (struct GSharedWeakRef, weak_ref);
}

/**
 * g_shared_weak_ref:
 * @data: shared pointer
 * @func: callback to invoke when @data is to be destroyed
 * @user_data: data to pass to @func
 * @notify: called when @user_data is no longer necessary
 *
 * Add a weak reference to a shared pointer.
 *
 * Returns: an identifier which can be passed to g_shared_weak_unref()
 * to remove the weak reference.
 */
guint
g_shared_weak_ref (gpointer data, GFunc func, gpointer user_data, GDestroyNotify notify)
{
  struct GShared* shared;
  struct GSharedWeakRef* weak_ref;
  guint index;

  g_return_val_if_fail (data != NULL, 0);
  shared = G_SHARED_DATA_TO_HEADER (data);

  if (shared->weak_refs == NULL)
    {
      shared->weak_refs = g_sequence_new ((GDestroyNotify) g_shared_weak_ref_free);
    }

  weak_ref = g_shared_weak_ref_new (++shared->weak_refs_count, func, user_data, notify);

  g_sequence_insert_sorted (shared->weak_refs, weak_ref,
                            (GCompareDataFunc) g_shared_weak_ref_compare, NULL);

  return weak_ref->id;
}

/**
 * g_shared_weak_unref:
 * @data: a shared pointer
 * @weak_ref_id: the identifier of the weak reference
 *
 * Remove a weak reference.
 *
 * Returns: %TRUE if a weak reference was removed, %FALSE if not
 */
gboolean
g_shared_weak_unref (gpointer data, guint weak_ref_id)
{
  struct GShared* shared;
  g_return_if_fail (data != NULL);
  shared = G_SHARED_DATA_TO_HEADER (data);

  if (shared->weak_refs == NULL)
    {
      return FALSE;
    }
  else
    {
      struct GSharedWeakRef target = { weak_ref_id, NULL, NULL };
      struct GSharedWeakRef* result;
      GSequenceIter* iter;

      iter = g_sequence_search (shared->weak_refs, &target,
                                (GCompareDataFunc) g_shared_weak_ref_compare, NULL);

      if (g_sequence_iter_is_begin (iter))
        {
          return FALSE;
        }

      iter = g_sequence_iter_prev (iter);
      result = g_sequence_get (iter);
      if (result->id != weak_ref_id)
        {
          return FALSE;
        }

      g_sequence_remove (iter);
      return TRUE;
    }

  g_assert_not_reached ();
}

/**
 * GSharedPtr:
 * @data: the content
 *
 * A generic container for a single container which is used to layer
 * reference counting on top of pointers for which you have no control
 * over the allocator and, therefore, cannot cannot be allocated with
 * g_shared_alloc().
 *
 * Use g_shared_ref() and g_shared_unref() to ref and unref instances.
 */

typedef struct _GSharedPtrReal {
  gpointer data;
  GDestroyNotify notify;
} GSharedPtrReal;

static void
g_shared_ptr_destroy_notify (GSharedPtr* ptr)
{
  GSharedPtrReal* real_ptr = (GSharedPtrReal*) ptr;

  g_return_if_fail (ptr != NULL);

  if (real_ptr->notify != NULL)
    {
      real_ptr->notify (real_ptr->data);
    }
}

/**
 * g_shared_ptr_new:
 * @data: data
 * @notify: destroy notify
 *
 * Creates a new #GSharedPtr
 */
GSharedPtr*
g_shared_ptr_new (gpointer data, GDestroyNotify notify)
{
  GSharedPtrReal* real_ptr;

  real_ptr = g_shared_new (GSharedPtrReal,
                           (GDestroyNotify) g_shared_ptr_destroy_notify);

  g_return_val_if_fail (real_ptr != NULL, NULL);

  real_ptr->data = data;
  real_ptr->notify = notify;

  return real_ptr;
}





/******** Tests / Examples ********/

typedef struct _Foo {
  int one;
  int two;
  int three;
} Foo;

void
foo_destroy (Foo* foo)
{
  memset (foo, 0, sizeof (Foo));
}

Foo*
foo_new ()
{
  Foo* foo = g_shared_new (Foo, (GDestroyNotify) foo_destroy);
  foo->one = 1;
  foo->two = 2;
  foo->three = 3;
  return foo;
}

void
foo_weak_notify (Foo* foo, gpointer user_data)
{
  g_debug ("Destroy notify for Foo %p { %d, %d, %d }", foo, foo->one, foo->two, foo->three);
}

void
foo_test ()
{
  Foo* foo = foo_new ();
  g_debug ("Got a new Foo %p { %d, %d, %d }", foo, foo->one, foo->two, foo->three);

  guint weak_ref_id = g_shared_weak_ref (foo, (GFunc) foo_weak_notify, g_strdup ("Test"), g_free);
  g_shared_weak_unref (foo, weak_ref_id);
  g_shared_weak_ref (foo, (GFunc) foo_weak_notify, g_strdup ("Test"), g_free);

  g_shared_ref (foo);
  g_debug ("Ref a Foo %p { %d, %d, %d }", foo, foo->one, foo->two, foo->three);
  g_shared_unref (foo);
  g_debug ("Unref a Foo %p { %d, %d, %d }", foo, foo->one, foo->two, foo->three);
  g_shared_unref (foo);
  g_debug ("Unref (hopefully destroyed) a Foo %p", foo);
}





typedef struct _Callbacks {
  GFunc foo;
  GSharedPtr* foo_user_data;
  GFunc bar;
  GSharedPtr* bar_user_data;
  GFunc baz;
  GSharedPtr* baz_user_data;
} Callbacks;

void
callbacks_invoke_foo (Callbacks* callbacks, gpointer data)
{
  if (callbacks->foo != NULL)
    callbacks->foo (data, callbacks->foo_user_data->data);
}

void
callbacks_invoke_bar (Callbacks* callbacks, gpointer data)
{
  if (callbacks->bar != NULL)
    callbacks->bar (data, callbacks->bar_user_data->data);
}

void
callbacks_invoke_baz (Callbacks* callbacks, gpointer data)
{
  if (callbacks->baz != NULL)
    callbacks->baz (data, callbacks->baz_user_data->data);
}

void
callbacks_set_foo (Callbacks* callbacks, GFunc foo, gpointer user_data, GDestroyNotify notify)
{
  if (callbacks->foo_user_data != NULL)
    callbacks->foo_user_data = (g_shared_unref (callbacks->foo_user_data), NULL);
  callbacks->foo = foo;
  if (user_data != NULL)
    callbacks->foo_user_data = g_shared_ptr_new (user_data, notify);
}

void
callbacks_set_bar (Callbacks* callbacks, GFunc bar, gpointer user_data, GDestroyNotify notify)
{
  if (callbacks->bar_user_data != NULL)
    callbacks->bar_user_data = (g_shared_unref (callbacks->bar_user_data), NULL);
  callbacks->bar = bar;
  if (user_data != NULL)
    callbacks->bar_user_data = g_shared_ptr_new (user_data, notify);
}

void
callbacks_set_baz (Callbacks* callbacks, GFunc baz, gpointer user_data, GDestroyNotify notify)
{
  if (callbacks->baz_user_data != NULL)
    callbacks->baz_user_data = (g_shared_unref (callbacks->baz_user_data), NULL);
  callbacks->baz = baz;
  if (user_data != NULL)
    callbacks->baz_user_data = g_shared_ptr_new (user_data, notify);
}

void
callbacks_set_callbacks (Callbacks* callbacks,
                   GFunc foo,
                   GFunc bar,
                   GFunc baz,
                   gpointer user_data,
                   GDestroyNotify notify) {
  GSharedPtr* shared_user_data = g_shared_ptr_new (user_data, notify);

  if (callbacks->foo_user_data != NULL)
    g_shared_unref (callbacks->foo_user_data);
  callbacks->foo = foo;
  callbacks->foo_user_data = shared_user_data;

  if (callbacks->bar_user_data != NULL)
    g_shared_unref (callbacks->bar_user_data);
  callbacks->bar = bar;
  callbacks->bar_user_data = g_shared_ref (shared_user_data);

  if (callbacks->baz_user_data != NULL)
    g_shared_unref (callbacks->baz_user_data);
  callbacks->baz = baz;
  callbacks->baz_user_data = g_shared_ref (shared_user_data);
}

void
callbacks_foo_cb (gpointer data, gpointer user_data)
{
  g_debug ("callbacks_foo_cb (\"%s\", \"%s\");", data, user_data);
}

void
callbacks_bar_cb (gpointer data, gpointer user_data)
{
  g_debug ("callbacks_bar_cb (\"%s\", \"%s\");", data, user_data);
}

void
callbacks_baz_cb (gpointer data, gpointer user_data)
{
  g_debug ("callbacks_baz_cb (\"%s\", \"%s\");", data, user_data);
}

void
vocal_g_free (gpointer user_data)
{
  g_debug ("g_free (%p: \"%s\")", user_data, user_data);
  g_free (user_data);
}

void
callbacks_destroy (Callbacks* callbacks)
{
  g_debug ("Destroying Callbacks %p", callbacks);

  if (callbacks->foo_user_data != NULL)
    callbacks->foo_user_data = (g_shared_unref (callbacks->foo_user_data), NULL);
  if (callbacks->bar_user_data != NULL)
    callbacks->bar_user_data = (g_shared_unref (callbacks->bar_user_data), NULL);
  if (callbacks->baz_user_data != NULL)
    callbacks->baz_user_data = (g_shared_unref (callbacks->baz_user_data), NULL);
}

void
callbacks_test ()
{
  Callbacks callbacks = { 0, };

  g_debug ("Callbacks %p", &callbacks);

  gchar* user_data = g_strdup ("user data");
  g_debug ("callbacks_set_callbacks (%p, %p, %p, %p, %p, %p)", &callbacks, callbacks_foo_cb, callbacks_bar_cb, callbacks_baz_cb, user_data, vocal_g_free);
  callbacks_set_callbacks (&callbacks, callbacks_foo_cb, callbacks_bar_cb, callbacks_baz_cb, user_data, vocal_g_free);

  callbacks_invoke_foo (&callbacks, "Foo says");
  callbacks_invoke_bar (&callbacks, "Bar says");
  callbacks_invoke_baz (&callbacks, "Baz says");

  callbacks_set_foo (&callbacks, callbacks_foo_cb, g_strdup ("foo user data"), vocal_g_free);
  callbacks_set_bar (&callbacks, callbacks_bar_cb, g_strdup ("bar user data"), vocal_g_free);
  callbacks_set_baz (&callbacks, callbacks_baz_cb, g_strdup ("baz user data"), vocal_g_free);
  // user_data should have been freed

  callbacks_invoke_foo (&callbacks, "Foo says");
  callbacks_invoke_bar (&callbacks, "Bar says");
  callbacks_invoke_baz (&callbacks, "Baz says");

  callbacks_destroy (&callbacks);
}




int main (int argc, char** argv) {
  foo_test ();
  callbacks_test ();

  return 0;
}
