/* gst-editing-services
 * Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com>
 *               <2013> Collabora Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/**
 * SECTION:gestimelineelement
 * @title: GESTimelineElement
 * @short_description: Base Class for all elements with some temporal extent
 * within a #GESTimeline.
 *
 * A #GESTimelineElement will have some temporal extent in its
 * corresponding #GESTimelineElement:timeline, controlled by its
 * #GESTimelineElement:start and #GESTimelineElement:duration. This
 * determines when its content will be displayed, or its effect applied,
 * in the timeline. Several objects may overlap within a given
 * #GESTimeline, in which case their #GESTimelineElement:priority is used
 * to determine their ordering in the timeline. Priority is mostly handled
 * internally by #GESLayer-s and #GESClip-s.
 *
 * A timeline element can have a #GESTimelineElement:parent,
 * such as a #GESClip, which is responsible for controlling its timing.
 *
 * ## Editing
 *
 * Elements can be moved around in their #GESTimelineElement:timeline by
 * setting their #GESTimelineElement:start and
 * #GESTimelineElement:duration using ges_timeline_element_set_start()
 * and ges_timeline_element_set_duration(). Additionally, which parts of
 * the underlying content are played in the timeline can be adjusted by
 * setting the #GESTimelineElement:in-point using
 * ges_timeline_element_set_inpoint(). The library also provides
 * ges_timeline_element_edit(), with various #GESEditMode-s, which can
 * adjust these properties in a convenient way, as well as introduce
 * similar changes in neighbouring or later elements in the timeline.
 *
 * However, a timeline may refuse a change in these properties if they
 * would place the timeline in an unsupported configuration. See
 * #GESTimeline for its overlap rules.
 *
 * Additionally, an edit may be refused if it would place one of the
 * timing properties out of bounds (such as a negative time value for
 * #GESTimelineElement:start, or having insufficient internal
 * content to last for the desired #GESTimelineElement:duration).
 *
 * ## Time Coordinates
 *
 * There are three main sets of time coordinates to consider when using
 * timeline elements:
 *
 * + Timeline coordinates: these are the time coordinates used in the
 *   output of the timeline in its #GESTrack-s. Each track share the same
 *   coordinates, so there is only one set of coordinates for the
 *   timeline. These extend indefinitely from 0. The times used for
 *   editing (including setting #GESTimelineElement:start and
 *   #GESTimelineElement:duration) use these coordinates, since these
 *   define when an element is present and for how long the element lasts
 *   for in the timeline.
 * + Internal source coordinates: these are the time coordinates used
 *   internally at the element's output. This is only really defined for
 *   #GESTrackElement-s, where it refers to time coordinates used at the
 *   final source pad of the wrapped #GstElement-s. However, these
 *   coordinates may also be used in a #GESClip in reference to its
 *   children. In particular, these are the coordinates used for
 *   #GESTimelineElement:in-point and #GESTimelineElement:max-duration.
 * + Internal sink coordinates: these are the time coordinates used
 *   internally at the element's input. A #GESSource has no input, so
 *   these would be undefined. Otherwise, for most #GESTrackElement-s
 *   these will be the same set of coordinates as the internal source
 *   coordinates because the element does not change the timing
 *   internally. Only #GESBaseEffect can support elements where these
 *   are different. See #GESBaseEffect for more information.
 *
 * You can determine the timeline time for a given internal source time
 * in a #GESTrack in a #GESClip using
 * ges_clip_get_timeline_time_from_internal_time(), and vice versa using
 * ges_clip_get_internal_time_from_timeline_time(), for the purposes of
 * editing and setting timings properties.
 *
 * ## Children Properties
 *
 * If a timeline element owns another #GstObject and wishes to expose
 * some of its properties, it can do so by registering the property as one
 * of the timeline element's children properties using
 * ges_timeline_element_add_child_property(). The registered property of
 * the child can then be read and set using the
 * ges_timeline_element_get_child_property() and
 * ges_timeline_element_set_child_property() methods, respectively. Some
 * sub-classed objects will be created with pre-registered children
 * properties; for example, to expose part of an underlying #GstElement
 * that is used internally. The registered properties can be listed with
 * ges_timeline_element_list_children_properties().
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "ges-utils.h"
#include "ges-timeline-element.h"
#include "ges-extractable.h"
#include "ges-meta-container.h"
#include "ges-internal.h"
#include "ges-effect.h"

#include <string.h>
#include <gobject/gvaluecollector.h>

#include "glib-compat-private.h"

/* maps type name quark => count */
static GData *object_name_counts = NULL;

static void
extractable_set_asset (GESExtractable * extractable, GESAsset * asset)
{
  GES_TIMELINE_ELEMENT (extractable)->asset = asset;
}

static void
ges_extractable_interface_init (GESExtractableInterface * iface)
{
  iface->set_asset = extractable_set_asset;
}

enum
{
  PROP_0,
  PROP_PARENT,
  PROP_TIMELINE,
  PROP_START,
  PROP_INPOINT,
  PROP_DURATION,
  PROP_MAX_DURATION,
  PROP_PRIORITY,
  PROP_NAME,
  PROP_SERIALIZE,
  PROP_LAST
};

enum
{
  DEEP_NOTIFY,
  CHILD_PROPERTY_ADDED,
  CHILD_PROPERTY_REMOVED,
  LAST_SIGNAL
};

static guint ges_timeline_element_signals[LAST_SIGNAL] = { 0 };

static GParamSpec *properties[PROP_LAST] = { NULL, };

typedef struct
{
  GObject *child;
  GParamSpec *pspec;
  GESTimelineElement *owner;
  gulong handler_id;
  GESTimelineElement *self;
  GESTimelineElementChildPropertyFlags flags;
} ChildPropSpec;

struct _GESTimelineElementPrivate
{
  gboolean serialize;

  GArray *children_props;

  GESTimelineElement *copied_from;

  GESTimelineElementFlags flags;
};

typedef struct
{
  GObject *child;
  GParamSpec *arg;
  GESTimelineElement *self;
} EmitDeepNotifyInIdleData;

G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESTimelineElement, ges_timeline_element,
    G_TYPE_INITIALLY_UNOWNED, G_ADD_PRIVATE (GESTimelineElement)
    G_IMPLEMENT_INTERFACE (GES_TYPE_EXTRACTABLE, ges_extractable_interface_init)
    G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL));

/*********************************************
 *      Virtual methods implementation       *
 *********************************************/
static void
_set_child_property (GESTimelineElement * self G_GNUC_UNUSED, GObject * child,
    GParamSpec * pspec, GValue * value)
{
  if (G_VALUE_TYPE (value) != pspec->value_type
      && G_VALUE_TYPE (value) == G_TYPE_STRING)
    gst_util_set_object_arg (child, pspec->name, g_value_get_string (value));
  else
    g_object_set_property (child, pspec->name, value);
}

static ChildPropSpec *
_find_child_prop (GESTimelineElement * self,
    GParamSpec * pspec, GObject * child, gint * index)
{
  gint i;

  for (i = 0; i < self->priv->children_props->len; i++) {
    ChildPropSpec *childprop =
        &g_array_index (self->priv->children_props, ChildPropSpec, i);

    if (child && childprop->child != child) {
      continue;
    }

    if (ges_pspec_hash (childprop->pspec) == ges_pspec_hash (pspec)) {
      if (index)
        *index = i;

      return childprop;
    }
  }

  return NULL;
}

static gboolean
_set_child_property_full (GESTimelineElement * self, GObject * child,
    GParamSpec * pspec, const GValue * value, GError ** error)
{
  GES_TIMELINE_ELEMENT_GET_CLASS (self)->set_child_property (self, child,
      pspec, (GValue *) value);
  return TRUE;
}

static gboolean
_lookup_child (GESTimelineElement * self, const gchar * prop_name,
    GObject ** child, GParamSpec ** pspec)
{
  gchar **names, *name, *classename;
  gboolean res;
  gint i;

  classename = NULL;
  res = FALSE;

  names = g_strsplit (prop_name, "::", 2);
  if (names[1] != NULL) {
    classename = names[0];
    name = names[1];
  } else
    name = names[0];

  for (i = 0; i < self->priv->children_props->len; i++) {
    ChildPropSpec *childprop =
        &g_array_index (self->priv->children_props, ChildPropSpec, i);
    if (g_strcmp0 (childprop->pspec->name, name) == 0) {
      if (classename == NULL
          || g_strcmp0 (G_OBJECT_TYPE_NAME (G_OBJECT (childprop->child)),
              classename) == 0
          || g_strcmp0 (g_type_name (childprop->pspec->owner_type),
              classename) == 0) {
        GST_DEBUG_OBJECT (self, "The %s property from %s has been found", name,
            classename);
        if (child)
          *child = gst_object_ref (childprop->child);

        if (pspec)
          *pspec = g_param_spec_ref (childprop->pspec);
        res = TRUE;
        break;
      }
    }
  }
  g_strfreev (names);

  return res;
}

GParamSpec **
ges_timeline_element_get_children_properties (GESTimelineElement * self,
    guint * n_properties)
{
  GParamSpec **pspec;

  guint i = 0;

  *n_properties = self->priv->children_props->len;
  pspec = g_new (GParamSpec *, *n_properties);

  for (i = 0; i < *n_properties; i++) {
    pspec[i] =
        g_param_spec_ref (g_array_index (self->priv->children_props,
            ChildPropSpec, i).pspec);
  }

  return pspec;
}

static void
_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  GESTimelineElement *self = GES_TIMELINE_ELEMENT (object);

  switch (property_id) {
    case PROP_PARENT:
      g_value_set_object (value, self->parent);
      break;
    case PROP_TIMELINE:
      g_value_set_object (value, self->timeline);
      break;
    case PROP_START:
      g_value_set_uint64 (value, self->start);
      break;
    case PROP_INPOINT:
      g_value_set_uint64 (value, self->inpoint);
      break;
    case PROP_DURATION:
      g_value_set_uint64 (value, self->duration);
      break;
    case PROP_MAX_DURATION:
      g_value_set_uint64 (value, self->maxduration);
      break;
    case PROP_PRIORITY:
      g_value_set_uint (value, self->priority);
      break;
    case PROP_NAME:
      g_value_take_string (value, ges_timeline_element_get_name (self));
      break;
    case PROP_SERIALIZE:
      g_value_set_boolean (value, self->priv->serialize);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
  }
}

static void
_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  GESTimelineElement *self = GES_TIMELINE_ELEMENT (object);

  switch (property_id) {
    case PROP_PARENT:
      ges_timeline_element_set_parent (self, g_value_get_object (value));
      break;
    case PROP_TIMELINE:
      ges_timeline_element_set_timeline (self, g_value_get_object (value));
      break;
    case PROP_START:
      ges_timeline_element_set_start (self, g_value_get_uint64 (value));
      break;
    case PROP_INPOINT:
      ges_timeline_element_set_inpoint (self, g_value_get_uint64 (value));
      break;
    case PROP_DURATION:
      ges_timeline_element_set_duration (self, g_value_get_uint64 (value));
      break;
    case PROP_PRIORITY:
      ges_timeline_element_set_priority (self, g_value_get_uint (value));
      break;
    case PROP_MAX_DURATION:
      ges_timeline_element_set_max_duration (self, g_value_get_uint64 (value));
      break;
    case PROP_NAME:
      ges_timeline_element_set_name (self, g_value_get_string (value));
      break;
    case PROP_SERIALIZE:
      self->priv->serialize = g_value_get_boolean (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
  }
}

static void
ges_timeline_element_dispose (GObject * object)
{
  GESTimelineElement *self = GES_TIMELINE_ELEMENT (object);

  g_clear_pointer (&self->priv->children_props, g_array_unref);

  g_clear_object (&self->priv->copied_from);

  G_OBJECT_CLASS (ges_timeline_element_parent_class)->dispose (object);
}

static void
ges_timeline_element_finalize (GObject * self)
{
  GESTimelineElement *tle = GES_TIMELINE_ELEMENT (self);

  g_free (tle->name);

  G_OBJECT_CLASS (ges_timeline_element_parent_class)->finalize (self);
}

static void
_child_prop_spec_free (ChildPropSpec * childprop)
{
  g_object_freeze_notify (childprop->child);
  if (childprop->handler_id)
    g_signal_handler_disconnect (childprop->child, childprop->handler_id);
  g_object_thaw_notify (childprop->child);

  if (childprop->child != (GObject *) childprop->self &&
      childprop->child != (GObject *) childprop->owner)
    gst_object_unref (childprop->child);
}

static gboolean
_get_natural_framerate (GESTimelineElement * self, gint * framerate_n,
    gint * framerate_d)
{
  GST_INFO_OBJECT (self, "No natural framerate");

  return FALSE;
}

static void
ges_timeline_element_init (GESTimelineElement * self)
{
  self->priv = ges_timeline_element_get_instance_private (self);

  self->priv->serialize = TRUE;

  self->priv->children_props = g_array_new (TRUE, TRUE, sizeof (ChildPropSpec));
  g_array_set_clear_func (self->priv->children_props,
      (GDestroyNotify) _child_prop_spec_free);
}

static void
ges_timeline_element_class_init (GESTimelineElementClass * klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->get_property = _get_property;
  object_class->set_property = _set_property;

  /**
   * GESTimelineElement:parent:
   *
   * The parent container of the element.
   */
  properties[PROP_PARENT] =
      g_param_spec_object ("parent", "Parent",
      "The parent container of the object", GES_TYPE_TIMELINE_ELEMENT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
   * GESTimelineElement:timeline:
   *
   * The timeline that the element lies within.
   */
  properties[PROP_TIMELINE] =
      g_param_spec_object ("timeline", "Timeline",
      "The timeline the object is in", GES_TYPE_TIMELINE,
      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GESTimelineElement:start:
   *
   * The starting position of the element in the timeline (in nanoseconds
   * and in the time coordinates of the timeline). For example, for a
   * source element, this would determine the time at which it should
   * start outputting its internal content. For an operation element, this
   * would determine the time at which it should start applying its effect
   * to any source content.
   */
  properties[PROP_START] = g_param_spec_uint64 ("start", "Start",
      "The position in the timeline", 0, G_MAXUINT64, 0,
      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GESTimelineElement:in-point: (getter get_inpoint)(setter set_inpoint)
   *
   * The initial offset to use internally when outputting content (in
   * nanoseconds, but in the time coordinates of the internal content).
   *
   * For example, for a #GESVideoUriSource that references some media
   * file, the "internal content" is the media file data, and the
   * in-point would correspond to some timestamp in the media file.
   * When playing the timeline, and when the element is first reached at
   * timeline-time #GESTimelineElement:start, it will begin outputting the
   * data from the timestamp in-point **onwards**, until it reaches the
   * end of its #GESTimelineElement:duration in the timeline.
   *
   * For elements that have no internal content, this should be kept
   * as 0.
   */
  properties[PROP_INPOINT] =
      g_param_spec_uint64 ("in-point", "In-point", "The in-point", 0,
      G_MAXUINT64, 0,
      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GESTimelineElement:duration:
   *
   * The duration that the element is in effect for in the timeline (a
   * time difference in nanoseconds using the time coordinates of the
   * timeline). For example, for a source element, this would determine
   * for how long it should output its internal content for. For an
   * operation element, this would determine for how long its effect
   * should be applied to any source content.
   */
  properties[PROP_DURATION] =
      g_param_spec_uint64 ("duration", "Duration", "The play duration", 0,
      G_MAXUINT64, GST_CLOCK_TIME_NONE,
      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GESTimelineElement:max-duration:
   *
   * The full duration of internal content that is available (a time
   * difference in nanoseconds using the time coordinates of the internal
   * content).
   *
   * This will act as a cap on the #GESTimelineElement:in-point of the
   * element (which is in the same time coordinates), and will sometimes
   * be used to limit the #GESTimelineElement:duration of the element in
   * the timeline.
   *
   * For example, for a #GESVideoUriSource that references some media
   * file, this would be the length of the media file.
   *
   * For elements that have no internal content, or whose content is
   * indefinite, this should be kept as #GST_CLOCK_TIME_NONE.
   */
  properties[PROP_MAX_DURATION] =
      g_param_spec_uint64 ("max-duration", "Maximum duration",
      "The maximum duration of the object", 0, G_MAXUINT64, GST_CLOCK_TIME_NONE,
      G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY |
      G_PARAM_STATIC_STRINGS);

  /**
   * GESTimelineElement:priority:
   *
   * The priority of the element.
   *
   * Deprecated: 1.10: Priority management is now done by GES itself.
   */
  properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority",
      "The priority of the object", 0, G_MAXUINT, 0,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
   * GESTimelineElement:name:
   *
   * The name of the element. This should be unique within its timeline.
   */
  properties[PROP_NAME] =
      g_param_spec_string ("name", "Name", "The name of the timeline object",
      NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);

  /**
   * GESTimelineElement:serialize:
   *
   * Whether the element should be serialized.
   */
  properties[PROP_SERIALIZE] = g_param_spec_boolean ("serialize", "Serialize",
      "Whether the element should be serialized", TRUE,
      G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (object_class, PROP_LAST, properties);

  /**
   * GESTimelineElement::deep-notify:
   * @timeline_element: A #GESTtimelineElement
   * @prop_object: The child whose property has been set
   * @prop: The specification for the property that been set
   *
   * Emitted when a child of the element has one of its registered
   * properties set. See ges_timeline_element_add_child_property().
   * Note that unlike #GObject::notify, a child property name can not be
   * used as a signal detail.
   */
  ges_timeline_element_signals[DEEP_NOTIFY] =
      g_signal_new ("deep-notify", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED |
      G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL,
      G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_PARAM);

  /**
   * GESTimelineElement::child-property-added:
   * @timeline_element: A #GESTtimelineElement
   * @prop_object: The child whose property has been registered
   * @prop: The specification for the property that has been registered
   *
   * Emitted when the element has a new child property registered. See
   * ges_timeline_element_add_child_property().
   *
   * Note that some GES elements will be automatically created with
   * pre-registered children properties. You can use
   * ges_timeline_element_list_children_properties() to list these.
   *
   * Since: 1.18
   */
  ges_timeline_element_signals[CHILD_PROPERTY_ADDED] =
      g_signal_new ("child-property-added", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
      G_TYPE_OBJECT, G_TYPE_PARAM);

  /**
   * GESTimelineElement::child-property-removed:
   * @timeline_element: A #GESTimelineElement
   * @prop_object: The child whose property has been unregistered
   * @prop: The specification for the property that has been unregistered
   *
   * Emitted when the element has a child property unregistered. See
   * ges_timeline_element_remove_child_property().
   *
   * Since: 1.18
   */
  ges_timeline_element_signals[CHILD_PROPERTY_REMOVED] =
      g_signal_new ("child-property-removed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
      G_TYPE_OBJECT, G_TYPE_PARAM);


  object_class->dispose = ges_timeline_element_dispose;
  object_class->finalize = ges_timeline_element_finalize;

  klass->set_parent = NULL;
  klass->set_start = NULL;
  klass->set_inpoint = NULL;
  klass->set_duration = NULL;
  klass->set_max_duration = NULL;
  klass->set_priority = NULL;

  klass->ripple = NULL;
  klass->ripple_end = NULL;
  klass->roll_start = NULL;
  klass->roll_end = NULL;
  klass->trim = NULL;

  klass->list_children_properties =
      ges_timeline_element_get_children_properties;
  klass->lookup_child = _lookup_child;
  klass->set_child_property = _set_child_property;
  klass->set_child_property_full = _set_child_property_full;
  klass->get_natural_framerate = _get_natural_framerate;
}

static void
_set_name (GESTimelineElement * self, const gchar * wanted_name)
{
  const gchar *type_name;
  gchar *lowcase_type;
  gint count;
  GQuark q;
  guint i, l;
  gchar *name = NULL;

  if (!object_name_counts) {
    g_datalist_init (&object_name_counts);
  }

  q = g_type_qname (G_OBJECT_TYPE (self));
  count = GPOINTER_TO_INT (g_datalist_id_get_data (&object_name_counts, q));

  /* GstFooSink -> foosink<N> */
  type_name = g_quark_to_string (q);
  if (strncmp (type_name, "GES", 3) == 0)
    type_name += 3;

  lowcase_type = g_strdup (type_name);
  l = strlen (lowcase_type);
  for (i = 0; i < l; i++)
    lowcase_type[i] = g_ascii_tolower (lowcase_type[i]);

  if (wanted_name == NULL) {
    /* give the 20th "uriclip" element and the first "uriclip2" (if needed in the future)
     * different names */
    l = strlen (type_name);
    if (l > 0 && g_ascii_isdigit (type_name[l - 1])) {
      name = g_strdup_printf ("%s-%d", lowcase_type, count++);
    } else {
      name = g_strdup_printf ("%s%d", lowcase_type, count++);
    }
  } else {
    /* If the wanted name uses the same 'namespace' as default, make
     * sure it does not badly interfere with our counting system */

    /* FIXME: should we really be allowing a user to set the name
     * "uriclip1" for, say, a GESTransition? The below code *does not*
     * capture this case (because the prefix does not match "transition").
     * If the user subsequently calls _set_name with name == NULL, on a
     * GESClip *for the first time*, then the GES library will
     * automatically choose the *same* name "uriclip1", but this is not
     * unique! */
    if (g_str_has_prefix (wanted_name, lowcase_type)) {
      guint64 tmpcount =
          g_ascii_strtoull (&wanted_name[strlen (lowcase_type)], NULL, 10);

      if (tmpcount > count) {
        count = tmpcount + 1;
        GST_DEBUG_OBJECT (self, "Using same naming %s but updated count to %i",
            wanted_name, count);
      } else if (tmpcount < count) {
        /* FIXME: this can unexpectedly change names given by the user
         * E.g. if "transition2" already exists, and a user then wants to
         * set a GESTransition to have the name "transition-custom" or
         * "transition 1 too many" then tmpcount would in fact be 0 or 1,
         * and the name would then be changed to "transition3"! */
        name = g_strdup_printf ("%s%d", lowcase_type, count);
        count++;
        GST_DEBUG_OBJECT (self, "Name %s already allocated, giving: %s instead"
            " New count is %i", wanted_name, name, count);
      } else {
        count++;
        GST_DEBUG_OBJECT (self, "Perfect name, just bumping object count");
      }
    }

    if (name == NULL)
      name = g_strdup (wanted_name);
  }

  g_free (lowcase_type);
  g_datalist_id_set_data (&object_name_counts, q, GINT_TO_POINTER (count));

  g_free (self->name);
  self->name = name;
}

/*********************************************
 *       Internal and private helpers        *
 *********************************************/

GESTimelineElement *
ges_timeline_element_peak_toplevel (GESTimelineElement * self)
{
  GESTimelineElement *toplevel = self;

  while (toplevel->parent)
    toplevel = toplevel->parent;

  return toplevel;
}

GESTimelineElement *
ges_timeline_element_get_copied_from (GESTimelineElement * self)
{
  GESTimelineElement *copied_from = self->priv->copied_from;
  self->priv->copied_from = NULL;
  return copied_from;
}

GESTimelineElementFlags
ges_timeline_element_flags (GESTimelineElement * self)
{
  return self->priv->flags;
}

void
ges_timeline_element_set_flags (GESTimelineElement * self,
    GESTimelineElementFlags flags)
{
  self->priv->flags = flags;

}

static gboolean
emit_deep_notify_in_idle (EmitDeepNotifyInIdleData * data)
{
  g_signal_emit (data->self, ges_timeline_element_signals[DEEP_NOTIFY], 0,
      data->child, data->arg);

  gst_object_unref (data->child);
  g_param_spec_unref (data->arg);
  gst_object_unref (data->self);
  g_free (data);

  return FALSE;
}

static void
child_prop_changed_cb (GObject * child, GParamSpec * arg,
    GESTimelineElement * self)
{
  EmitDeepNotifyInIdleData *data;

  /* Emit "deep-notify" right away if in main thread */
  if (g_main_context_acquire (g_main_context_default ())) {
    g_main_context_release (g_main_context_default ());
    g_signal_emit (self, ges_timeline_element_signals[DEEP_NOTIFY], 0,
        child, arg);
    return;
  }

  data = g_new (EmitDeepNotifyInIdleData, 1);

  data->child = gst_object_ref (child);
  data->arg = g_param_spec_ref (arg);
  data->self = gst_object_ref (self);

  ges_callback_add ((GSourceFunc) emit_deep_notify_in_idle, data, NULL);
}

static gboolean
set_child_property (GESTimelineElement * self, ChildPropSpec * childprop,
    const GValue * value, GError ** error)
{
  GESTimelineElement *setter = self;
  GESTimelineElementClass *klass;

  if (childprop->owner) {
    klass = GES_TIMELINE_ELEMENT_GET_CLASS (childprop->owner);
    setter = childprop->owner;
  } else {
    klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
  }

  if (klass->set_child_property_full)
    return klass->set_child_property_full (setter, childprop->child,
        childprop->pspec, value, error);

  g_assert (klass->set_child_property);
  klass->set_child_property (setter, childprop->child, childprop->pspec,
      (GValue *) value);

  return TRUE;
}

static gboolean
set_child_property_by_pspec (GESTimelineElement * self,
    GParamSpec * pspec, const GValue * value, GError ** error)
{
  gint i;
  ChildPropSpec *childprop;

  gboolean found = FALSE, res = TRUE;

  for (i = 0; i < self->priv->children_props->len; i++) {
    childprop = &g_array_index (self->priv->children_props, ChildPropSpec, i);

    if (ges_pspec_hash (childprop->pspec) != ges_pspec_hash (pspec))
      continue;

    found = TRUE;
    res &= set_child_property (self, childprop, value, error);
    if (!(childprop->flags &
            GES_TIMELINE_ELEMENT_CHILD_PROP_FLAG_SET_ON_ALL_INSTANCES))
      break;
  }

  if (!found) {
    GST_ERROR_OBJECT (self, "The %s property doesn't exist", pspec->name);
    return FALSE;
  }
  GST_DEBUG_OBJECT (self, "Set child property %s", pspec->name);

  return res;
}

gboolean
ges_timeline_element_add_child_property_full (GESTimelineElement * self,
    GESTimelineElement * owner, GParamSpec * pspec, GObject * child,
    GESTimelineElementChildPropertyFlags flags)
{
  gchar *signame;
  ChildPropSpec childprop, *prev;

  if (_find_child_prop (self, pspec, child, NULL)) {
    GST_INFO_OBJECT (self, "Child property already exists: %s", pspec->name);

    return FALSE;
  }

  prev = _find_child_prop (self, pspec, NULL, NULL);
  if (flags & GES_TIMELINE_ELEMENT_CHILD_PROP_FLAG_INHERIT) {
    if (!owner) {
      g_warning ("Trying to inherit child property flags without an owner");
    } else {
      ChildPropSpec *owner_prop = _find_child_prop (owner, pspec, NULL, NULL);

      g_return_val_if_fail (owner_prop, FALSE);
      flags |= owner_prop->flags;
    }
  }

  if (prev && prev->flags != flags) {
    GST_ERROR_OBJECT (self,
        "Trying to add child property with flags %d but the same"
        " property had flags %d - this is not supported", flags, prev->flags);

    return FALSE;
  }

  GST_DEBUG_OBJECT (self, "Adding child property: %" GST_PTR_FORMAT "::%s",
      child, pspec->name);

  signame = g_strconcat ("notify::", pspec->name, NULL);
  childprop.self = self;
  if (child == G_OBJECT (self) || child == G_OBJECT (owner))
    childprop.child = child;
  else
    childprop.child = gst_object_ref (child);
  childprop.pspec = g_param_spec_ref (pspec);
  childprop.owner = owner;
  childprop.flags = flags;
  childprop.handler_id =
      g_signal_connect (child, signame, G_CALLBACK (child_prop_changed_cb),
      self);
  g_array_append_val (self->priv->children_props, childprop);

  g_signal_emit (self, ges_timeline_element_signals[CHILD_PROPERTY_ADDED], 0,
      child, pspec);

  g_free (signame);
  return TRUE;
}

GList *
ges_timeline_element_get_children_from_child_property (GESTimelineElement *
    self, GParamSpec * pspec)
{
  gint i;
  GList *res = NULL;

  for (i = 0; i < self->priv->children_props->len; i++) {
    ChildPropSpec *childprop =
        &g_array_index (self->priv->children_props, ChildPropSpec, i);

    if (ges_pspec_hash (childprop->pspec) == ges_pspec_hash (pspec))
      res = g_list_append (res, childprop->child);
  }

  return res;
}


/*********************************************
 *            API implementation             *
 *********************************************/

/**
 * ges_timeline_element_set_parent:
 * @self: A #GESTimelineElement
 * @parent (nullable): New parent of @self
 *
 * Sets the #GESTimelineElement:parent for the element.
 *
 * This is used internally and you should normally not call this. A
 * #GESContainer will set the #GESTimelineElement:parent of its children
 * in ges_container_add() and ges_container_remove().
 *
 * Note, if @parent is not %NULL, @self must not already have a parent
 * set. Therefore, if you wish to switch parents, you will need to call
 * this function twice: first to set the parent to %NULL, and then to the
 * new parent.
 *
 * If @parent is not %NULL, you must ensure it already has a
 * (non-floating) reference to @self before calling this.
 *
 * Returns: %TRUE if @parent could be set for @self.
 */
gboolean
ges_timeline_element_set_parent (GESTimelineElement * self,
    GESTimelineElement * parent)
{
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (parent == NULL
      || GES_IS_TIMELINE_ELEMENT (parent), FALSE);

  if (self == parent) {
    GST_INFO_OBJECT (self, "Trying to add %p in itself, not a good idea!",
        self);
    /* FIXME: why are we sinking and then unreffing self when we do not
     * own it? */
    gst_object_ref_sink (self);
    gst_object_unref (self);
    return FALSE;
  }

  GST_DEBUG_OBJECT (self, "set parent to %" GST_PTR_FORMAT, parent);

  if (self->parent != NULL && parent != NULL)
    goto had_parent;

  if (GES_TIMELINE_ELEMENT_GET_CLASS (self)->set_parent) {
    if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->set_parent (self, parent))
      return FALSE;
  }

  self->parent = parent;

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PARENT]);
  return TRUE;

  /* ERROR handling */
had_parent:
  {
    GST_WARNING_OBJECT (self, "set parent failed, object already had a parent");
    /* FIXME: why are we sinking and then unreffing self when we do not
     * own it? */
    gst_object_ref_sink (self);
    gst_object_unref (self);
    return FALSE;
  }
}

/**
 * ges_timeline_element_get_parent:
 * @self: A #GESTimelineElement
 *
 * Gets the #GESTimelineElement:parent for the element.
 *
 * Returns: (transfer full) (nullable): The parent of @self, or %NULL if
 * @self has no parent.
 */
GESTimelineElement *
ges_timeline_element_get_parent (GESTimelineElement * self)
{
  GESTimelineElement *result = NULL;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL);

  result = self->parent;
  if (G_LIKELY (result))
    gst_object_ref (result);

  return result;
}

/**
 * ges_timeline_element_set_timeline:
 * @self: A #GESTimelineElement
 * @timeline (nullable): The #GESTimeline @self should be in
 *
 * Sets the #GESTimelineElement:timeline of the element.
 *
 * This is used internally and you should normally not call this. A
 * #GESClip will have its #GESTimelineElement:timeline set through its
 * #GESLayer. A #GESTrack will similarly take care of setting the
 * #GESTimelineElement:timeline of its #GESTrackElement-s. A #GESGroup
 * will adopt the same #GESTimelineElement:timeline as its children.
 *
 * If @timeline is %NULL, this will stop its current
 * #GESTimelineElement:timeline from tracking it, otherwise @timeline will
 * start tracking @self. Note, in the latter case, @self must not already
 * have a timeline set. Therefore, if you wish to switch timelines, you
 * will need to call this function twice: first to set the timeline to
 * %NULL, and then to the new timeline.
 *
 * Returns: %TRUE if @timeline could be set for @self.
 */
gboolean
ges_timeline_element_set_timeline (GESTimelineElement * self,
    GESTimeline * timeline)
{
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (timeline == NULL || GES_IS_TIMELINE (timeline), FALSE);

  GST_DEBUG_OBJECT (self, "set timeline to %" GST_PTR_FORMAT, timeline);

  if (self->timeline == timeline)
    return TRUE;

  if (timeline != NULL && G_UNLIKELY (self->timeline != NULL))
    goto had_timeline;

  if (timeline == NULL) {
    if (self->timeline) {
      if (!timeline_remove_element (self->timeline, self)) {
        GST_INFO_OBJECT (self, "Could not remove from"
            " currently set timeline %" GST_PTR_FORMAT, self->timeline);
        return FALSE;
      }
    }
  } else {
    if (!timeline_add_element (timeline, self)) {
      GST_INFO_OBJECT (self, "Could not add to timeline %" GST_PTR_FORMAT,
          self);
      return FALSE;
    }
  }

  self->timeline = timeline;

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMELINE]);
  return TRUE;

  /* ERROR handling */
had_timeline:
  {
    GST_DEBUG_OBJECT (self, "set timeline failed, object already had a "
        "timeline");
    return FALSE;
  }
}

/**
 * ges_timeline_element_get_timeline:
 * @self: A #GESTimelineElement
 *
 * Gets the #GESTimelineElement:timeline for the element.
 *
 * Returns: (transfer full) (nullable): The timeline of @self, or %NULL
 * if @self has no timeline.
 */
GESTimeline *
ges_timeline_element_get_timeline (GESTimelineElement * self)
{
  GESTimeline *result = NULL;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL);

  result = self->timeline;
  if (G_LIKELY (result))
    gst_object_ref (result);

  return result;
}

/**
 * ges_timeline_element_set_start:
 * @self: A #GESTimelineElement
 * @start: The desired start position of the element in its timeline
 *
 * Sets #GESTimelineElement:start for the element. If the element has a
 * parent, this will also move its siblings with the same shift.
 *
 * Whilst the element is part of a #GESTimeline, this is the same as
 * editing the element with ges_timeline_element_edit() under
 * #GES_EDIT_MODE_NORMAL with #GES_EDGE_NONE. In particular, the
 * #GESTimelineElement:start of the element may be snapped to a different
 * timeline time from the one given. In addition, setting may fail if it
 * would place the timeline in an unsupported configuration.
 *
 * Returns: %TRUE if @start could be set for @self.
 */
gboolean
ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
{
  GESTimelineElementClass *klass;
  GESTimelineElement *toplevel_container, *parent;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE);

  if (self->start == start)
    return TRUE;

  GST_DEBUG_OBJECT (self, "current start: %" GST_TIME_FORMAT
      " new start: %" GST_TIME_FORMAT,
      GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self)), GST_TIME_ARGS (start));

  if (self->timeline && !GES_TIMELINE_ELEMENT_BEING_EDITED (self))
    return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_NORMAL,
        GES_EDGE_NONE, start);

  toplevel_container = ges_timeline_element_peak_toplevel (self);
  parent = self->parent;

  /* FIXME This should not belong to GESTimelineElement */
  /* only check if no timeline, otherwise the timeline-tree will handle this
   * check */
  if (!self->timeline && toplevel_container &&
      ((gint64) (_START (toplevel_container) + start - _START (self))) < 0 &&
      parent
      && GES_CONTAINER (parent)->children_control_mode == GES_CHILDREN_UPDATE) {
    GST_INFO_OBJECT (self,
        "Can not move the object as it would imply its "
        "container to have a negative start value");

    return FALSE;
  }

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
  if (klass->set_start) {
    gint res = klass->set_start (self, start);
    if (res == FALSE)
      return FALSE;
    if (res == TRUE) {
      self->start = start;
      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_START]);
    }

    GST_DEBUG_OBJECT (self, "New start: %" GST_TIME_FORMAT,
        GST_TIME_ARGS (GES_TIMELINE_ELEMENT_START (self)));

    return TRUE;
  }

  GST_WARNING_OBJECT (self, "No set_start virtual method implementation"
      " on class %s. Can not set start %" GST_TIME_FORMAT,
      G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (start));
  return FALSE;
}

/**
 * ges_timeline_element_set_inpoint: (set-property in-point):
 * @self: A #GESTimelineElement
 * @inpoint: The in-point, in internal time coordinates
 *
 * Sets #GESTimelineElement:in-point for the element. If the new in-point
 * is above the current #GESTimelineElement:max-duration of the element,
 * this method will fail.
 *
 * Returns: %TRUE if @inpoint could be set for @self.
 */
gboolean
ges_timeline_element_set_inpoint (GESTimelineElement * self,
    GstClockTime inpoint)
{
  GESTimelineElementClass *klass;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);

  GST_DEBUG_OBJECT (self, "current inpoint: %" GST_TIME_FORMAT
      " new inpoint: %" GST_TIME_FORMAT, GST_TIME_ARGS (self->inpoint),
      GST_TIME_ARGS (inpoint));

  if (G_UNLIKELY (inpoint == self->inpoint))
    return TRUE;

  if (GES_CLOCK_TIME_IS_LESS (self->maxduration, inpoint)) {
    GST_WARNING_OBJECT (self, "Can not set an in-point of %" GST_TIME_FORMAT
        " because it exceeds the element's max-duration: %" GST_TIME_FORMAT,
        GST_TIME_ARGS (inpoint), GST_TIME_ARGS (self->maxduration));
    return FALSE;
  }

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);

  if (klass->set_inpoint) {
    /* FIXME: Could we instead use g_object_freeze_notify() to prevent
     * duplicate notify signals? Rather than relying on the return value
     * being -1 for setting that succeeds but does not want a notify
     * signal because it will call this method on itself a second time. */
    if (!klass->set_inpoint (self, inpoint))
      return FALSE;

    self->inpoint = inpoint;
    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INPOINT]);

    return TRUE;
  }

  GST_DEBUG_OBJECT (self, "No set_inpoint virtual method implementation"
      " on class %s. Can not set inpoint %" GST_TIME_FORMAT,
      G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (inpoint));

  return FALSE;
}

/**
 * ges_timeline_element_set_max_duration:
 * @self: A #GESTimelineElement
 * @maxduration: The maximum duration, in internal time coordinates
 *
 * Sets #GESTimelineElement:max-duration for the element. If the new
 * maximum duration is below the current #GESTimelineElement:in-point of
 * the element, this method will fail.
 *
 * Returns: %TRUE if @maxduration could be set for @self.
 */
gboolean
ges_timeline_element_set_max_duration (GESTimelineElement * self,
    GstClockTime maxduration)
{
  GESTimelineElementClass *klass;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);

  GST_DEBUG_OBJECT (self, "current max-duration: %" GST_TIME_FORMAT
      " new max-duration: %" GST_TIME_FORMAT,
      GST_TIME_ARGS (self->maxduration), GST_TIME_ARGS (maxduration));

  if (G_UNLIKELY (maxduration == self->maxduration))
    return TRUE;

  if (GES_CLOCK_TIME_IS_LESS (maxduration, self->inpoint)) {
    GST_WARNING_OBJECT (self, "Can not set a max-duration of %"
        GST_TIME_FORMAT " because it lies below the element's in-point: %"
        GST_TIME_FORMAT, GST_TIME_ARGS (maxduration),
        GST_TIME_ARGS (self->inpoint));
    return FALSE;
  }

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);

  if (klass->set_max_duration) {
    if (!klass->set_max_duration (self, maxduration))
      return FALSE;
    self->maxduration = maxduration;
    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_DURATION]);

    return TRUE;
  }

  GST_DEBUG_OBJECT (self, "No set_max_duration virtual method implementation"
      " on class %s. Can not set max-duration  %" GST_TIME_FORMAT,
      G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (maxduration));

  return FALSE;
}

/**
 * ges_timeline_element_set_duration:
 * @self: A #GESTimelineElement
 * @duration: The desired duration in its timeline
 *
 * Sets #GESTimelineElement:duration for the element.
 *
 * Whilst the element is part of a #GESTimeline, this is the same as
 * editing the element with ges_timeline_element_edit() under
 * #GES_EDIT_MODE_TRIM with #GES_EDGE_END. In particular, the
 * #GESTimelineElement:duration of the element may be snapped to a
 * different timeline time difference from the one given. In addition,
 * setting may fail if it would place the timeline in an unsupported
 * configuration, or the element does not have enough internal content to
 * last the desired duration.
 *
 * Returns: %TRUE if @duration could be set for @self.
 */
gboolean
ges_timeline_element_set_duration (GESTimelineElement * self,
    GstClockTime duration)
{
  GESTimelineElementClass *klass;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);

  if (duration == self->duration)
    return TRUE;

  if (self->timeline && !GES_TIMELINE_ELEMENT_BEING_EDITED (self))
    return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_TRIM,
        GES_EDGE_END, self->start + duration);

  GST_DEBUG_OBJECT (self, "current duration: %" GST_TIME_FORMAT
      " new duration: %" GST_TIME_FORMAT,
      GST_TIME_ARGS (GES_TIMELINE_ELEMENT_DURATION (self)),
      GST_TIME_ARGS (duration));

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
  if (klass->set_duration) {
    gint res = klass->set_duration (self, duration);
    if (res == FALSE)
      return FALSE;
    if (res == TRUE) {
      self->duration = duration;
      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
    }

    return TRUE;
  }

  GST_WARNING_OBJECT (self, "No set_duration virtual method implementation"
      " on class %s. Can not set duration %" GST_TIME_FORMAT,
      G_OBJECT_CLASS_NAME (klass), GST_TIME_ARGS (duration));
  return FALSE;
}

/**
 * ges_timeline_element_get_start:
 * @self: A #GESTimelineElement
 *
 * Gets the #GESTimelineElement:start for the element.
 *
 * Returns: The start of @self (in nanoseconds).
 */
GstClockTime
ges_timeline_element_get_start (GESTimelineElement * self)
{
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE);

  return self->start;
}

/**
 * ges_timeline_element_get_inpoint: (get-property in-point):
 * @self: A #GESTimelineElement
 *
 * Gets the #GESTimelineElement:in-point for the element.
 *
 * Returns: The in-point of @self (in nanoseconds).
 */
GstClockTime
ges_timeline_element_get_inpoint (GESTimelineElement * self)
{
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE);

  return self->inpoint;
}

/**
 * ges_timeline_element_get_duration:
 * @self: A #GESTimelineElement
 *
 * Gets the #GESTimelineElement:duration for the element.
 *
 * Returns: The duration of @self (in nanoseconds).
 */
GstClockTime
ges_timeline_element_get_duration (GESTimelineElement * self)
{
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE);

  return self->duration;
}

/**
 * ges_timeline_element_get_max_duration:
 * @self: A #GESTimelineElement
 *
 * Gets the #GESTimelineElement:max-duration for the element.
 *
 * Returns: The max-duration of @self (in nanoseconds).
 */
GstClockTime
ges_timeline_element_get_max_duration (GESTimelineElement * self)
{
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), GST_CLOCK_TIME_NONE);

  return self->maxduration;
}

/**
 * ges_timeline_element_get_priority:
 * @self: A #GESTimelineElement
 *
 * Gets the #GESTimelineElement:priority for the element.
 *
 * Returns: The priority of @self.
 */
guint32
ges_timeline_element_get_priority (GESTimelineElement * self)
{
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), 0);

  return self->priority;
}

/**
 * ges_timeline_element_set_priority:
 * @self: A #GESTimelineElement
 * @priority: The priority
 *
 * Sets the priority of the element within the containing layer.
 *
 * Deprecated:1.10: All priority management is done by GES itself now.
 * To set #GESEffect priorities #ges_clip_set_top_effect_index should
 * be used.
 *
 * Returns: %TRUE if @priority could be set for @self.
 */
gboolean
ges_timeline_element_set_priority (GESTimelineElement * self, guint32 priority)
{
  GESTimelineElementClass *klass;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);

  GST_DEBUG_OBJECT (self, "current priority: %d new priority: %d",
      self->priority, priority);

  if (klass->set_priority) {
    gboolean res = klass->set_priority (self, priority);
    if (res) {
      self->priority = priority;
      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PRIORITY]);
    }

    return res;
  }

  GST_WARNING_OBJECT (self, "No set_priority virtual method implementation"
      " on class %s. Can not set priority %d", G_OBJECT_CLASS_NAME (klass),
      priority);
  return FALSE;
}

/**
 * ges_timeline_element_ripple:
 * @self: The #GESTimelineElement to ripple
 * @start: The new start time of @self in ripple mode
 *
 * Edits the start time of an element within its timeline in ripple mode.
 * See ges_timeline_element_edit() with #GES_EDIT_MODE_RIPPLE and
 * #GES_EDGE_NONE.
 *
 * Returns: %TRUE if the ripple edit of @self completed, %FALSE on
 * failure.
 */
gboolean
ges_timeline_element_ripple (GESTimelineElement * self, GstClockTime start)
{
  GESTimelineElementClass *klass;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE);

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);

  if (klass->ripple)
    return klass->ripple (self, start);

  return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_RIPPLE,
      GES_EDGE_NONE, start);

  return FALSE;
}

/**
 * ges_timeline_element_ripple_end:
 * @self: The #GESTimelineElement to ripple
 * @end: The new end time of @self in ripple mode
 *
 * Edits the end time of an element within its timeline in ripple mode.
 * See ges_timeline_element_edit() with #GES_EDIT_MODE_RIPPLE and
 * #GES_EDGE_END.
 *
 * Returns: %TRUE if the ripple edit of @self completed, %FALSE on
 * failure.
 */
gboolean
ges_timeline_element_ripple_end (GESTimelineElement * self, GstClockTime end)
{
  GESTimelineElementClass *klass;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (end), FALSE);

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);

  if (klass->ripple_end)
    return klass->ripple_end (self, end);

  return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_RIPPLE,
      GES_EDGE_END, end);
}

/**
 * ges_timeline_element_roll_start:
 * @self: The #GESTimelineElement to roll
 * @start: The new start time of @self in roll mode
 *
 * Edits the start time of an element within its timeline in roll mode.
 * See ges_timeline_element_edit() with #GES_EDIT_MODE_ROLL and
 * #GES_EDGE_START.
 *
 * Returns: %TRUE if the roll edit of @self completed, %FALSE on failure.
 */
gboolean
ges_timeline_element_roll_start (GESTimelineElement * self, GstClockTime start)
{
  GESTimelineElementClass *klass;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE);

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);

  if (klass->roll_start)
    return klass->roll_start (self, start);

  return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_ROLL,
      GES_EDGE_START, start);
}

/**
 * ges_timeline_element_roll_end:
 * @self: The #GESTimelineElement to roll
 * @end: The new end time of @self in roll mode
 *
 * Edits the end time of an element within its timeline in roll mode.
 * See ges_timeline_element_edit() with #GES_EDIT_MODE_ROLL and
 * #GES_EDGE_END.
 *
 * Returns: %TRUE if the roll edit of @self completed, %FALSE on failure.
 */
gboolean
ges_timeline_element_roll_end (GESTimelineElement * self, GstClockTime end)
{
  GESTimelineElementClass *klass;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (end), FALSE);

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);

  if (klass->roll_end)
    return klass->roll_end (self, end);

  return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_ROLL,
      GES_EDGE_END, end);
}

/**
 * ges_timeline_element_trim:
 * @self: The #GESTimelineElement to trim
 * @start: The new start time of @self in trim mode
 *
 * Edits the start time of an element within its timeline in trim mode.
 * See ges_timeline_element_edit() with #GES_EDIT_MODE_TRIM and
 * #GES_EDGE_START.
 *
 * Returns: %TRUE if the trim edit of @self completed, %FALSE on failure.
 */
gboolean
ges_timeline_element_trim (GESTimelineElement * self, GstClockTime start)
{
  GESTimelineElementClass *klass;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start), FALSE);

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);

  if (klass->trim)
    return klass->trim (self, start);

  return ges_timeline_element_edit (self, NULL, -1, GES_EDIT_MODE_TRIM,
      GES_EDGE_START, start);
}

/**
 * ges_timeline_element_copy:
 * @self: The #GESTimelineElement to copy
 * @deep: Whether the copy is needed for pasting
 *
 * Create a copy of @self. All the properties of @self are copied into
 * a new element, with the exception of #GESTimelineElement:parent,
 * #GESTimelineElement:timeline and #GESTimelineElement:name. Other data,
 * such the list of a #GESContainer's children, is **not** copied.
 *
 * If @deep is %TRUE, then the new element is prepared so that it can be
 * used in ges_timeline_element_paste() or ges_timeline_paste_element().
 * In the case of copying a #GESContainer, this ensures that the children
 * of @self will also be pasted. The new element should not be used for
 * anything else and can only be used **once** in a pasting operation. In
 * particular, the new element itself is not an actual 'deep' copy of
 * @self, but should be thought of as an intermediate object used for a
 * single paste operation.
 *
 * Returns: (transfer floating): The newly create element, copied from @self.
 */
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;       /* Start ignoring GParameter deprecation */
GESTimelineElement *
ges_timeline_element_copy (GESTimelineElement * self, gboolean deep)
{
  GESAsset *asset;
  GParamSpec **specs;
  GESTimelineElementClass *klass;
  guint n, n_specs;

  GESTimelineElement *ret = NULL;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL);

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);

  specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (self), &n_specs);

  asset = ges_extractable_get_asset (GES_EXTRACTABLE (self));
  g_assert (asset);
  ret = GES_TIMELINE_ELEMENT (ges_asset_extract (asset, NULL));
  for (n = 0; n < n_specs; ++n) {
    /* We do not want the timeline or the name to be copied */
    if (g_strcmp0 (specs[n]->name, "parent") &&
        g_strcmp0 (specs[n]->name, "timeline") &&
        g_strcmp0 (specs[n]->name, "name") &&
        (specs[n]->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE &&
        (specs[n]->flags & G_PARAM_CONSTRUCT_ONLY) == 0) {
      GValue v = G_VALUE_INIT;
      g_value_init (&v, specs[n]->value_type);
      g_object_get_property (G_OBJECT (self), specs[n]->name, &v);

      g_object_set_property (G_OBJECT (ret), specs[n]->name, &v);
      g_value_reset (&v);
    }
  }

  g_free (specs);
  if (deep) {
    if (klass->deep_copy)
      klass->deep_copy (self, ret);
    else
      GST_WARNING_OBJECT (self, "No deep_copy virtual method implementation"
          " on class %s. Can not finish the copy", G_OBJECT_CLASS_NAME (klass));
  }

  if (deep) {
    ret->priv->copied_from = gst_object_ref (self);
  }

  return ret;
}

G_GNUC_END_IGNORE_DEPRECATIONS; /* End ignoring GParameter deprecation */

/**
 * ges_timeline_element_get_toplevel_parent:
 * @self: The #GESTimelineElement to get the toplevel parent from
 *
 * Gets the toplevel #GESTimelineElement:parent of the element.
 *
 * Returns: (transfer full): The toplevel parent of @self.
 */
GESTimelineElement *
ges_timeline_element_get_toplevel_parent (GESTimelineElement * self)
{
  GESTimelineElement *toplevel;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL);

  toplevel = ges_timeline_element_peak_toplevel (self);

  return gst_object_ref (toplevel);
}

/**
 * ges_timeline_element_get_name:
 * @self: A #GESTimelineElement
 *
 * Gets the #GESTimelineElement:name for the element.
 *
 * Returns: (transfer full) (nullable): The name of @self.
 */
gchar *
ges_timeline_element_get_name (GESTimelineElement * self)
{
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL);

  return g_strdup (self->name);
}

/**
 * ges_timeline_element_set_name:
 * @self: A #GESTimelineElement
 * @name: (nullable): The name @self should take
 *
 * Sets the #GESTimelineElement:name for the element. If %NULL is given
 * for @name, then the library will instead generate a new name based on
 * the type name of the element, such as the name "uriclip3" for a
 * #GESUriClip, and will set that name instead.
 *
 * If @self already has a #GESTimelineElement:timeline, you should not
 * call this function with @name set to %NULL.
 *
 * You should ensure that, within each #GESTimeline, every element has a
 * unique name. If you call this function with @name as %NULL, then
 * the library should ensure that the set generated name is unique from
 * previously **generated** names. However, if you choose a @name that
 * interferes with the naming conventions of the library, the library will
 * attempt to ensure that the generated names will not conflict with the
 * chosen name, which may lead to a different name being set instead, but
 * the uniqueness between generated and user-chosen names is not
 * guaranteed.
 *
 * Returns: %TRUE if @name or a generated name for @self could be set.
 */
gboolean
ges_timeline_element_set_name (GESTimelineElement * self, const gchar * name)
{
  gboolean result = TRUE, readd_to_timeline = FALSE;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);

  if (name != NULL && !g_strcmp0 (name, self->name)) {
    GST_DEBUG_OBJECT (self, "Same name!");
    return TRUE;
  }

  /* parented objects cannot be renamed */
  if (self->timeline != NULL && name) {
    GESTimelineElement *tmp = ges_timeline_get_element (self->timeline, name);

    /* FIXME: if tmp == self then this means that we setting the name of
     * self to its existing name. There is no need to throw an error */
    if (tmp) {
      gst_object_unref (tmp);
      goto had_timeline;
    }

    timeline_remove_element (self->timeline, self);
    readd_to_timeline = TRUE;
  }
  /* FIXME: if self already has a timeline and name is NULL, then it also
   * needs to be re-added to the timeline (or, at least its entry in
   * timeline->priv->all_elements needs its key to be updated) using the
   * new generated name */

  _set_name (self, name);

  /* FIXME: the set name may not always be unique in a given timeline, see
   * _set_name(). This can cause timeline_add_element to fail! */
  if (readd_to_timeline)
    timeline_add_element (self->timeline, self);

  return result;

  /* error */
had_timeline:
  {
    /* FIXME: message is misleading. We are here if some other object in
     * the timeline was added under @name (see above) */
    GST_WARNING ("Object %s already in a timeline can't be renamed to %s",
        self->name, name);
    return FALSE;
  }
}

/**
 * ges_timeline_element_add_child_property:
 * @self: A #GESTimelineElement
 * @pspec: The specification for the property to add
 * @child: The #GstObject who the property belongs to
 *
 * Register a property of a child of the element to allow it to be
 * written with ges_timeline_element_set_child_property() and read with
 * ges_timeline_element_get_child_property(). A change in the property
 * will also appear in the #GESTimelineElement::deep-notify signal.
 *
 * @pspec should be unique from other children properties that have been
 * registered on @self.
 *
 * Returns: %TRUE if the property was successfully registered.
 */
gboolean
ges_timeline_element_add_child_property (GESTimelineElement * self,
    GParamSpec * pspec, GObject * child)
{
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE);
  g_return_val_if_fail (G_IS_OBJECT (child), FALSE);

  return ges_timeline_element_add_child_property_full (self, NULL, pspec,
      child, 0);
}

/**
 * ges_timeline_element_get_child_property_by_pspec:
 * @self: A #GESTimelineElement
 * @pspec: The specification of a registered child property to get
 * @value: (out): The return location for the value
 *
 * Gets the property of a child of the element. Specifically, the property
 * corresponding to the @pspec used in
 * ges_timeline_element_add_child_property() is copied into @value.
 */
void
ges_timeline_element_get_child_property_by_pspec (GESTimelineElement * self,
    GParamSpec * pspec, GValue * value)
{
  ChildPropSpec *childprop;

  g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
  g_return_if_fail (G_IS_PARAM_SPEC (pspec));

  childprop = _find_child_prop (self, pspec, NULL, NULL);
  if (!childprop)
    goto not_found;

  g_object_get_property (G_OBJECT (childprop->child), pspec->name, value);

  return;

not_found:
  {
    GST_ERROR_OBJECT (self, "The %s property doesn't exist", pspec->name);
    return;
  }
}

/**
 * ges_timeline_element_set_child_property_by_pspec:
 * @self: A #GESTimelineElement
 * @pspec: The specification of a registered child property to set
 * @value: The value to set the property to
 *
 * Sets the property of a child of the element. Specifically, the property
 * corresponding to the @pspec used in
 * ges_timeline_element_add_child_property() is set to @value.
 */
void
ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self,
    GParamSpec * pspec, const GValue * value)
{
  g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
  g_return_if_fail (G_IS_PARAM_SPEC (pspec));

  set_child_property_by_pspec (self, pspec, value, NULL);
}

/**
 * ges_timeline_element_set_child_property_full:
 * @self: A #GESTimelineElement
 * @property_name: The name of the child property to set
 * @value: The value to set the property to
 * @error: (nullable): Return location for an error
 *
 * Sets the property of a child of the element.
 *
 * @property_name can either be in the format "prop-name" or
 * "TypeName::prop-name", where "prop-name" is the name of the property
 * to set (as used in g_object_set()), and "TypeName" is the type name of
 * the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is
 * useful when two children of different types share the same property
 * name.
 *
 * The first child found with the given "prop-name" property that was
 * registered with ges_timeline_element_add_child_property() (and of the
 * type "TypeName", if it was given) will have the corresponding
 * property set to @value. Other children that may have also matched the
 * property name (and type name) are left unchanged!
 *
 * Returns: %TRUE if the property was found and set.
 * Since: 1.18
 */
gboolean
ges_timeline_element_set_child_property_full (GESTimelineElement * self,
    const gchar * property_name, const GValue * value, GError ** error)
{
  GParamSpec *pspec;
  gboolean res;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (!error || !*error, FALSE);

  if (!ges_timeline_element_lookup_child (self, property_name, NULL, &pspec))
    goto not_found;

  res = set_child_property_by_pspec (self, pspec, value, error);

  g_param_spec_unref (pspec);
  return res;

not_found:
  {
    GST_WARNING_OBJECT (self, "The %s property doesn't exist", property_name);

    return FALSE;
  }
}

/**
 * ges_timeline_element_set_child_property:
 * @self: A #GESTimelineElement
 * @property_name: The name of the child property to set
 * @value: The value to set the property to
 *
 * See ges_timeline_element_set_child_property_full(), which also gives an
 * error.
 *
 * Note that ges_timeline_element_set_child_properties() may be more
 * convenient for C programming.
 *
 * Returns: %TRUE if the property was found and set.
 */
gboolean
ges_timeline_element_set_child_property (GESTimelineElement * self,
    const gchar * property_name, const GValue * value)
{
  return ges_timeline_element_set_child_property_full (self, property_name,
      value, NULL);
}

/**
 * ges_timeline_element_get_child_property:
 * @self: A #GESTimelineElement
 * @property_name: The name of the child property to get
 * @value: (out): The return location for the value
 *
 * Gets the property of a child of the element.
 *
 * @property_name can either be in the format "prop-name" or
 * "TypeName::prop-name", where "prop-name" is the name of the property
 * to get (as used in g_object_get()), and "TypeName" is the type name of
 * the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is
 * useful when two children of different types share the same property
 * name.
 *
 * The first child found with the given "prop-name" property that was
 * registered with ges_timeline_element_add_child_property() (and of the
 * type "TypeName", if it was given) will have the corresponding
 * property copied into @value.
 *
 * Note that ges_timeline_element_get_child_properties() may be more
 * convenient for C programming.
 *
 * Returns: %TRUE if the property was found and copied to @value.
 */
gboolean
ges_timeline_element_get_child_property (GESTimelineElement * self,
    const gchar * property_name, GValue * value)
{
  GParamSpec *pspec;
  GObject *child;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);

  if (!ges_timeline_element_lookup_child (self, property_name, &child, &pspec))
    goto not_found;

  /* FIXME: since GLib 2.60, g_object_get_property() will automatically
   * initialize the type */
  if (G_VALUE_TYPE (value) == G_TYPE_INVALID)
    g_value_init (value, pspec->value_type);

  g_object_get_property (child, pspec->name, value);

  gst_object_unref (child);
  g_param_spec_unref (pspec);

  return TRUE;

not_found:
  {
    GST_WARNING_OBJECT (self, "The %s property doesn't exist", property_name);

    return FALSE;
  }
}

/**
 * ges_timeline_element_lookup_child:
 * @self: A #GESTimelineElement
 * @prop_name: The name of a child property
 * @child: (out) (optional) (transfer full): The return location for the
 * found child
 * @pspec: (out) (optional) (transfer full): The return location for the
 * specification of the child property
 *
 * Looks up a child property of the element.
 *
 * @prop_name can either be in the format "prop-name" or
 * "TypeName::prop-name", where "prop-name" is the name of the property
 * to look up (as used in g_object_get()), and "TypeName" is the type name
 * of the child (as returned by G_OBJECT_TYPE_NAME()). The latter format is
 * useful when two children of different types share the same property
 * name.
 *
 * The first child found with the given "prop-name" property that was
 * registered with ges_timeline_element_add_child_property() (and of the
 * type "TypeName", if it was given) will be passed to @child, and the
 * registered specification of this property will be passed to @pspec.
 *
 * Returns: %TRUE if a child corresponding to the property was found, in
 * which case @child and @pspec are set.
 */
gboolean
ges_timeline_element_lookup_child (GESTimelineElement * self,
    const gchar * prop_name, GObject ** child, GParamSpec ** pspec)
{
  GESTimelineElementClass *class;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  class = GES_TIMELINE_ELEMENT_GET_CLASS (self);
  g_return_val_if_fail (class->lookup_child, FALSE);

  return class->lookup_child (self, prop_name, child, pspec);
}

/**
 * ges_timeline_element_set_child_property_valist:
 * @self: A #GESTimelineElement
 * @first_property_name: The name of the first child property to set
 * @var_args: The value for the first property, followed optionally by more
 * name/value pairs, followed by %NULL
 *
 * Sets several of the children properties of the element. See
 * ges_timeline_element_set_child_property().
 */
void
ges_timeline_element_set_child_property_valist (GESTimelineElement * self,
    const gchar * first_property_name, va_list var_args)
{
  const gchar *name;
  GParamSpec *pspec;

  gchar *error = NULL;
  GValue value = { 0, };

  g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));

  name = first_property_name;

  /* Note: This part is in big part copied from the gst_child_object_set_valist
   * method. */

  /* iterate over pairs */
  while (name) {
    if (!ges_timeline_element_lookup_child (self, name, NULL, &pspec))
      goto not_found;

    memset (&value, 0, sizeof (value));
    G_VALUE_COLLECT_INIT (&value, pspec->value_type, var_args,
        G_VALUE_NOCOPY_CONTENTS, &error);

    if (error)
      goto cant_copy;

    set_child_property_by_pspec (self, pspec, &value, NULL);

    g_param_spec_unref (pspec);
    g_value_unset (&value);

    name = va_arg (var_args, gchar *);
  }
  return;

not_found:
  {
    GST_WARNING_OBJECT (self, "No property %s in OBJECT\n", name);
    return;
  }
cant_copy:
  {
    GST_WARNING_OBJECT (self, "error copying value %s in %p: %s", pspec->name,
        self, error);

    g_param_spec_unref (pspec);
    g_value_unset (&value);
    return;
  }
}

/**
 * ges_timeline_element_set_child_properties:
 * @self: A #GESTimelineElement
 * @first_property_name: The name of the first child property to set
 * @...: The value for the first property, followed optionally by more
 * name/value pairs, followed by %NULL
 *
 * Sets several of the children properties of the element. See
 * ges_timeline_element_set_child_property().
 */
void
ges_timeline_element_set_child_properties (GESTimelineElement * self,
    const gchar * first_property_name, ...)
{
  va_list var_args;

  g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));

  va_start (var_args, first_property_name);
  ges_timeline_element_set_child_property_valist (self, first_property_name,
      var_args);
  va_end (var_args);
}

/**
 * ges_timeline_element_get_child_property_valist:
 * @self: A #GESTimelineElement
 * @first_property_name: The name of the first child property to get
 * @var_args: The return location for the first property, followed
 * optionally by more name/return location pairs, followed by %NULL
 *
 * Gets several of the children properties of the element. See
 * ges_timeline_element_get_child_property().
 */
void
ges_timeline_element_get_child_property_valist (GESTimelineElement * self,
    const gchar * first_property_name, va_list var_args)
{
  const gchar *name;
  gchar *error = NULL;
  GValue value = { 0, };
  GParamSpec *pspec;
  GObject *child;

  g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));

  name = first_property_name;

  /* This part is in big part copied from the gst_child_object_get_valist method */
  while (name) {
    if (!ges_timeline_element_lookup_child (self, name, &child, &pspec))
      goto not_found;

    g_value_init (&value, pspec->value_type);
    g_object_get_property (child, pspec->name, &value);
    gst_object_unref (child);
    g_param_spec_unref (pspec);

    G_VALUE_LCOPY (&value, var_args, 0, &error);
    if (error)
      goto cant_copy;
    g_value_unset (&value);
    name = va_arg (var_args, gchar *);
  }
  return;

not_found:
  {
    GST_WARNING_OBJECT (self, "no child property %s", name);
    return;
  }
cant_copy:
  {
    GST_WARNING_OBJECT (self, "error copying value %s in %s", pspec->name,
        error);

    g_value_unset (&value);
    return;
  }
}

static gint
compare_gparamspec (GParamSpec ** a, GParamSpec ** b, gpointer udata)
{
  return g_strcmp0 ((*a)->name, (*b)->name);
}


/**
 * ges_timeline_element_list_children_properties:
 * @self: A #GESTimelineElement
 * @n_properties: (out): The return location for the length of the
 * returned array
 *
 * Get a list of children properties of the element, which is a list of
 * all the specifications passed to
 * ges_timeline_element_add_child_property().
 *
 * Returns: (transfer full) (array length=n_properties): An array of
 * #GParamSpec corresponding to the child properties of @self, or %NULL if
 * something went wrong.
 */
GParamSpec **
ges_timeline_element_list_children_properties (GESTimelineElement * self,
    guint * n_properties)
{
  GParamSpec **ret;
  GESTimelineElementClass *class;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), NULL);

  class = GES_TIMELINE_ELEMENT_GET_CLASS (self);

  if (!class->list_children_properties) {
    GST_INFO_OBJECT (self, "No %s->list_children_properties implementation",
        G_OBJECT_TYPE_NAME (self));

    *n_properties = 0;
    return NULL;
  }

  ret = class->list_children_properties (self, n_properties);
  g_sort_array (ret, *n_properties, sizeof (GParamSpec *),
      (GCompareDataFunc) compare_gparamspec, NULL);

  return ret;
}

/**
 * ges_timeline_element_get_child_properties:
 * @self: A #GESTimelineElement
 * @first_property_name: The name of the first child property to get
 * @...: The return location for the first property, followed
 * optionally by more name/return location pairs, followed by %NULL
 *
 * Gets several of the children properties of the element. See
 * ges_timeline_element_get_child_property().
 */
void
ges_timeline_element_get_child_properties (GESTimelineElement * self,
    const gchar * first_property_name, ...)
{
  va_list var_args;

  g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));

  va_start (var_args, first_property_name);
  ges_timeline_element_get_child_property_valist (self, first_property_name,
      var_args);
  va_end (var_args);
}

/**
 * ges_timeline_element_remove_child_property:
 * @self: A #GESTimelineElement
 * @pspec: The specification for the property to remove
 *
 * Remove a child property from the element. @pspec should be a
 * specification that was passed to
 * ges_timeline_element_add_child_property(). The corresponding property
 * will no longer be registered as a child property for the element.
 *
 * Returns: %TRUE if the property was successfully un-registered for @self.
 */
gboolean
ges_timeline_element_remove_child_property (GESTimelineElement * self,
    GParamSpec * pspec)
{
  return ges_timeline_element_remove_child_property_full (self, pspec, NULL);
}

gboolean
ges_timeline_element_remove_child_property_full (GESTimelineElement * self,
    GParamSpec * pspec, GObject * child)
{
  gint index;
  ChildPropSpec *childprop, handler_copy;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE);
  g_return_val_if_fail ((!child || G_IS_OBJECT (child)), FALSE);

  if (!(childprop = _find_child_prop (self, pspec, child, &index))) {
    GST_WARNING_OBJECT (self, "No child property with pspec %p (%s) found",
        pspec, pspec->name);
    return FALSE;
  }

  GST_DEBUG_OBJECT (child, "Removing %s", pspec->name);
  handler_copy = *childprop;
  g_array_set_clear_func (self->priv->children_props, NULL);
  g_array_remove_index (self->priv->children_props, index);
  g_array_set_clear_func (self->priv->children_props,
      (GDestroyNotify) _child_prop_spec_free);

  g_signal_emit (self, ges_timeline_element_signals[CHILD_PROPERTY_REMOVED], 0,
      handler_copy.child, handler_copy.pspec);

  _child_prop_spec_free (&handler_copy);

  return TRUE;
}

/**
 * ges_timeline_element_get_track_types:
 * @self: A #GESTimelineElement
 *
 * Gets the track types that the element can interact with, i.e. the type
 * of #GESTrack it can exist in, or will create #GESTrackElement-s for.
 *
 * Returns: The track types that @self supports.
 *
 * Since: 1.6.0
 */
GESTrackType
ges_timeline_element_get_track_types (GESTimelineElement * self)
{
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), 0);
  g_return_val_if_fail (GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_track_types,
      0);

  return GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_track_types (self);
}

/**
 * ges_timeline_element_paste:
 * @self: The #GESTimelineElement to paste
 * @paste_position: The position in the timeline @element should be pasted
 * to, i.e. the #GESTimelineElement:start value for the pasted element.
 *
 * Paste an element inside the same timeline and layer as @self. @self
 * **must** be the return of ges_timeline_element_copy() with `deep=TRUE`,
 * and it should not be changed before pasting.
 * @self is not placed in the timeline, instead a new element is created,
 * alike to the originally copied element. Note that the originally
 * copied element must stay within the same timeline and layer, at both
 * the point of copying and pasting.
 *
 * Pasting may fail if it would place the timeline in an unsupported
 * configuration.
 *
 * After calling this function @element should not be used. In particular,
 * @element can **not** be pasted again. Instead, you can copy the
 * returned element and paste that copy (although, this is only possible
 * if the paste was successful).
 *
 * See also ges_timeline_paste_element().
 *
 * Returns: (transfer full) (nullable): The newly created element, or
 * %NULL if pasting fails.
 *
 * Since: 1.6.0
 */
GESTimelineElement *
ges_timeline_element_paste (GESTimelineElement * self,
    GstClockTime paste_position)
{
  GESTimelineElement *res;
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (paste_position), FALSE);

  if (!self->priv->copied_from) {
    GST_ERROR_OBJECT (self, "Is not being 'deeply' copied!");

    return NULL;
  }

  if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste) {
    GST_ERROR_OBJECT (self, "No paste vmethod implemented");

    return NULL;
  }

  res = GES_TIMELINE_ELEMENT_GET_CLASS (self)->paste (self,
      self->priv->copied_from, paste_position);

  g_clear_object (&self->priv->copied_from);

  return res ? g_object_ref_sink (res) : res;
}

/**
 * ges_timeline_element_get_layer_priority:
 * @self: A #GESTimelineElement
 *
 * Gets the priority of the layer the element is in. A #GESGroup may span
 * several layers, so this would return the highest priority (numerically,
 * the smallest) amongst them.
 *
 * Returns: The priority of the layer @self is in, or
 * #GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY if @self does not exist in a
 * layer.
 *
 * Since: 1.16
 */
guint32
ges_timeline_element_get_layer_priority (GESTimelineElement * self)
{
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self),
      GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY);

  if (!GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_layer_priority)
    return self->priority;

  return GES_TIMELINE_ELEMENT_GET_CLASS (self)->get_layer_priority (self);
}

/**
 * ges_timeline_element_edit_full:
 * @self: The #GESTimelineElement to edit
 * @new_layer_priority: The priority/index of the layer @self should be
 * moved to. -1 means no move
 * @mode: The edit mode
 * @edge: The edge of @self where the edit should occur
 * @position: The edit position: a new location for the edge of @self
 * (in nanoseconds) in the timeline coordinates
 * @error: Return location for an error
 *
 * Edits the element within its timeline by adjusting its
 * #GESTimelineElement:start, #GESTimelineElement:duration or
 * #GESTimelineElement:in-point, and potentially doing the same for
 * other elements in the timeline. See #GESEditMode for details about each
 * edit mode. An edit may fail if it would place one of these properties
 * out of bounds, or if it would place the timeline in an unsupported
 * configuration.
 *
 * Note that if you act on a #GESTrackElement, this will edit its parent
 * #GESClip instead. Moreover, for any #GESTimelineElement, if you select
 * #GES_EDGE_NONE for #GES_EDIT_MODE_NORMAL or #GES_EDIT_MODE_RIPPLE, this
 * will edit the toplevel instead, but still in such a way as to make the
 * #GESTimelineElement:start of @self reach the edit @position.
 *
 * Note that if the element's timeline has a
 * #GESTimeline:snapping-distance set, then the edit position may be
 * snapped to the edge of some element under the edited element.
 *
 * @new_layer_priority can be used to switch @self, and other elements
 * moved by the edit, to a new layer. New layers may be be created if the
 * the corresponding layer priority/index does not yet exist for the
 * timeline.
 *
 * Returns: %TRUE if the edit of @self completed, %FALSE on failure.
 * Since: 1.18
 */

gboolean
ges_timeline_element_edit_full (GESTimelineElement * self,
    gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position,
    GError ** error)
{
  GESTimeline *timeline;
  guint32 layer_prio;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), FALSE);
  g_return_val_if_fail (!error || !*error, FALSE);

  timeline = GES_TIMELINE_ELEMENT_TIMELINE (self);
  g_return_val_if_fail (timeline, FALSE);

  layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (self);

  if (new_layer_priority < 0)
    new_layer_priority = layer_prio;

  GST_DEBUG_OBJECT (self, "Editing %s at edge %s to position %"
      GST_TIME_FORMAT " under %s mode, and to layer %" G_GINT64_FORMAT,
      self->name, ges_edge_name (edge), GST_TIME_ARGS (position),
      ges_edit_mode_name (mode), new_layer_priority);

  return ges_timeline_edit (timeline, self, new_layer_priority, mode,
      edge, position, error);
}

/**
 * ges_timeline_element_edit:
 * @self: The #GESTimelineElement to edit
 * @layers: (element-type GESLayer) (nullable): A whitelist of layers
 * where the edit can be performed, %NULL allows all layers in the
 * timeline.
 * @new_layer_priority: The priority/index of the layer @self should be
 * moved to. -1 means no move
 * @mode: The edit mode
 * @edge: The edge of @self where the edit should occur
 * @position: The edit position: a new location for the edge of @self
 * (in nanoseconds) in the timeline coordinates
 *
 * See ges_timeline_element_edit_full(), which also gives an error.
 *
 * Note that the @layers argument is currently ignored, so you should
 * just pass %NULL.
 *
 * Returns: %TRUE if the edit of @self completed, %FALSE on failure.
 *
 * Since: 1.18
 */

/* FIXME: handle the layers argument. Currently we always treat it as if
 * it is NULL in the ges-timeline code */
gboolean
ges_timeline_element_edit (GESTimelineElement * self, GList * layers,
    gint64 new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position)
{
  return ges_timeline_element_edit_full (self, new_layer_priority, mode, edge,
      position, NULL);
}

/**
 * ges_timeline_element_get_natural_framerate:
 * @self: The #GESTimelineElement to get "natural" framerate from
 * @framerate_n: (out): The framerate numerator
 * @framerate_d: (out): The framerate denominator
 *
 * Get the "natural" framerate of @self. This is to say, for example
 * for a #GESVideoUriSource the framerate of the source.
 *
 * Note that a #GESAudioSource may also have a natural framerate if it derives
 * from the same #GESSourceClip asset as a #GESVideoSource, and its value will
 * be that of the video source. For example, if the uri of a #GESUriClip points
 * to a file that contains both a video and audio stream, then the corresponding
 * #GESAudioUriSource will share the natural framerate of the corresponding
 * #GESVideoUriSource.
 *
 * Returns: Whether @self has a natural framerate or not, @framerate_n
 * and @framerate_d will be set to, respectively, 0 and -1 if it is
 * not the case.
 *
 * Since: 1.18
 */
gboolean
ges_timeline_element_get_natural_framerate (GESTimelineElement * self,
    gint * framerate_n, gint * framerate_d)
{
  GESTimelineElementClass *klass;

  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
  g_return_val_if_fail (framerate_n && framerate_d, FALSE);

  klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);

  *framerate_n = 0;
  *framerate_d = -1;
  return klass->get_natural_framerate (self, framerate_n, framerate_d);
}
