/* GStreamer Editing Services
 * Copyright (C) 2013 Mathieu Duponchelle <mduponchelle1@gmail.com>
 *
 * 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.
 */

#include "test-utils.h"
#include <ges/ges.h>
#include <gst/check/gstcheck.h>

static GList *tests_names;

/* *INDENT-OFF* */
static const char * const profile_specs[][4] = {
  { "application/ogg", "audio/x-vorbis", "video/x-theora", "assets/vorbis_theora.rendered.ogv" },
  { "video/webm", "audio/x-vorbis", "video/x-vp8", "assets/vorbis_vp8.rendered.webm"},
  { "video/quicktime,variant=iso", "audio/mpeg,mpegversion=1,layer=3", "video/x-h264",  "assets/aac_h264.rendered.mov"},
  { "video/x-matroska", "audio/x-vorbis", "video/x-h264", "assets/vorbis_h264.rendered.mkv"},
};
/* *INDENT-ON* */

typedef enum
{
  PROFILE_NONE = -1,
  PROFILE_VORBIS_THEORA_OGG,
  PROFILE_VORBIS_VP8_WEBM,
  PROFILE_AAC_H264_QUICKTIME,
  PROFILE_VORBIS_H264_MATROSKA,
} EncodingProfileName;

typedef struct _PresetInfos
{
  const gchar *muxer_preset_name;
  const gchar *audio_preset_name;
  const gchar *video_preset_name;

  gsize expected_size;

} PresetInfos;

typedef struct SeekInfo
{
  GstClockTime position;        /* position to seek to */
  GstClockTime seeking_position;        /* position to do seek from */
} SeekInfo;

static GMainLoop *loop;
static GESPipeline *pipeline = NULL;
static gint64 seeked_position = GST_CLOCK_TIME_NONE;    /* last seeked position */
static gint64 seek_tol = 0.05 * GST_SECOND;     /* tolerance seek interval */
static GList *seeks;            /* list of seeks */
static gboolean got_async_done = FALSE;
static gboolean seek_paused = FALSE, seek_paused_noplay = FALSE;

/* This allow us to run the tests multiple times with different input files */
static const gchar *testfilename1 = NULL;
static const gchar *testfilename2 = NULL;
static const gchar *test_image_filename = NULL;
static EncodingProfileName current_profile = PROFILE_NONE;

#define DURATION_TOLERANCE 0.1 * GST_SECOND

#define get_asset(filename, asset)                                            \
{                                                                              \
  GError *error = NULL;                                                        \
  gchar *uri = ges_test_file_name (filename);                                  \
  asset = ges_uri_clip_asset_request_sync (uri, &error);                       \
  fail_unless (GES_IS_ASSET (asset), "Testing file %s could not be used as an "\
      "asset -- Reason: %s", uri, error ? error->message : "Uknown");          \
  g_free (uri);                                                                \
}

static SeekInfo *
new_seek_info (GstClockTime seeking_position, GstClockTime position)
{
  SeekInfo *info = g_slice_new0 (SeekInfo);
  info->seeking_position = seeking_position;
  info->position = position;
  return info;
}

static GstEncodingProfile *
create_profile (const char *container, const char *container_preset,
    const char *audio, const char *audio_preset, const char *video,
    const char *video_preset)
{
  GstEncodingContainerProfile *cprof = NULL;
  GstEncodingProfile *prof = NULL;
  GstCaps *caps, *restriction_caps;

  /* If we have both audio and video, we must have container */
  if (audio && video && !container)
    return NULL;

  if (container) {
    caps = gst_caps_from_string (container);
    cprof = gst_encoding_container_profile_new ("User profile", "User profile",
        caps, NULL);
    gst_caps_unref (caps);
    if (!cprof)
      return NULL;
    if (container_preset)
      gst_encoding_profile_set_preset ((GstEncodingProfile *) cprof,
          container_preset);
  }

  if (audio) {
    caps = gst_caps_from_string (audio);
    prof = (GstEncodingProfile *) gst_encoding_audio_profile_new (caps, NULL,
        NULL, 0);
    if (!prof)
      goto beach;
    if (audio_preset)
      gst_encoding_profile_set_preset (prof, audio_preset);
    if (cprof)
      gst_encoding_container_profile_add_profile (cprof, prof);
    gst_caps_unref (caps);
  }
  if (video) {
    restriction_caps =
        gst_caps_new_simple ("video/x-raw", "framerate", GST_TYPE_FRACTION, 30,
        1, "format", G_TYPE_STRING, "I420", NULL);
    caps = gst_caps_from_string (video);
    prof = (GstEncodingProfile *) gst_encoding_video_profile_new (caps, NULL,
        restriction_caps, 0);
    if (!prof)
      goto beach;
    if (video_preset)
      gst_encoding_profile_set_preset (prof, video_preset);
    if (cprof)
      gst_encoding_container_profile_add_profile (cprof, prof);
    gst_caps_unref (caps);
  }

  return cprof ? (GstEncodingProfile *) cprof : (GstEncodingProfile *) prof;

beach:
  if (cprof)
    gst_encoding_profile_unref (cprof);
  else
    gst_encoding_profile_unref (prof);
  return NULL;
}

static GstEncodingProfile *
create_audio_video_profile (EncodingProfileName type)
{
  return create_profile (profile_specs[type][0], NULL, profile_specs[type][1],
      NULL, profile_specs[type][2], NULL);
}

/* This is used to specify a dot dumping after the target element started outputting buffers */
static const gchar *target_element = "smart-mixer-mixer";

static GstPadProbeReturn
dump_to_dot (GstPad * pad, GstPadProbeInfo * info)
{
  GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
      GST_DEBUG_GRAPH_SHOW_ALL, "ges-integration-smart-mixer-push-buffer");
  return (GST_PAD_PROBE_REMOVE);
}

static gboolean
my_bus_callback (GstBus * bus, GstMessage * message, gpointer data)
{
  gboolean *ret = (gboolean *) data;

  switch (GST_MESSAGE_TYPE (message)) {
    case GST_MESSAGE_STATE_CHANGED:{
      GstState old_state, new_state;

      gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
      /* HACK */
      if (new_state == GST_STATE_PLAYING
          && !g_strcmp0 (GST_MESSAGE_SRC_NAME (message), target_element))
        gst_pad_add_probe (gst_element_get_static_pad (GST_ELEMENT
                (GST_MESSAGE_SRC (message)), "src"), GST_PAD_PROBE_TYPE_BUFFER,
            (GstPadProbeCallback) dump_to_dot, NULL, NULL);
      break;
    }
    case GST_MESSAGE_ERROR:{
      GError *err;
      gchar *debug;

      gst_message_parse_error (message, &err, &debug);
      GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
          GST_DEBUG_GRAPH_SHOW_ALL, "ges-integration-error");

      g_assert_no_error (err);
      g_error_free (err);
      g_free (debug);
      g_main_loop_quit (loop);
      break;
    }
    case GST_MESSAGE_EOS:
      GST_INFO ("EOS\n");
      *ret = TRUE;
      g_main_loop_quit (loop);
      break;
    case GST_MESSAGE_ASYNC_DONE:
      got_async_done = TRUE;
      if (GST_CLOCK_TIME_IS_VALID (seeked_position))
        seeked_position = GST_CLOCK_TIME_NONE;

      if (seeks == NULL && seek_paused_noplay) {
        /* We are now done with seeking, let it play until the end */
        gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
        gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, -1);
      }
      break;
    default:
      /* unhandled message */
      break;
  }
  return TRUE;
}

static gboolean
get_position (void)
{
  GList *tmp;
  gint64 position;
  gst_element_query_position (GST_ELEMENT (pipeline), GST_FORMAT_TIME,
      &position);
  tmp = seeks;

  GST_LOG ("Current position: %" GST_TIME_FORMAT, GST_TIME_ARGS (position));
  while (tmp) {
    SeekInfo *seek = tmp->data;
    if ((position >= (seek->seeking_position - seek_tol))
        && (position <= (seek->seeking_position + seek_tol))) {

      if (!got_async_done) {
        GST_INFO ("Still not received ASYNC_DONE, keep going");
        return TRUE;
      }
      got_async_done = FALSE;

      GST_INFO ("seeking to: %" GST_TIME_FORMAT,
          GST_TIME_ARGS (seek->position));

      seeked_position = seek->position;
      if (seek_paused) {
        gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED);
        GST_LOG ("Set state playing");
        gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, -1);
        GST_LOG ("Done wainting");
      }

      if (seek_paused_noplay) {
        gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED);
        gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, -1);
      }
      fail_unless (gst_element_seek_simple (GST_ELEMENT (pipeline),
              GST_FORMAT_TIME,
              GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, seek->position));

      if (seek_paused) {
        gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
        gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, -1);
      }
      seeks = g_list_remove_link (seeks, tmp);
      g_slice_free (SeekInfo, seek);
      g_list_free (tmp);
      break;
    }
    tmp = tmp->next;
  }
  /* if seeking paused without playing and we reached the last seek, just play
   * till the end */
  return TRUE;
}

static void
check_rendered_file_properties (const gchar * render_file,
    GstClockTime duration)
{
  GESUriClipAsset *asset;
  GstDiscovererInfo *info;
  GstClockTime real_duration;

  /* TODO: extend these tests */

  get_asset (render_file, asset);
  info = ges_uri_clip_asset_get_info (GES_URI_CLIP_ASSET (asset));
  gst_object_unref (asset);

  fail_unless (GST_IS_DISCOVERER_INFO (info), "Could not discover file %s",
      render_file);

  /* Let's not be too nazi */

  real_duration = gst_discoverer_info_get_duration (info);

  fail_if ((real_duration < duration - DURATION_TOLERANCE)
      || (real_duration > duration + DURATION_TOLERANCE), "Duration %"
      GST_TIME_FORMAT " not in range [%" GST_TIME_FORMAT " -- %"
      GST_TIME_FORMAT "]", GST_TIME_ARGS (real_duration),
      GST_TIME_ARGS (duration - DURATION_TOLERANCE),
      GST_TIME_ARGS (duration + DURATION_TOLERANCE));


  gst_object_unref (info);
}

static gboolean
check_timeline (GESTimeline * timeline)
{
  GstBus *bus;
  static gboolean ret;
  GstEncodingProfile *profile;
  gchar *render_uri = NULL;

  ret = FALSE;

  ges_timeline_commit (timeline);
  pipeline = ges_pipeline_new ();
  if (current_profile != PROFILE_NONE) {
    render_uri = ges_test_file_name (profile_specs[current_profile][3]);

    profile = create_audio_video_profile (current_profile);
    ges_pipeline_set_render_settings (pipeline, render_uri, profile);
    ges_pipeline_set_mode (pipeline, GES_PIPELINE_MODE_RENDER);


    gst_object_unref (profile);
  } else if (g_getenv ("GES_MUTE_TESTS")) {
    GstElement *sink = gst_element_factory_make ("fakesink", NULL);

    g_object_set (sink, "sync", TRUE, NULL);
    ges_pipeline_preview_set_audio_sink (pipeline, sink);

    sink = gst_element_factory_make ("fakesink", NULL);
    g_object_set (sink, "sync", TRUE, NULL);
    ges_pipeline_preview_set_video_sink (pipeline, sink);
  }

  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  gst_bus_add_watch (bus, my_bus_callback, &ret);
  gst_object_unref (bus);

  ges_pipeline_set_timeline (pipeline, timeline);
  gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
  gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, -1);
  GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
      GST_DEBUG_GRAPH_SHOW_ALL, "ges-integration-playing");
  if (seeks != NULL)
    g_timeout_add (50, (GSourceFunc) get_position, NULL);

  g_main_loop_run (loop);

  gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
  gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, -1);

  if (current_profile != PROFILE_NONE) {
    check_rendered_file_properties (profile_specs[current_profile][3],
        ges_timeline_get_duration (timeline));
    g_free (render_uri);
  }

  gst_object_unref (pipeline);

  return ret;
}

/* Test seeking in various situations */
static void
run_simple_seeks_test (GESTimeline * timeline)
{
  GList *tmp;
  GESLayer *layer;
  GESUriClipAsset *asset1;

  get_asset (testfilename1, asset1);

  layer = ges_layer_new ();
  fail_unless (ges_timeline_add_layer (timeline, layer));
  ges_layer_add_asset (layer, GES_ASSET (asset1), 0 * GST_SECOND,
      0 * GST_SECOND, 1 * GST_SECOND, GES_TRACK_TYPE_UNKNOWN);

  gst_object_unref (asset1);

  ges_layer_add_asset (layer, GES_ASSET (asset1), 1 * GST_SECOND,
      0 * GST_SECOND, 1 * GST_SECOND, GES_TRACK_TYPE_UNKNOWN);

    /**
   * Our timeline
   *          [   E    ]
   * inpoints 0--------01--------2
   *          |  clip  |   clip  |
   * time     0--------10--------1
   */
  if (!seek_paused_noplay) {
    seeks =
        g_list_append (seeks, new_seek_info (0.2 * GST_SECOND,
            0.6 * GST_SECOND));
    seeks =
        g_list_append (seeks, new_seek_info (1.0 * GST_SECOND,
            1.2 * GST_SECOND));
    seeks =
        g_list_append (seeks, new_seek_info (1.5 * GST_SECOND,
            1.8 * GST_SECOND));
  } else {
    /* if pipeline is not playing, let's make point-to-point seeks */
    seeks =
        g_list_append (seeks, new_seek_info (0.2 * GST_SECOND,
            0.6 * GST_SECOND));
    seeks =
        g_list_append (seeks, new_seek_info (0.6 * GST_SECOND,
            1.2 * GST_SECOND));
    seeks =
        g_list_append (seeks, new_seek_info (1.2 * GST_SECOND,
            1.8 * GST_SECOND));
  }
  fail_unless (check_timeline (timeline));
  if (seeks != NULL) {
    /* free failed seeks */
    while (seeks) {
      SeekInfo *info = seeks->data;

      tmp = seeks;
      GST_ERROR ("Seeking at %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT
          " did not happen", GST_TIME_ARGS (info->seeking_position),
          GST_TIME_ARGS (info->position));
      seeks = g_list_remove_link (seeks, tmp);
      g_slice_free (SeekInfo, info);
      g_list_free (tmp);
    }
    fail_if (TRUE, "Got EOS before being able to execute all seeks");
  }
}

static void
test_seeking_audio (void)
{
  GESTimeline *timeline = ges_timeline_new ();

  fail_unless (ges_timeline_add_track (timeline,
          GES_TRACK (ges_audio_track_new ())));

  seek_paused = FALSE;
  seek_paused_noplay = FALSE;
  run_simple_seeks_test (timeline);
}

static void
test_seeking_video (void)
{
  GESTimeline *timeline = ges_timeline_new ();

  fail_unless (ges_timeline_add_track (timeline,
          GES_TRACK (ges_video_track_new ())));

  seek_paused = FALSE;
  seek_paused_noplay = FALSE;
  run_simple_seeks_test (timeline);
}

static void
test_seeking (void)
{
  GESTimeline *timeline = ges_timeline_new_audio_video ();

  seek_paused = FALSE;
  seek_paused_noplay = FALSE;
  run_simple_seeks_test (timeline);
}

static void
test_seeking_paused_audio (void)
{
  GESTimeline *timeline = ges_timeline_new ();

  fail_unless (ges_timeline_add_track (timeline,
          GES_TRACK (ges_audio_track_new ())));

  seek_paused = TRUE;
  seek_paused_noplay = FALSE;
  run_simple_seeks_test (timeline);
}

static void
test_seeking_paused_video (void)
{
  GESTimeline *timeline = ges_timeline_new ();

  fail_unless (ges_timeline_add_track (timeline,
          GES_TRACK (ges_video_track_new ())));

  seek_paused = TRUE;
  seek_paused_noplay = FALSE;
  run_simple_seeks_test (timeline);
}

static void
test_seeking_paused (void)
{
  GESTimeline *timeline = ges_timeline_new_audio_video ();

  seek_paused = TRUE;
  seek_paused_noplay = FALSE;
  run_simple_seeks_test (timeline);
}

static void
test_seeking_paused_audio_noplay (void)
{
  GESTimeline *timeline = ges_timeline_new ();

  fail_unless (ges_timeline_add_track (timeline,
          GES_TRACK (ges_audio_track_new ())));

  seek_paused = FALSE;
  seek_paused_noplay = TRUE;
  run_simple_seeks_test (timeline);
}

static void
test_seeking_paused_video_noplay (void)
{
  GESTimeline *timeline = ges_timeline_new ();

  fail_unless (ges_timeline_add_track (timeline,
          GES_TRACK (ges_video_track_new ())));

  seek_paused = FALSE;
  seek_paused_noplay = TRUE;
  run_simple_seeks_test (timeline);
}

static void
test_seeking_paused_noplay (void)
{
  GESTimeline *timeline = ges_timeline_new_audio_video ();

  seek_paused = FALSE;
  seek_paused_noplay = TRUE;
  run_simple_seeks_test (timeline);
}

/* Test adding an effect [E] marks the effect */
static void
test_effect (void)
{
  GESTimeline *timeline;
  GESLayer *layer;
  GError *error = NULL;
  GESUriClipAsset *asset1;
  GESEffect *effect;
  GESClip *clip;
  gchar *uri = ges_test_file_name (testfilename1);

  asset1 = ges_uri_clip_asset_request_sync (uri, &error);
  g_free (uri);
  fail_unless (GES_IS_ASSET (asset1), "Testing file %s could not be used as an "
      "asset -- Reason: %s", uri, error ? error->message : "Uknown");

  fail_unless (asset1 != NULL);

  layer = ges_layer_new ();
  timeline = ges_timeline_new_audio_video ();
  fail_unless (ges_timeline_add_layer (timeline, layer));

  clip =
      ges_layer_add_asset (layer, GES_ASSET (asset1), 0 * GST_SECOND,
      0 * GST_SECOND, 1 * GST_SECOND, GES_TRACK_TYPE_UNKNOWN);
  gst_object_unref (asset1);

  effect = ges_effect_new ("agingtv");
  ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (effect));

    /**
   * Our timeline
   *          [   E    ]
   * inpoints 0--------0
   *          |  clip  |
   * time     0--------1
   */

  fail_unless (check_timeline (timeline));
}

static void
test_transition (void)
{
  GESTimeline *timeline;
  GESLayer *layer;
  GESUriClipAsset *asset1, *asset2;
  GESClip *clip;

  timeline = ges_timeline_new_audio_video ();
  layer = ges_layer_new ();
  fail_unless (ges_timeline_add_layer (timeline, layer));

  g_object_set (layer, "auto-transition", TRUE, NULL);

  get_asset (testfilename1, asset1);
  get_asset (testfilename2, asset2);

  fail_unless (asset1 != NULL && asset2 != NULL);

  clip =
      ges_layer_add_asset (layer, GES_ASSET (asset1), 0 * GST_SECOND,
      0 * GST_SECOND, 2 * GST_SECOND, GES_TRACK_TYPE_UNKNOWN);
  gst_object_unref (asset1);

  clip =
      ges_layer_add_asset (layer, GES_ASSET (asset2), 1 * GST_SECOND,
      0 * GST_SECOND, 2 * GST_SECOND, GES_TRACK_TYPE_UNKNOWN);
  gst_object_unref (asset2);

  ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 1 * GST_SECOND);

    /**
   * Our timeline
   *                    [T]
   * inpoints 0--------0 0--------0
   *          |  clip  | |  clip2 |
   * time     0------- 2 1--------3
   */

  fail_unless (check_timeline (timeline));
}

static void
run_basic (GESTimeline * timeline)
{
  GESLayer *layer;
  GESUriClipAsset *asset1;
  GESUriClipAsset *asset2;

  get_asset (testfilename1, asset1);
  get_asset (testfilename2, asset2);
  layer = ges_layer_new ();
  fail_unless (ges_timeline_add_layer (timeline, layer));

  ges_layer_add_asset (layer, GES_ASSET (asset1), 0 * GST_SECOND,
      0 * GST_SECOND, 1 * GST_SECOND, GES_TRACK_TYPE_UNKNOWN);
  gst_object_unref (asset1);
  /* Test most simple case */

  ges_layer_add_asset (layer, GES_ASSET (asset2), 1 * GST_SECOND,
      0 * GST_SECOND, 1 * GST_SECOND, GES_TRACK_TYPE_UNKNOWN);
  gst_object_unref (asset2);

    /**
   * Our timeline
   *
   * inpoints 0--------0 0--------0
   *          |  clip  | |  clip2 |
   * time     0------- 1 1--------2
   */

  fail_unless (check_timeline (timeline));
}

static void
test_basic (void)
{
  run_basic (ges_timeline_new_audio_video ());
}

static void
test_basic_audio (void)
{
  GESTimeline *timeline = ges_timeline_new ();

  fail_unless (ges_timeline_add_track (timeline,
          GES_TRACK (ges_audio_track_new ())));

  run_basic (timeline);
}

static void
test_basic_video (void)
{
  GESTimeline *timeline = ges_timeline_new ();

  fail_unless (ges_timeline_add_track (timeline,
          GES_TRACK (ges_video_track_new ())));

  run_basic (timeline);
}

static void
test_image (void)
{
  GESTimeline *timeline;
  GESLayer *layer;
  GESUriClipAsset *asset1, *asset2;

  get_asset (test_image_filename, asset1);
  get_asset (testfilename1, asset2);

  layer = ges_layer_new ();
  timeline = ges_timeline_new_audio_video ();
  fail_unless (ges_timeline_add_layer (timeline, layer));

  ges_layer_add_asset (layer, GES_ASSET (asset1), 0 * GST_SECOND,
      0 * GST_SECOND, 1 * GST_SECOND, GES_TRACK_TYPE_UNKNOWN);
  gst_object_unref (asset1);
  /* Test most simple case */

  layer = ges_layer_new ();
  fail_unless (ges_timeline_add_layer (timeline, layer));

  ges_layer_add_asset (layer, GES_ASSET (asset2), 0 * GST_SECOND,
      0 * GST_SECOND, 1 * GST_SECOND, GES_TRACK_TYPE_UNKNOWN);
  gst_object_unref (asset2);


    /**
   * Our timeline
   *
   * inpoints 0--------0
   *          |  clip  |
   * time     0--------1
   */

  fail_unless (check_timeline (timeline));
}

static gboolean
test_mix_layers (GESTimeline * timeline, GESUriClipAsset ** assets,
    guint32 num_assets, guint32 num_layers)
{
  GESLayer *layer;
  GESClip *clip;
  GList *tmp;
  GESTrackElement *track_element;
  GESTrackType track_type;
  GESUriClipAsset *asset;
  guint32 i, j;
  gfloat step = 1.0 / num_layers;

  for (i = 0; i < num_layers; i++) {
    layer = ges_timeline_append_layer (timeline);
    fail_unless (layer != NULL);

    for (j = 0; j < num_assets; j++) {
      asset = assets[j];

      clip =
          ges_layer_add_asset (layer, GES_ASSET (asset),
          (i * step + j) * GST_SECOND, 0 * GST_SECOND, 1 * GST_SECOND,
          GES_TRACK_TYPE_UNKNOWN);
      fail_unless (clip != NULL);

      for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
        track_element = GES_TRACK_ELEMENT (tmp->data);
        track_type = ges_track_element_get_track_type (track_element);

        switch (track_type) {
          case GES_TRACK_TYPE_VIDEO:
            ges_track_element_set_child_properties (track_element, "alpha",
                (gdouble) (num_layers - 1 - i) * step, NULL);
            break;
          case GES_TRACK_TYPE_AUDIO:
            ges_track_element_set_child_properties (track_element, "volume",
                (gdouble) (num_layers - 1 - i) * step, NULL);
            break;
          default:
            break;
        }
      }
    }
  }
  return TRUE;
}


static void
test_mixing (void)
{
  GESTimeline *timeline;
  GESUriClipAsset *asset[2];
  GError *error = NULL;

  gchar *uri1 = ges_test_file_name (testfilename1);
  gchar *uri2 = ges_test_file_name (testfilename1);

  timeline = ges_timeline_new_audio_video ();

  asset[0] = ges_uri_clip_asset_request_sync (uri1, &error);
  asset[1] = ges_uri_clip_asset_request_sync (uri2, &error);

  g_free (uri1);
  g_free (uri2);

  /* we are only using the first asset / clip for now */
  fail_unless (test_mix_layers (timeline, asset, 1, 2));

    /**
   * Our timeline has 4 layers
   *
   * inpoints 0--------0
   *          |  clip  |
   * time     0--------1
   * inpoints    0--------0
   *             |  clip  |
   * time        0.25--1.25
   * inpoints       0--------0
   *                |  clip  |
   * time           0.5----1.5
   * inpoints          0--------0
   *                   |  clip  |
   * time              0.75--1.75
   */

  fail_unless (check_timeline (timeline));

}

static void
test_title (void)
{
  GESTitleClip *title;
  GESAsset *asset[2];
  GESTimeline *timeline;
  GESLayer *layer, *layer1;

  GError *error = NULL;
  gchar *uri1 = ges_test_file_name (testfilename1);

  timeline = ges_timeline_new_audio_video ();
  layer = ges_timeline_append_layer (timeline);
  layer1 = ges_timeline_append_layer (timeline);
  asset[0] = ges_asset_request (GES_TYPE_TITLE_CLIP, NULL, &error);
  asset[1] = GES_ASSET (ges_uri_clip_asset_request_sync (uri1, &error));
  g_free (uri1);

  /**
   * Our timeline
   *
   * inpoints 0--------0
   *          |  Title |
   * time     0------- 1
   * inpoints          0--------0
   *                   |  clip  |
   * time              0------- 1
   */
  title =
      GES_TITLE_CLIP (ges_layer_add_asset (layer, asset[0], 0, 0,
          1 * GST_SECOND, GES_TRACK_TYPE_UNKNOWN));
  ges_title_clip_set_text (title, "This is a title test");
  ges_layer_add_asset (layer1, asset[1], 1 * GST_SECOND, 0, 1 * GST_SECOND,
      GES_TRACK_TYPE_UNKNOWN);

  fail_unless (check_timeline (timeline));
}


#define CREATE_TEST(name, func, profile)                                       \
GST_START_TEST (test_##name##_raw_h264_mov)                                    \
{                                                                              \
  g_print("running test_%s_%s\n", #name, "raw_h264_mov");                      \
  testfilename1 = "assets/raw_h264.0.mov";                                     \
  testfilename2 = "assets/raw_h264.1.mov";                                     \
  test_image_filename = "assets/png.png";                                      \
  current_profile = profile;                                                   \
  func ();                                                                     \
}                                                                              \
GST_END_TEST;                                                                  \
GST_START_TEST (test_##name##_vorbis_theora_ogv)                               \
{                                                                              \
  g_print("running test_%s_%s\n", #name, "vorbis_theora_ogv");                 \
  testfilename1 = "assets/vorbis_theora.0.ogg";                                \
  testfilename2 = "assets/vorbis_theora.1.ogg";                                \
  test_image_filename = "assets/png.png";                                      \
  current_profile = profile;                                                   \
  func ();                                                                     \
}                                                                              \
GST_END_TEST;                                                                  \
GST_START_TEST (test_##name##_vorbis_vp8_webm)                                 \
{                                                                              \
  g_print("running test_%s_%s\n", #name, "vorbis_vp8_webm");                   \
  testfilename1 = "assets/vorbis_vp8.0.webm";                                  \
  testfilename2 = "assets/vorbis_vp8.1.webm";                                  \
  test_image_filename = "assets/png.png";                                      \
  current_profile = profile;                                                   \
  func ();                                                                     \
}                                                                              \
GST_END_TEST;                                                                  \
GST_START_TEST (test_##name##_mp3_h264_mov)                                    \
{                                                                              \
  g_print("running test_%s_%s\n", #name, "mp3_h264_mov");                      \
  testfilename1 = "assets/mp3_h264.0.mov";                                     \
  testfilename2 = "assets/mp3_h264.1.mov";                                     \
  test_image_filename = "assets/png.png";                                      \
  current_profile = profile;                                                   \
  func ();                                                                     \
}                                                                              \
GST_END_TEST;

#define CREATE_TEST_FROM_NAMES(name, to, profile)                              \
  CREATE_TEST( name##to, test_##name, profile)

#define CREATE_RENDERING_TEST(name, func)                                      \
  CREATE_TEST_FROM_NAMES(name, _render_to_vorbis_theora_ogg, PROFILE_VORBIS_THEORA_OGG)   \
  CREATE_TEST_FROM_NAMES(name, _render_to_vorbis_vp8_webm, PROFILE_VORBIS_VP8_WEBM)       \
  CREATE_TEST_FROM_NAMES(name, _render_to_aac_h264_quicktime, PROFILE_AAC_H264_QUICKTIME) \
  CREATE_TEST_FROM_NAMES(name, _render_to_vorbis_h264_matroska, PROFILE_VORBIS_H264_MATROSKA)

#define CREATE_PLAYBACK_TEST(name)                                             \
  CREATE_TEST_FROM_NAMES(name, _playback, PROFILE_NONE)

#define CREATE_TEST_FULL(name)                                                 \
  CREATE_PLAYBACK_TEST(name)                                                   \
  CREATE_RENDERING_TEST(name, func)

#define ADD_PLAYBACK_TESTS(name)                                               \
  tcase_add_test (tc_chain, test_##name##_playback_vorbis_vp8_webm);           \
  tcase_add_test (tc_chain, test_##name##_playback_vorbis_theora_ogv);         \
  tcase_add_test (tc_chain, test_##name##_playback_raw_h264_mov);              \
  tcase_add_test (tc_chain, test_##name##_playback_mp3_h264_mov);              \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
        "_playback_mp3_h264_mov"));                                             \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
        "_playback_raw_h264_mov"));                                             \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
        "_playback_vorbis_theora_ogv"));                                        \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
        "_playback_vorbis_vp8_webm"));

#define ADD_RENDERING_TESTS(name)                                              \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_theora_ogg_raw_h264_mov);      \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_theora_ogg_mp3_h264_mov);      \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_theora_ogg_vorbis_vp8_webm);   \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_theora_ogg_vorbis_theora_ogv); \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_vp8_webm_vorbis_vp8_webm);     \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_vp8_webm_raw_h264_mov);        \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_vp8_webm_mp3_h264_mov);        \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_vp8_webm_vorbis_theora_ogv);   \
  tcase_add_test (tc_chain, test_##name##_render_to_aac_h264_quicktime_raw_h264_mov);      \
  tcase_add_test (tc_chain, test_##name##_render_to_aac_h264_quicktime_vorbis_theora_ogv);   \
  tcase_add_test (tc_chain, test_##name##_render_to_aac_h264_quicktime_vorbis_vp8_webm);   \
  tcase_add_test (tc_chain, test_##name##_render_to_aac_h264_quicktime_mp3_h264_mov); \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_h264_matroska_raw_h264_mov);      \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_h264_matroska_vorbis_theora_ogv);   \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_h264_matroska_vorbis_vp8_webm);   \
  tcase_add_test (tc_chain, test_##name##_render_to_vorbis_h264_matroska_mp3_h264_mov); \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_",            \
      #name, "_render_to_vorbis_theora_ogg_raw_h264_mov"));                           \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_vorbis_theora_ogg_mp3_h264_mov"));                            \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_vorbis_theora_ogg_vorbis_vp8_webm"));                         \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_vorbis_theora_ogg_vorbis_theora_ogv"));                       \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_vorbis_vp8_webm_vorbis_vp8_webm"));                           \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_vorbis_vp8_webm_raw_h264_mov"));                              \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_vorbis_vp8_webm_mp3_h264_mov"));                              \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_vorbis_vp8_webm_vorbis_theora_ogv"));                         \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_aac_h264_quicktime_raw_h264_mov"));                           \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_aac_h264_quicktime_vorbis_theora_ogv"));                      \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_aac_h264_quicktime_vorbis_vp8_webm"));                        \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_aac_h264_quicktime_mp3_h264_mov"));                           \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_vorbis_h264_matroska_raw_h264_mov"));                         \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_vorbis_h264_matroska_vorbis_theora_ogv"));                    \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_vorbis_h264_matroska_vorbis_vp8_webm"));                      \
  tests_names = g_list_prepend (tests_names, g_strdup_printf ("%s%s%s", "test_", #name,     \
      "_render_to_vorbis_h264_matroska_mp3_h264_mov"));

#define ADD_TESTS(name)                                                        \
  ADD_PLAYBACK_TESTS(name)                                                     \
  ADD_RENDERING_TESTS(name)


/* *INDENT-OFF* */
CREATE_TEST_FULL(basic)
CREATE_TEST_FULL(basic_audio)
CREATE_TEST_FULL(basic_video)
CREATE_TEST_FULL(transition)
CREATE_TEST_FULL(effect)
CREATE_TEST_FULL(mixing)
CREATE_TEST_FULL(title)

CREATE_PLAYBACK_TEST(seeking)
CREATE_PLAYBACK_TEST(seeking_audio)
CREATE_PLAYBACK_TEST(seeking_video)
CREATE_PLAYBACK_TEST(seeking_paused)
CREATE_PLAYBACK_TEST(seeking_paused_audio)
CREATE_PLAYBACK_TEST(seeking_paused_video)
CREATE_PLAYBACK_TEST(seeking_paused_noplay)
CREATE_PLAYBACK_TEST(seeking_paused_audio_noplay)
CREATE_PLAYBACK_TEST(seeking_paused_video_noplay)
CREATE_PLAYBACK_TEST(image)
/* *INDENT-ON* */

static Suite *
ges_suite (void)
{
  Suite *s = suite_create ("ges-integration");
  TCase *tc_chain = tcase_create ("integration");

  suite_add_tcase (s, tc_chain);

  ADD_TESTS (basic);
  ADD_TESTS (basic_audio);
  ADD_TESTS (basic_video);

  ADD_TESTS (effect);
  ADD_TESTS (transition);

  ADD_TESTS (mixing);

  ADD_TESTS (title);

  ADD_PLAYBACK_TESTS (image);

  ADD_PLAYBACK_TESTS (seeking);
  ADD_PLAYBACK_TESTS (seeking_audio);
  ADD_PLAYBACK_TESTS (seeking_video);

  ADD_PLAYBACK_TESTS (seeking_paused);
  ADD_PLAYBACK_TESTS (seeking_paused_audio);
  ADD_PLAYBACK_TESTS (seeking_paused_video);

  ADD_PLAYBACK_TESTS (seeking_paused_noplay);
  ADD_PLAYBACK_TESTS (seeking_paused_audio_noplay);
  ADD_PLAYBACK_TESTS (seeking_paused_video_noplay);

  /* TODO : next test case : complex timeline created from project. */
  /* TODO : deep checking of rendered clips */
  /* TODO : might be interesting to try all profiles, and maintain a list of currently working profiles ? */

  return s;
}

static gboolean
generate_all_files (void)
{
  if (!ges_generate_test_file_audio_video ("assets/vorbis_vp8.0.webm",
          "vorbisenc", "vp8enc", "webmmux", "18", "11"))
    return FALSE;
  if (!ges_generate_test_file_audio_video ("assets/vorbis_vp8.1.webm",
          "vorbisenc", "vp8enc", "webmmux", "0", "0"))
    return FALSE;
  if (!ges_generate_test_file_audio_video ("assets/vorbis_theora.0.ogg",
          "vorbisenc", "theoraenc", "oggmux", "18", "11"))
    return FALSE;
  if (!ges_generate_test_file_audio_video ("assets/vorbis_theora.1.ogg",
          "vorbisenc", "theoraenc", "oggmux", "0", "0"))
    return FALSE;
  if (!ges_generate_test_file_audio_video ("assets/raw_h264.0.mov", NULL,
          "x264enc", "qtmux", "18", "11"))
    return FALSE;
  if (!ges_generate_test_file_audio_video ("assets/raw_h264.1.mov", NULL,
          "x264enc", "qtmux", "0", "0"))
    return FALSE;
  if (!ges_generate_test_file_audio_video ("assets/mp3_h264.0.mov",
          "lamemp3enc", "x264enc", "qtmux", "18", "11"))
    return FALSE;
  if (!ges_generate_test_file_audio_video ("assets/mp3_h264.1.mov",
          "lamemp3enc", "x264enc", "qtmux", "0", "0"))
    return FALSE;

  return TRUE;
}


int
main (int argc, char **argv)
{
  int nf;
  GOptionContext *ctx;

  GError *err = NULL;
  gboolean list_tests = FALSE, list_tests_only = FALSE;
  Suite *s = ges_suite ();

  GOptionEntry options[] = {
    {"list-tests", 'l', 0.0, G_OPTION_ARG_NONE, &list_tests,
        "List all avalaible tests", "N"},
    {"list-tests-only", 'o', 0.0, G_OPTION_ARG_NONE, &list_tests_only,
        "List all avalaible tests", "N"},
    {NULL}
  };

  ctx = g_option_context_new ("Run integration tests");
  g_option_context_add_main_entries (ctx, options, NULL);
  g_option_context_add_group (ctx, gst_init_get_option_group ());

  if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
    g_printerr ("Error initializing: %s\n", err->message);
    g_option_context_free (ctx);
    exit (1);
  }

  if (list_tests || list_tests_only) {
    GList *tmp;

    g_print ("=== Listing tests %s === \n", list_tests_only ? "only" : "");
    for (tmp = tests_names; tmp; tmp = tmp->next)
      g_print ("%s\n", (gchar *) tmp->data);
    g_print ("=== Listed tests ===\n");

    if (list_tests_only == TRUE) {
      g_list_free_full (tests_names, g_free);
      return 0;
    }
  }

  gst_check_init (&argc, &argv);
  ges_init ();

  if (!generate_all_files ()) {
    GST_ERROR ("error generating necessary test files in rendering test\n");
    return 1;
  }


  loop = g_main_loop_new (NULL, FALSE);
  nf = gst_check_run_suite (s, "ges", __FILE__);

  g_list_free_full (tests_names, g_free);

  return nf;
}
