/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (2019)
  
	Adresse mèl :
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Damien
	CALISTE, laboratoire L_Sim, (2019)

	E-mail address:
	BILLARD, not reachable any more ;
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/
#include "iface_sourceable.h"

#include <string.h>
#include "config.h"

/**
 * SECTION:iface_sourceable
 * @short_description: an interface for sourceable objects.
 *
 * <para>This interface describes objects that can take data from a
 * #VisuNodeValues object. This model can be named and changed each
 * time a #VisuData is reloaded.</para> 
 */

/**
 * VisuSourceableData:
 *
 * An opaque structure to store internal implementation of #VisuSourceable.
 *
 * Since: 3.8
 */
struct _VisuSourceableData
{
  gchar *source;
  VisuData *dataObj;
  gulong added_sig, removed_sig;
  VisuNodeValues *model;
  gulong chg_sig;
};

enum {
  PROP_0,
  PROP_SOURCE,
  PROP_MODEL,
  N_PROPS
};
static GParamSpec *_properties[N_PROPS];

/* Sourceable interface. */
G_DEFINE_INTERFACE(VisuSourceable, visu_sourceable, G_TYPE_OBJECT)

static void visu_sourceable_default_init(VisuSourceableInterface *iface)
{
  /**
   * VisuSourceable::source:
   *
   * The source property used to get the model from.
   *
   * Since: 3.8
   */
  _properties[PROP_SOURCE] =
    g_param_spec_string("source", "Source", "name of the source property",
                        "", G_PARAM_READWRITE);
  g_object_interface_install_property(iface, _properties[PROP_SOURCE]);

  /**
   * VisuSourceable::model:
   *
   * The #VisuNodeValues object associated to the object.
   *
   * Since: 3.8
   */
  _properties[PROP_MODEL] =
    g_param_spec_object("model", "Model", "node property associated to the object",
                        VISU_TYPE_NODE_VALUES, G_PARAM_READWRITE);
  g_object_interface_install_property(iface, _properties[PROP_MODEL]);
}

/**
 * visu_sourceable_init:
 * @self: a #VisuSourceable object.
 *
 * Method used in initialisation of the object to allocate a
 * #VisuSourceableData structure. This structure should be store by
 * the implementer of the sourceable interface and returned by the
 * getSource() virtual method.
 *
 * Since: 3.8
 **/
void visu_sourceable_init(VisuSourceable *self)
{
  VisuSourceableData **src;

  g_return_if_fail(VISU_IS_SOURCEABLE(self));

  src = VISU_SOURCEABLE_GET_INTERFACE(self)->getSource(self);
  *src = g_malloc0(sizeof(VisuSourceableData));
}

/**
 * visu_sourceable_dispose:
 * @self: a #VisuSourceable object.
 *
 * This method should be called in the dispose() method of @self to
 * ensure that any data referenced in a #VisuSourceableData structure
 * are released.
 *
 * Since: 3.8
 **/
void visu_sourceable_dispose(VisuSourceable *self)
{
  VisuSourceableData **src;

  g_return_if_fail(VISU_IS_SOURCEABLE(self));

  src = VISU_SOURCEABLE_GET_INTERFACE(self)->getSource(self);
  if (*src)
    {
      visu_sourceable_follow(self, (VisuData*)0);
      visu_sourceable_setNodeModel(self, (VisuNodeValues*)0);
      visu_sourceable_setSource(self, (const gchar*)0);
      g_free(*src);
    }
}

/**
 * visu_sourceable_setSource:
 * @self: a #VisuSourceable object.
 * @source: a property name.
 *
 * @self can be bound to a specific #VisuNodeValues defined by
 * its name. When @source is not %NULL and @self attached to a
 * #VisuData, its model is changed to match any #VisuNodeValues of its
 * current #VisuNodeArray with the label @source.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the value is actually changed.
 **/
gboolean visu_sourceable_setSource(VisuSourceable *self, const gchar *source)
{
  VisuSourceableData *src;
  VisuNodeValues *model;

  g_return_val_if_fail(VISU_IS_SOURCEABLE(self), FALSE);

  src = *VISU_SOURCEABLE_GET_INTERFACE(self)->getSource(self);
  if (!src || !g_strcmp0(src->source, source))
    return FALSE;

  DBG_fprintf(stderr, "Iface Sourceable: set source '%s'.\n", source);

  g_free(src->source);
  src->source = g_strdup(source);
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_SOURCE]);

  if (src->dataObj && src->source)
    {
      model = visu_data_getNodeProperties(src->dataObj, src->source);
      if (model)
        visu_sourceable_setNodeModel(self, model);
    }
  return TRUE;
}
/**
 * visu_sourceable_getSource:
 * @self: a #VisuSourceable object.
 *
 * Retrieves the name of the property @model should be bound to
 * retrieve its #VisuNodeValues model.
 *
 * Since: 3.8
 *
 * Returns: (allow-none): a property name.
 **/
const gchar* visu_sourceable_getSource(const VisuSourceable *self)
{
  VisuSourceableData *src;

  g_return_val_if_fail(VISU_IS_SOURCEABLE(self), (const gchar*)0);

  src = *VISU_SOURCEABLE_GET_INTERFACE(self)->getSource(VISU_SOURCEABLE(self));
  return src ? src->source: (const gchar*)0;
}

static void onModelChanged(VisuSourceable *self)
{
  if (VISU_SOURCEABLE_GET_INTERFACE(self)->modelChanged)
    VISU_SOURCEABLE_GET_INTERFACE(self)->modelChanged(self);
}
/**
 * visu_sourceable_setNodeModel:
 * @self: The #VisuSourceable to attached to.
 * @model: (allow-none): a #VisuNodeValues object.
 *
 * Set @model to @self.
 *
 * Since: 3.8
 *
 * Returns: TRUE if changed.
 **/
gboolean visu_sourceable_setNodeModel(VisuSourceable *self,
                                      VisuNodeValues *model)
{
  VisuSourceableData *src;
  
  g_return_val_if_fail(VISU_IS_SOURCEABLE(self), FALSE);

  src = *VISU_SOURCEABLE_GET_INTERFACE(self)->getSource(self);
  if (!src || src->model == model)
    return FALSE;

  if (src->model)
    {
      g_signal_handler_disconnect(G_OBJECT(src->model), src->chg_sig);
      g_object_unref(src->model);
    }
  src->model = model;
  if (model)
    {
      g_object_ref(model);
      src->chg_sig = g_signal_connect_swapped(G_OBJECT(model), "changed",
                                              G_CALLBACK(onModelChanged), self);
    }
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_MODEL]);
  onModelChanged(self);
  return TRUE;
}
/**
 * visu_sourceable_getNodeModel:
 * @self: a #VisuSourceable object.
 *
 * Retrieves associated model, if any.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the associated model.
 **/
VisuNodeValues* visu_sourceable_getNodeModel(VisuSourceable *self)
{
  VisuSourceableData *src;
  
  g_return_val_if_fail(VISU_IS_SOURCEABLE(self), (VisuNodeValues*)0);

  src = *VISU_SOURCEABLE_GET_INTERFACE(self)->getSource(self);
  return src ? src->model : (VisuNodeValues*)0;
}
/**
 * visu_sourceable_getConstNodeModel:
 * @self: a #VisuSourceable object.
 *
 * Retrieves associated model, if any.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the associated model.
 **/
const VisuNodeValues* visu_sourceable_getConstNodeModel(const VisuSourceable *self)
{
  VisuSourceableData *src;
  
  g_return_val_if_fail(VISU_IS_SOURCEABLE(self), (const VisuNodeValues*)0);

  src = *VISU_SOURCEABLE_GET_INTERFACE(self)->getSource(VISU_SOURCEABLE(self));
  return src ? src->model : (const VisuNodeValues*)0;
}

static void onNodePropAdded(VisuSourceable *self, VisuNodeValues *prop)
{
  if (!visu_sourceable_getSource(self))
    return;

  if (!g_strcmp0(visu_node_values_getLabel(prop), visu_sourceable_getSource(self)))
    visu_sourceable_setNodeModel(self, prop);
}
static void onNodePropRemoved(VisuSourceable *self, VisuNodeValues *prop)
{
  if (!visu_sourceable_getSource(self))
    return;

  if (!g_strcmp0(visu_node_values_getLabel(prop), visu_sourceable_getSource(self)))
    visu_sourceable_setNodeModel(self, (VisuNodeValues*)0);
}
/**
 * visu_sourceable_follow:
 * @self: a #VisuSourceable object.
 * @data: (allow-none): a #VisuData object.
 *
 * Set the #VisuNodeValues @self is based on by polling the node
 * property of @data referenced by the source name of @self (see
 * visu_sourceable_setSource()). If @self is not following any named
 * property, this call does nothing. If @self is following a named
 * property and @data is %NULL, the node model is set to %NULL also.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the followed @data is actually changed.
 **/
gboolean visu_sourceable_follow(VisuSourceable *self, VisuData *data)
{
  VisuSourceableData *src;
  VisuNodeValues *model;
  
  g_return_val_if_fail(VISU_IS_SOURCEABLE(self), FALSE);

  src = *VISU_SOURCEABLE_GET_INTERFACE(self)->getSource(VISU_SOURCEABLE(self));
  if (!src || !src->source || src->dataObj == data)
    return FALSE;

  model = (VisuNodeValues*)0;
  if (src->dataObj)
    {
      g_signal_handler_disconnect(src->dataObj, src->added_sig);
      g_signal_handler_disconnect(src->dataObj, src->removed_sig);
      g_object_unref(src->dataObj);
    }
  src->dataObj = data;
  if (data)
    {
      g_object_ref(data);
      src->added_sig = g_signal_connect_swapped(G_OBJECT(data), "node-properties-added",
                                                G_CALLBACK(onNodePropAdded), self);
      src->removed_sig = g_signal_connect_swapped(G_OBJECT(data), "node-properties-removed",
                                                  G_CALLBACK(onNodePropRemoved), self);
      model = visu_data_getNodeProperties(data, src->source);
    }
  visu_sourceable_setNodeModel(self, model);
  return TRUE;
}
