/* GStreamer 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:gescontainer
 * @title: GESContainer
 * @short_description: Base Class for objects responsible for controlling other
 * GESTimelineElement-s
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "ges-container.h"
#include "ges.h"
#include "ges-internal.h"

#include <string.h>

GST_DEBUG_CATEGORY_STATIC (ges_container_debug);
#undef GST_CAT_DEFAULT
#define GST_CAT_DEFAULT ges_container_debug

/* Mapping of relationship between a Container and the TimelineElements
 * it controls
 *
 * NOTE : Does it make sense to make it public in the future ?
 */
typedef struct
{
  GESTimelineElement *child;

  GstClockTime start_offset;
  GstClockTime duration_offset;
  GstClockTime inpoint_offset;
  gint32 priority_offset;

  guint start_notifyid;
  guint duration_notifyid;
  guint inpoint_notifyid;
} ChildMapping;

enum
{
  CHILD_ADDED_SIGNAL,
  CHILD_REMOVED_SIGNAL,
  LAST_SIGNAL
};

static guint ges_container_signals[LAST_SIGNAL] = { 0 };

struct _GESContainerPrivate
{
  /*< public > */
  GESLayer *layer;

  /*< private > */
  /* Set to TRUE when the container is doing updates of track object
   * properties so we don't end up in infinite property update loops
   */
  GHashTable *mappings;
  guint nb_effects;

  /* List of GESTimelineElement being in the "child-added" signal
   * emission stage */
  GList *adding_children;

  GList *copied_children;
};

enum
{
  PROP_0,
  PROP_HEIGHT,
  PROP_LAST
};

static GParamSpec *properties[PROP_LAST];

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESContainer, ges_container,
    GES_TYPE_TIMELINE_ELEMENT);

/************************
 *   Private  methods   *
 ************************/
static void
_free_mapping (ChildMapping * mapping)
{
  GESTimelineElement *child = mapping->child;

  /* Disconnect all notify listeners */
  if (mapping->start_notifyid)
    g_signal_handler_disconnect (child, mapping->start_notifyid);
  if (mapping->duration_notifyid)
    g_signal_handler_disconnect (child, mapping->duration_notifyid);
  if (mapping->inpoint_notifyid)
    g_signal_handler_disconnect (child, mapping->inpoint_notifyid);

  ges_timeline_element_set_parent (child, NULL);
  g_slice_free (ChildMapping, mapping);
}

static gint
compare_grouping_prio (GType * a, GType * b)
{
  gint ret = 0;
  GObjectClass *aclass = g_type_class_ref (*a);
  GObjectClass *bclass = g_type_class_ref (*b);

  /* We want higher prios to be first */
  if (GES_CONTAINER_CLASS (aclass)->grouping_priority <
      GES_CONTAINER_CLASS (bclass)->grouping_priority)
    ret = 1;
  else if (GES_CONTAINER_CLASS (aclass)->grouping_priority >
      GES_CONTAINER_CLASS (bclass)->grouping_priority)
    ret = -1;

  g_type_class_unref (aclass);
  g_type_class_unref (bclass);
  return ret;
}

static void
_resync_start_offsets (GESTimelineElement * child,
    ChildMapping * map, GESContainer * container)
{
  map->start_offset = _START (container) - _START (child);
}

/*****************************************************
 *                                                   *
 * GESTimelineElement virtual methods implementation *
 *                                                   *
 *****************************************************/
static gboolean
_set_start (GESTimelineElement * element, GstClockTime start)
{
  GList *tmp;
  ChildMapping *map;
  GESContainer *container = GES_CONTAINER (element);
  GESContainerPrivate *priv = container->priv;

  GST_DEBUG_OBJECT (element, "Updating children offsets, (initiated_move: %"
      GST_PTR_FORMAT ")", container->initiated_move);

  for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
    GESTimelineElement *child = (GESTimelineElement *) tmp->data;

    map = g_hash_table_lookup (priv->mappings, child);
    map->start_offset = start - _START (child);
  }
  container->children_control_mode = GES_CHILDREN_UPDATE;

  return TRUE;
}

static gboolean
_set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
{
  GList *tmp;
  GESContainer *container = GES_CONTAINER (element);

  for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
    GESTimelineElement *child = (GESTimelineElement *) tmp->data;
    ChildMapping *map = g_hash_table_lookup (container->priv->mappings, child);

    map->inpoint_offset = inpoint - _INPOINT (child);
  }

  return TRUE;
}

static gboolean
_set_duration (GESTimelineElement * element, GstClockTime duration)
{
  GList *tmp;
  GESContainer *container = GES_CONTAINER (element);
  GESContainerPrivate *priv = container->priv;

  for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
    GESTimelineElement *child = (GESTimelineElement *) tmp->data;
    ChildMapping *map = g_hash_table_lookup (priv->mappings, child);

    map->duration_offset = duration - _DURATION (child);
  }

  return TRUE;
}

static void
_ges_container_add_child_properties (GESContainer * container,
    GESTimelineElement * child)
{
  guint n_props, i;

  GParamSpec **child_props =
      ges_timeline_element_list_children_properties (child,
      &n_props);

  for (i = 0; i < n_props; i++) {
    GObject *prop_child;
    gchar *prop_name = g_strdup_printf ("%s::%s",
        g_type_name (child_props[i]->owner_type),
        child_props[i]->name);

    if (ges_timeline_element_lookup_child (child, prop_name, &prop_child, NULL)) {
      ges_timeline_element_add_child_property (GES_TIMELINE_ELEMENT (container),
          child_props[i], prop_child);
      gst_object_unref (prop_child);

    }
    g_free (prop_name);
    g_param_spec_unref (child_props[i]);
  }

  g_free (child_props);
}

static void
_ges_container_remove_child_properties (GESContainer * container,
    GESTimelineElement * child)
{
  guint n_props, i;

  GParamSpec **child_props =
      ges_timeline_element_list_children_properties (child,
      &n_props);

  for (i = 0; i < n_props; i++) {
    GObject *prop_child;
    gchar *prop_name = g_strdup_printf ("%s::%s",
        g_type_name (child_props[i]->owner_type),
        child_props[i]->name);

    if (ges_timeline_element_lookup_child (child, prop_name, &prop_child, NULL)) {
      ges_timeline_element_remove_child_property (GES_TIMELINE_ELEMENT
          (container), child_props[i]);
      gst_object_unref (prop_child);

    }

    g_free (prop_name);
    g_param_spec_unref (child_props[i]);
  }

  g_free (child_props);
}

static GParamSpec **
_list_children_properties (GESTimelineElement * self, guint * n_properties)
{
  GList *tmp;

  for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next)
    _ges_container_add_child_properties (GES_CONTAINER (self), tmp->data);

  return
      GES_TIMELINE_ELEMENT_CLASS
      (ges_container_parent_class)->list_children_properties (self,
      n_properties);
}

static gboolean
_lookup_child (GESTimelineElement * self, const gchar * prop_name,
    GObject ** child, GParamSpec ** pspec)
{
  GList *tmp;

  /* FIXME Implement a syntax to precisely get properties by path */
  for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
    if (ges_timeline_element_lookup_child (tmp->data, prop_name, child, pspec))
      return TRUE;
  }

  return FALSE;
}

static GESTrackType
_get_track_types (GESTimelineElement * object)
{
  GESTrackType types = GES_TRACK_TYPE_UNKNOWN;
  GList *tmp, *children = ges_container_get_children (GES_CONTAINER (object),
      TRUE);

  for (tmp = children; tmp; tmp = tmp->next) {
    if (GES_IS_TRACK_ELEMENT (tmp->data)) {
      types |= ges_timeline_element_get_track_types (tmp->data);
    }
  }

  g_list_free_full (children, gst_object_unref);

  return types ^ GES_TRACK_TYPE_UNKNOWN;
}

static void
_deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
{
  GList *tmp;
  GESContainer *self = GES_CONTAINER (element), *ccopy = GES_CONTAINER (copy);

  for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
    ChildMapping *map;

    map =
        g_slice_dup (ChildMapping, g_hash_table_lookup (self->priv->mappings,
            tmp->data));
    map->child = ges_timeline_element_copy (tmp->data, TRUE);
    map->start_notifyid = 0;
    map->inpoint_notifyid = 0;
    map->duration_notifyid = 0;

    ccopy->priv->copied_children = g_list_prepend (ccopy->priv->copied_children,
        map);
  }
}

static GESTimelineElement *
_paste (GESTimelineElement * element, GESTimelineElement * ref,
    GstClockTime paste_position)
{
  GList *tmp;
  ChildMapping *map;
  GESContainer *ncontainer =
      GES_CONTAINER (ges_timeline_element_copy (element, FALSE));
  GESContainer *self = GES_CONTAINER (element);

  for (tmp = self->priv->copied_children; tmp; tmp = tmp->next) {
    GESTimelineElement *nchild;

    map = tmp->data;
    nchild =
        ges_timeline_element_paste (map->child,
        paste_position - map->start_offset);
    ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (ncontainer),
        GES_TIMELINE_ELEMENT_TIMELINE (ref));
    ges_container_add (ncontainer, nchild);
  }

  return GES_TIMELINE_ELEMENT (ncontainer);
}


/******************************************
 *                                        *
 * GObject virtual methods implementation *
 *                                        *
 ******************************************/
static void
_dispose (GObject * object)
{
  GList *tmp;
  GESContainer *self = GES_CONTAINER (object);
  GList *children;

  _ges_container_sort_children (self);
  children = ges_container_get_children (self, FALSE);

  for (tmp = g_list_last (children); tmp; tmp = tmp->prev)
    ges_container_remove (self, tmp->data);

  g_list_free_full (children, gst_object_unref);
  self->children = NULL;

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

static void
_finalize (GObject * object)
{
  GESContainer *self = GES_CONTAINER (object);

  g_list_free_full (self->priv->copied_children,
      (GDestroyNotify) _free_mapping);

  if (self->priv->mappings)
    g_hash_table_destroy (self->priv->mappings);

  G_OBJECT_CLASS (ges_container_parent_class)->finalize (object);
}

static void
_get_property (GObject * container, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  GESContainer *tobj = GES_CONTAINER (container);

  switch (property_id) {
    case PROP_HEIGHT:
      g_value_set_uint (value, tobj->height);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (container, property_id, pspec);
  }
}

static void
_set_property (GObject * container, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  switch (property_id) {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (container, property_id, pspec);
  }
}

static void
ges_container_class_init (GESContainerClass * klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);

  GST_DEBUG_CATEGORY_INIT (ges_container_debug, "gescontainer",
      GST_DEBUG_FG_YELLOW, "ges container");

  object_class->get_property = _get_property;
  object_class->set_property = _set_property;
  object_class->dispose = _dispose;
  object_class->finalize = _finalize;

  /**
   * GESContainer:height:
   *
   * The span of priorities which this container occupies.
   */
  properties[PROP_HEIGHT] = g_param_spec_uint ("height", "Height",
      "The span of priorities this container occupies", 0, G_MAXUINT, 1,
      G_PARAM_READABLE);
  g_object_class_install_property (object_class, PROP_HEIGHT,
      properties[PROP_HEIGHT]);

  /**
   * GESContainer::child-added:
   * @container: the #GESContainer
   * @element: the #GESTimelineElement that was added.
   *
   * Will be emitted after a child was added to @container.
   * Usually you should connect with #g_signal_connect_after
   * as in the first emission stage, the signal emission might
   * get stopped internally.
   */
  ges_container_signals[CHILD_ADDED_SIGNAL] =
      g_signal_new ("child-added", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESContainerClass, child_added),
      NULL, NULL, g_cclosure_marshal_generic,
      G_TYPE_NONE, 1, GES_TYPE_TIMELINE_ELEMENT);

  /**
   * GESContainer::child-removed:
   * @container: the #GESContainer
   * @element: the #GESTimelineElement that was removed.
   *
   * Will be emitted after a child was removed from @container.
   */
  ges_container_signals[CHILD_REMOVED_SIGNAL] =
      g_signal_new ("child-removed", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESContainerClass, child_removed),
      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
      GES_TYPE_TIMELINE_ELEMENT);


  element_class->set_start = _set_start;
  element_class->set_duration = _set_duration;
  element_class->set_inpoint = _set_inpoint;
  element_class->list_children_properties = _list_children_properties;
  element_class->lookup_child = _lookup_child;
  element_class->get_track_types = _get_track_types;
  element_class->paste = _paste;
  element_class->deep_copy = _deep_copy;

  /* No default implementations */
  klass->remove_child = NULL;
  klass->add_child = NULL;
  klass->ungroup = NULL;
  klass->group = NULL;
  klass->grouping_priority = 0;
  klass->edit = NULL;
}

static void
ges_container_init (GESContainer * self)
{
  self->priv = ges_container_get_instance_private (self);

  /* FIXME, check why default was GST_SECOND? (before the existend of
   * ges-container)
   *
   * _DURATION (self) = GST_SECOND; */
  self->height = 1;             /* FIXME Why 1 and not 0? */
  self->children = NULL;

  self->priv->mappings = g_hash_table_new_full (g_direct_hash, g_direct_equal,
      NULL, (GDestroyNotify) _free_mapping);
}

/**********************************************
 *                                            *
 *    Property notifications from Children    *
 *                                            *
 **********************************************/
static void
_child_start_changed_cb (GESTimelineElement * child,
    GParamSpec * arg G_GNUC_UNUSED, GESContainer * container)
{
  ChildMapping *map;
  GstClockTime start;

  GESContainerPrivate *priv = container->priv;
  GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);

  map = g_hash_table_lookup (priv->mappings, child);
  g_assert (map);

  switch (container->children_control_mode) {
    case GES_CHILDREN_IGNORE_NOTIFIES:
      return;
    case GES_CHILDREN_UPDATE_ALL_VALUES:
      _ges_container_sort_children (container);
      start = container->children ?
          _START (container->children->data) : _START (container);

      if (start != _START (container)) {
        _DURATION (container) = _END (container) - start;
        _START (container) = start;

        GST_DEBUG_OBJECT (container, "Child move made us "
            "move to %" GST_TIME_FORMAT, GST_TIME_ARGS (_START (container)));

        g_object_notify (G_OBJECT (container), "start");
      }

      /* Falltrough! */
    case GES_CHILDREN_UPDATE_OFFSETS:
      map->start_offset = _START (container) - _START (child);
      break;

    case GES_CHILDREN_UPDATE:
      /* We update all the children calling our set_start method */
      container->initiated_move = child;
      _set_start0 (element, _START (child) + map->start_offset);
      container->initiated_move = NULL;
      break;
    default:
      break;
  }
}

static void
_child_inpoint_changed_cb (GESTimelineElement * child,
    GParamSpec * arg G_GNUC_UNUSED, GESContainer * container)
{
  ChildMapping *map;

  GESContainerPrivate *priv = container->priv;
  GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);

  if (container->children_control_mode == GES_CHILDREN_IGNORE_NOTIFIES)
    return;

  map = g_hash_table_lookup (priv->mappings, child);
  g_assert (map);

  if (container->children_control_mode == GES_CHILDREN_UPDATE_OFFSETS) {
    map->inpoint_offset = _START (container) - _START (child);

    return;
  }

  /* We update all the children calling our set_inpoint method */
  container->initiated_move = child;
  _set_inpoint0 (element, _INPOINT (child) + map->inpoint_offset);
  container->initiated_move = NULL;
}

static void
_child_duration_changed_cb (GESTimelineElement * child,
    GParamSpec * arg G_GNUC_UNUSED, GESContainer * container)
{
  ChildMapping *map;

  GList *tmp;
  GstClockTime end = 0;
  GESContainerPrivate *priv = container->priv;
  GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);

  if (container->children_control_mode == GES_CHILDREN_IGNORE_NOTIFIES)
    return;

  map = g_hash_table_lookup (priv->mappings, child);
  g_assert (map);

  switch (container->children_control_mode) {
    case GES_CHILDREN_IGNORE_NOTIFIES:
      break;
    case GES_CHILDREN_UPDATE_ALL_VALUES:
      _ges_container_sort_children_by_end (container);

      for (tmp = container->children; tmp; tmp = tmp->next)
        end = MAX (end, _END (tmp->data));

      if (end != _END (container)) {
        _DURATION (container) = end - _START (container);
        g_object_notify (G_OBJECT (container), "duration");
      }
      /* Falltrough */
    case GES_CHILDREN_UPDATE_OFFSETS:
      map->inpoint_offset = _START (container) - _START (child);
      break;
    case GES_CHILDREN_UPDATE:
      /* We update all the children calling our set_duration method */
      container->initiated_move = child;
      _set_duration0 (element, _DURATION (child) + map->duration_offset);
      container->initiated_move = NULL;
      break;
    default:
      break;
  }
}

/****************************************************
 *                                                  *
 *         Internal methods implementation          *
 *                                                  *
 ****************************************************/

void
_ges_container_sort_children (GESContainer * container)
{
  container->children = g_list_sort (container->children,
      (GCompareFunc) element_start_compare);
}

void
_ges_container_sort_children_by_end (GESContainer * container)
{
  container->children = g_list_sort (container->children,
      (GCompareFunc) element_end_compare);
}

void
_ges_container_set_height (GESContainer * container, guint32 height)
{
  if (container->height != height) {
    container->height = height;
    GST_DEBUG_OBJECT (container, "Updating height %i", container->height);
    g_object_notify (G_OBJECT (container), "height");
  }
}

gint
_ges_container_get_priority_offset (GESContainer * container,
    GESTimelineElement * elem)
{
  ChildMapping *map = g_hash_table_lookup (container->priv->mappings, elem);

  g_return_val_if_fail (map, 0);

  return map->priority_offset;
}

void
_ges_container_set_priority_offset (GESContainer * container,
    GESTimelineElement * elem, gint32 priority_offset)
{
  ChildMapping *map = g_hash_table_lookup (container->priv->mappings, elem);

  g_return_if_fail (map);

  map->priority_offset = priority_offset;
}

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

/**
 * ges_container_add:
 * @container: a #GESContainer
 * @child: the #GESTimelineElement
 *
 * Add the #GESTimelineElement to the container.
 *
 * Returns: %TRUE on success, %FALSE on failure.
 */
gboolean
ges_container_add (GESContainer * container, GESTimelineElement * child)
{
  ChildMapping *mapping;
  gboolean notify_start = FALSE;
  GESContainerClass *class;
  GESContainerPrivate *priv;

  g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (child), FALSE);
  g_return_val_if_fail (GES_TIMELINE_ELEMENT_PARENT (child) == NULL, FALSE);

  class = GES_CONTAINER_GET_CLASS (container);
  priv = container->priv;

  GST_DEBUG_OBJECT (container, "adding timeline element %" GST_PTR_FORMAT,
      child);

  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
  if (class->add_child) {
    if (class->add_child (container, child) == FALSE) {
      container->children_control_mode = GES_CHILDREN_UPDATE;
      GST_WARNING_OBJECT (container, "Erreur adding child %p", child);
      return FALSE;
    }
  }
  container->children_control_mode = GES_CHILDREN_UPDATE;

  if (_START (container) > _START (child)) {
    _START (container) = _START (child);

    g_hash_table_foreach (priv->mappings, (GHFunc) _resync_start_offsets,
        container);
    notify_start = TRUE;
  }

  mapping = g_slice_new0 (ChildMapping);
  mapping->child = gst_object_ref (child);
  mapping->start_offset = _START (container) - _START (child);
  mapping->duration_offset = _DURATION (container) - _DURATION (child);
  mapping->inpoint_offset = _INPOINT (container) - _INPOINT (child);

  g_hash_table_insert (priv->mappings, child, mapping);

  container->children = g_list_prepend (container->children, child);

  _ges_container_sort_children (container);

  /* Listen to all property changes */
  mapping->start_notifyid =
      g_signal_connect (G_OBJECT (child), "notify::start",
      G_CALLBACK (_child_start_changed_cb), container);
  mapping->duration_notifyid =
      g_signal_connect (G_OBJECT (child), "notify::duration",
      G_CALLBACK (_child_duration_changed_cb), container);
  mapping->inpoint_notifyid =
      g_signal_connect (G_OBJECT (child), "notify::in-point",
      G_CALLBACK (_child_inpoint_changed_cb), container);

  if (ges_timeline_element_set_parent (child, GES_TIMELINE_ELEMENT (container))
      == FALSE) {

    g_hash_table_remove (priv->mappings, child);
    container->children = g_list_remove (container->children, child);
    _ges_container_sort_children (container);

    return FALSE;
  }

  _ges_container_add_child_properties (container, child);

  priv->adding_children = g_list_prepend (priv->adding_children, child);
  g_signal_emit (container, ges_container_signals[CHILD_ADDED_SIGNAL], 0,
      child);
  priv->adding_children = g_list_remove (priv->adding_children, child);

  if (notify_start)
    g_object_notify (G_OBJECT (container), "start");

  return TRUE;
}

/**
 * ges_container_remove:
 * @container: a #GESContainer
 * @child: the #GESTimelineElement to release
 *
 * Release the @child from the control of @container.
 *
 * Returns: %TRUE if the @child was properly released, else %FALSE.
 */
gboolean
ges_container_remove (GESContainer * container, GESTimelineElement * child)
{
  GESContainerClass *klass;
  GESContainerPrivate *priv;

  g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
  g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (child), FALSE);

  GST_DEBUG_OBJECT (container, "removing child: %" GST_PTR_FORMAT, child);

  klass = GES_CONTAINER_GET_CLASS (container);
  priv = container->priv;

  if (!(g_hash_table_lookup (priv->mappings, child))) {
    GST_WARNING_OBJECT (container, "Element isn't controlled by this "
        "container");
    return FALSE;
  }

  if (klass->remove_child) {
    if (klass->remove_child (container, child) == FALSE)
      return FALSE;
  }

  container->children = g_list_remove (container->children, child);
  /* Let it live removing from our mappings */
  g_hash_table_remove (priv->mappings, child);

  _ges_container_remove_child_properties (container, child);

  if (!g_list_find (container->priv->adding_children, child)) {
    g_signal_emit (container, ges_container_signals[CHILD_REMOVED_SIGNAL], 0,
        child);
  } else {
    GST_INFO_OBJECT (container, "Not emitting 'child-removed' signal as child"
        " removal happend during 'child-added' signal emission");
  }
  gst_object_unref (child);

  return TRUE;
}

static void
_get_children_recursively (GESContainer * container, GList ** children)
{
  GList *tmp;

  *children =
      g_list_concat (*children, g_list_copy_deep (container->children,
          (GCopyFunc) gst_object_ref, NULL));

  for (tmp = container->children; tmp; tmp = tmp->next) {
    GESTimelineElement *element = tmp->data;

    if (GES_IS_CONTAINER (element))
      _get_children_recursively (tmp->data, children);
  }
}

/**
 * ges_container_get_children:
 * @container: a #GESContainer
 * @recursive:  Whether to recursively get children in @container
 *
 * Get the list of #GESTimelineElement contained in @container
 * The user is responsible for unreffing the contained objects
 * and freeing the list.
 *
 * Returns: (transfer full) (element-type GESTimelineElement): The list of
 * timeline element contained in @container.
 */
GList *
ges_container_get_children (GESContainer * container, gboolean recursive)
{
  GList *children = NULL;

  g_return_val_if_fail (GES_IS_CONTAINER (container), NULL);

  if (!recursive)
    return g_list_copy_deep (container->children, (GCopyFunc) gst_object_ref,
        NULL);

  _get_children_recursively (container, &children);
  return children;
}

/**
 * ges_container_ungroup:
 * @container: (transfer full): The #GESContainer to ungroup
 * @recursive: Wether to recursively ungroup @container
 *
 * Ungroups the #GESTimelineElement contained in this GESContainer,
 * creating new #GESContainer containing those #GESTimelineElement
 * apropriately.
 *
 * Returns: (transfer full) (element-type GESContainer): The list of
 * #GESContainer resulting from the ungrouping operation
 * The user is responsible for unreffing the contained objects
 * and freeing the list.
 */
GList *
ges_container_ungroup (GESContainer * container, gboolean recursive)
{
  GESContainerClass *klass;

  g_return_val_if_fail (GES_IS_CONTAINER (container), NULL);

  GST_DEBUG_OBJECT (container, "Ungrouping container %s recursively",
      recursive ? "" : "not");

  klass = GES_CONTAINER_GET_CLASS (container);
  if (klass->ungroup == NULL) {
    GST_INFO_OBJECT (container, "No ungoup virtual method, doint nothing");
    return NULL;
  }

  return klass->ungroup (container, recursive);
}

/**
 * ges_container_group:
 * @containers: (transfer none)(element-type GESContainer) (allow-none): The
 * #GESContainer to group, they must all be in a same #GESTimeline
 *
 * Groups the #GESContainer-s provided in @containers. It creates a subclass
 * of #GESContainer, depending on the containers provided in @containers.
 * Basically, if all the containers in @containers should be contained in a same
 * clip (all the #GESTrackElement they contain have the exact same
 * start/inpoint/duration and are in the same layer), it will create a #GESClip
 * otherwise a #GESGroup will be created
 *
 * Returns: (transfer none): The #GESContainer (subclass) resulting of the
 * grouping
 */
GESContainer *
ges_container_group (GList * containers)
{
  GList *tmp;
  guint n_children;
  GType *children_types;
  GESTimelineElement *element;
  GObjectClass *clip_class;

  guint i = 0;
  GESContainer *ret = NULL;
  GESTimeline *timeline = NULL;

  if (containers) {
    element = GES_TIMELINE_ELEMENT (containers->data);
    timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
    g_return_val_if_fail (timeline, NULL);
  }

  if (g_list_length (containers) == 1)
    return containers->data;

  for (tmp = containers; tmp; tmp = tmp->next) {
    g_return_val_if_fail (GES_IS_CONTAINER (tmp->data), NULL);
    g_return_val_if_fail (GES_TIMELINE_ELEMENT_PARENT (tmp->data) == NULL,
        NULL);
    g_return_val_if_fail (GES_TIMELINE_ELEMENT_TIMELINE (tmp->data) == timeline,
        NULL);
  }

  children_types = g_type_children (GES_TYPE_CONTAINER, &n_children);
  g_qsort_with_data (children_types, n_children, sizeof (GType),
      (GCompareDataFunc) compare_grouping_prio, NULL);

  for (i = 0; i < n_children; i++) {
    clip_class = g_type_class_peek (children_types[i]);
    ret = GES_CONTAINER_CLASS (clip_class)->group (containers);

    if (ret)
      break;
  }

  g_free (children_types);
  return ret;
}

/**
 * ges_container_edit:
 * @container: the #GESClip to edit
 * @layers: (element-type GESLayer): The layers you want the edit to
 *  happen in, %NULL means that the edition is done in all the
 *  #GESLayers contained in the current timeline.
 * @new_layer_priority: The priority of the layer @container should land in.
 *  If the layer you're trying to move the container to doesn't exist, it will
 *  be created automatically. -1 means no move.
 * @mode: The #GESEditMode in which the editition will happen.
 * @edge: The #GESEdge the edit should happen on.
 * @position: The position at which to edit @container (in nanosecond)
 *
 * Edit @container in the different exisiting #GESEditMode modes. In the case of
 * slide, and roll, you need to specify a #GESEdge
 *
 * Returns: %TRUE if the container as been edited properly, %FALSE if an error
 * occured
 */
gboolean
ges_container_edit (GESContainer * container, GList * layers,
    gint new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position)
{
  g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);

  if (G_UNLIKELY (GES_CONTAINER_GET_CLASS (container)->edit == NULL)) {
    GST_WARNING_OBJECT (container, "No edit vmethod implementation");
    return FALSE;
  }

  return GES_CONTAINER_GET_CLASS (container)->edit (container, layers,
      new_layer_priority, mode, edge, position);
}
