/*
 * Copyright (c) 2008-2012 Zmanda, Inc.  All Rights Reserved.
 * Copyright (c) 2013-2016 Carbonite, Inc.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 * Contact information: Carbonite Inc., 756 N Pastoria Ave
 * Sunnyvale, CA 94085, or: http://www.zmanda.com
 */

#include "amglue.h"

/* GSources are tricky to bind to perl for a few reasons:
 *  - they have a one-way state machine: once attached and detached, they
 *    cannot be re-attached
 *  - different "kinds" of GSources require C-level callbacks with
 *    different signatures
 *  - an attached GSource should continue running, even if not referenced
 *    from perl, while a detached GSource should free all its resources
 *    when no longer referenced.
 *
 * To accomplish all of this, this file implements a "glue object" called
 * amglue_Source.  There are zero or one amglue_Source objects for each
 * GSource object, so they serve as a place to store "extra" data about a
 * GSource.  In particular, they store:
 *  - a pointer to a C callback function that can trigger a Perl callback
 *  - a pointer to an SV representing the perl callback to run
 *  - a reference count
 * Any number of Perl SV's may reference the amglue_Source -- it tracks this
 * via its reference count.
 *
 * Let's look at this arrangement as it follows a typical usage scenario.  The
 * numbers in brackets are reference counts.
 *
 * -- my $src = Amanda::MainLoop::new_foo_source();
 * GSrc[1] <----) amSrc[1] <---- $src[1] <--- perl-stack
 *
 * The lexical $src contains a reference to the amglue_Source object, which is
 * referencing the underlying GSource object.  Pretty simple.  The amglue_Source
 * only counts one reference because the GSource isn't yet attached.  Think of
 * the ')' in the diagram as a weak reference.  If the perl scope were to end
 * now, all of these objects would be freed immediately.
 *
 * -- $src->set_callback(\&cb);
 *                              ,--> &cb[1]
 * GMainLoop --> GSrc[2] <---> amSrc[2]
 *                              ^--- $src[1] <--- perl-stack
 *
 * The GSource has been attached, so GMainLoop holds a reference to it.  The
 * amglue_Source incremented its own reference count, making the previous weak
 * reference a full reference, because the link from the GSource will be used
 * when a callback occurs.  The amglue_Source object also keeps a reference to
 * the callback coderef.
 *
 * -- return;
 *                              ,--> &cb[1]
 * GMainLoop --> GSrc[2] <---> amSrc[1]
 *
 * When the perl scope ends, the lexical $src is freed, reducing the reference
 * count on the amglue_Source to 1.  At this point, the object is not accessible
 * from perl, but it is still accessible from the GSource via a callback.
 *
 * -- # in callback
 *                              ,--> &cb[1]
 * GMainLoop --> GSrc[2] <---> amSrc[2] <--- $self[1] <--- perl-stack
 *
 * When the callback is invoked, a reference to the amglue_Source is placed on
 * the perl stack, so it is once again referenced twice.
 *
 * -- $self->remove();
 *               GSrc[1] <---) amSrc[1] <--- $self[1] <--- perl-stack
 *
 * Now the callback itself has called remove().  The amglue_Source object removes
 * the GSource from the MainLoop and drops its reference to the perl callback, and
 * decrements its refcount to again weaken the reference from the GSource.  The
 * amglue_Source is now useless, but since it is still in scope, it remains
 * allocated and accessible.
 *
 * -- return;
 *
 * When the callback returns, the last reference to SV is destroyed, reducing
 * the reference count to the amglue_Source to zero, reducing the reference to
 * the GSource to zero.  Everything is gone.
 */

/* We use a glib 'dataset' to attach an amglue_Source to each GSource
 * object.  This requires a Quark to describe the kind of data being
 * attached.
 *
 * We define a macro and corresponding global to support access
 * to our quark.  The compiler will optimize out all but the first
 * conditional in each function, which is just as we want it. */
static GQuark _quark = 0;
#define AMGLUE_SOURCE_QUARK \
    ( _quark?_quark:(_quark = g_quark_from_static_string("amglue_Source")) )

amglue_Source *
amglue_source_get(
    GSource *gsrc,
    GSourceFunc callback)
{
    amglue_Source *src;
    g_assert(gsrc != NULL);

    src = (amglue_Source *)g_dataset_id_get_data(gsrc, AMGLUE_SOURCE_QUARK);

    if (!src)
	src = amglue_source_new(gsrc, callback);
    else
	amglue_source_ref(src);

    return src;
}

amglue_Source *
amglue_source_new(
    GSource *gsrc,
    GSourceFunc callback)
{
    amglue_Source *src = g_new0(amglue_Source, 1);
    g_source_ref(gsrc);
    src->src = gsrc;
    src->callback = callback;
    src->state = AMGLUE_SOURCE_NEW;
    src->refcount = 1;
    g_dataset_id_set_data(gsrc, AMGLUE_SOURCE_QUARK, (gpointer)src);

    return src;
}

void
amglue_source_free(
    amglue_Source *self)
{
    /* if we're attached, we hold a circular reference to ourselves,
     * so we shouldn't be at refcount=0 */
    g_assert(self->state != AMGLUE_SOURCE_ATTACHED);
    g_assert(self->callback_sv == NULL);

    g_dataset_id_remove_data(self->src, AMGLUE_SOURCE_QUARK);
    g_source_unref(self->src);
    g_free(self);
}
