/* GStreamer
 * Copyright (C) 2008 Nokia Corporation. (contact <stefan.kost@nokia.com>)
 * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
 *
 * 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 <gst/check/gstcheck.h>

static GstTagList *received_tags = NULL;

static gboolean
bus_handler (GstBus * bus, GstMessage * message, gpointer data)
{
  GMainLoop *loop = (GMainLoop *) data;

  switch (message->type) {
    case GST_MESSAGE_EOS:
      g_main_loop_quit (loop);
      break;
    case GST_MESSAGE_WARNING:
    case GST_MESSAGE_ERROR:{
      GError *gerror;

      gchar *debug;

      gst_message_parse_error (message, &gerror, &debug);
      gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug);
      g_error_free (gerror);
      g_free (debug);
      g_main_loop_quit (loop);
      break;
    }
    case GST_MESSAGE_TAG:{
      if (received_tags == NULL) {
        gst_message_parse_tag (message, &received_tags);
      } else {
        GstTagList *tl = NULL, *ntl = NULL;

        gst_message_parse_tag (message, &tl);
        if (tl) {
          ntl = gst_tag_list_merge (received_tags, tl, GST_TAG_MERGE_PREPEND);
          if (ntl) {
            GST_LOG ("taglists merged: %" GST_PTR_FORMAT, ntl);
            gst_tag_list_unref (received_tags);
            received_tags = ntl;
          }
          gst_tag_list_unref (tl);
        }
      }
      break;
    }
    default:
      break;
  }

  return TRUE;
}

/*
 * Creates a pipeline in the form:
 * fakesrc num-buffers=1 ! caps ! muxer ! filesink location=file
 *
 * And sets the tags in tag_str into the muxer via tagsetter.
 */
static void
test_mux_tags (const gchar * tag_str, const gchar * caps,
    const gchar * muxer, const gchar * file)
{
  GstElement *pipeline;
  GstBus *bus;
  GMainLoop *loop;
  GstTagList *sent_tags;
  GstElement *mux;
  GstTagSetter *setter;
  gchar *launch_str;
  guint bus_watch = 0;

  GST_DEBUG ("testing xmp muxing on : %s", muxer);

  launch_str = g_strdup_printf ("fakesrc num-buffers=1 format=time ! "
      "%s ! %s name=mux ! filesink location=%s name=sink", caps, muxer, file);
  pipeline = gst_parse_launch (launch_str, NULL);
  g_free (launch_str);
  fail_unless (pipeline != NULL);

  mux = gst_bin_get_by_name (GST_BIN (pipeline), "mux");
  fail_unless (mux != NULL);

  loop = g_main_loop_new (NULL, TRUE);
  fail_unless (loop != NULL);

  bus = gst_element_get_bus (pipeline);
  fail_unless (bus != NULL);
  bus_watch = gst_bus_add_watch (bus, bus_handler, loop);
  gst_object_unref (bus);

  gst_element_set_state (pipeline, GST_STATE_READY);

  setter = GST_TAG_SETTER (mux);
  fail_unless (setter != NULL);
  sent_tags = gst_tag_list_new_from_string (tag_str);
  fail_unless (sent_tags != NULL);
  gst_tag_setter_merge_tags (setter, sent_tags, GST_TAG_MERGE_REPLACE);
  gst_tag_list_unref (sent_tags);

  gst_element_set_state (pipeline, GST_STATE_PLAYING);
  g_main_loop_run (loop);

  gst_element_set_state (pipeline, GST_STATE_NULL);

  g_main_loop_unref (loop);
  g_object_unref (mux);
  g_object_unref (pipeline);
  g_source_remove (bus_watch);
}

/*
 * Makes a pipeline in the form:
 * filesrc location=file ! demuxer ! fakesink
 *
 * And gets the tags that are posted on the bus to compare
 * with the tags in 'tag_str'
 */
static void
test_demux_tags (const gchar * tag_str, const gchar * demuxer,
    const gchar * file)
{
  GstElement *pipeline;
  GstBus *bus;
  GMainLoop *loop;
  GstTagList *sent_tags;
  gint i, j, k, n_recv, n_sent;
  const gchar *name_sent, *name_recv;
  const GValue *value_sent, *value_recv;
  gboolean found;
  gint comparison;
  GstElement *demux;
  gchar *launch_str;
  guint bus_watch = 0;

  GST_DEBUG ("testing tags : %s", tag_str);

  if (received_tags) {
    gst_tag_list_unref (received_tags);
    received_tags = NULL;
  }

  launch_str = g_strdup_printf ("filesrc location=%s ! %s name=demux ! "
      "fakesink", file, demuxer);
  pipeline = gst_parse_launch (launch_str, NULL);
  g_free (launch_str);
  fail_unless (pipeline != NULL);

  demux = gst_bin_get_by_name (GST_BIN (pipeline), "demux");
  fail_unless (demux != NULL);

  loop = g_main_loop_new (NULL, TRUE);
  fail_unless (loop != NULL);

  bus = gst_element_get_bus (pipeline);
  fail_unless (bus != NULL);
  bus_watch = gst_bus_add_watch (bus, bus_handler, loop);
  gst_object_unref (bus);

  sent_tags = gst_tag_list_new_from_string (tag_str);
  fail_unless (sent_tags != NULL);

  gst_element_set_state (pipeline, GST_STATE_PLAYING);
  g_main_loop_run (loop);

  GST_DEBUG ("mainloop done : %p", received_tags);

  /* verify tags */
  fail_unless (received_tags != NULL);
  n_recv = gst_tag_list_n_tags (received_tags);
  n_sent = gst_tag_list_n_tags (sent_tags);
  fail_unless (n_recv >= n_sent);
  /* FIXME: compare taglits values */
  for (i = 0; i < n_sent; i++) {
    name_sent = gst_tag_list_nth_tag_name (sent_tags, i);

    found = FALSE;
    for (j = 0; j < n_recv; j++) {
      name_recv = gst_tag_list_nth_tag_name (received_tags, j);

      if (!strcmp (name_sent, name_recv)) {
        guint sent_len, recv_len;

        sent_len = gst_tag_list_get_tag_size (sent_tags, name_sent);
        recv_len = gst_tag_list_get_tag_size (received_tags, name_recv);

        fail_unless (sent_len == recv_len,
            "tag item %s has been received with different size", name_sent);

        for (k = 0; k < sent_len; k++) {
          value_sent = gst_tag_list_get_value_index (sent_tags, name_sent, k);
          value_recv =
              gst_tag_list_get_value_index (received_tags, name_recv, k);

          comparison = gst_value_compare (value_sent, value_recv);
          if (comparison != GST_VALUE_EQUAL) {
            gchar *vs = g_strdup_value_contents (value_sent);
            gchar *vr = g_strdup_value_contents (value_recv);
            GST_DEBUG ("sent = %s:'%s', recv = %s:'%s'",
                G_VALUE_TYPE_NAME (value_sent), vs,
                G_VALUE_TYPE_NAME (value_recv), vr);
            g_free (vs);
            g_free (vr);
          }
          fail_unless (comparison == GST_VALUE_EQUAL,
              "tag item %s has been received with different type or value",
              name_sent);
          found = TRUE;
          break;
        }
      }
    }
    fail_unless (found, "tag item %s is lost", name_sent);
  }

  gst_tag_list_unref (received_tags);
  received_tags = NULL;
  gst_tag_list_unref (sent_tags);

  gst_element_set_state (pipeline, GST_STATE_NULL);

  g_main_loop_unref (loop);
  g_object_unref (demux);
  g_object_unref (pipeline);
  g_source_remove (bus_watch);
}

/*
 * Tests if the muxer/demuxer pair can serialize the tags in 'tag_str'
 * to a file and recover them correctly.
 *
 * 'caps' are used to assure the muxer accepts the fake buffer fakesrc
 * will send to it.
 */
static void
test_tags (const gchar * tag_str, const gchar * caps, const gchar * muxer,
    const gchar * demuxer)
{
  gchar *tmpfile;
  gchar *tmp;

  tmp = g_strdup_printf ("%s%d", "gst-check-xmp-test-", g_random_int ());
  tmpfile = g_build_filename (g_get_tmp_dir (), tmp, NULL);
  g_free (tmp);

  GST_DEBUG ("testing tags : %s", tag_str);
  test_mux_tags (tag_str, caps, muxer, tmpfile);
  test_demux_tags (tag_str, demuxer, tmpfile);
  g_free (tmpfile);
}

#define H264_CAPS "video/x-h264, width=(int)320, height=(int)240," \
                  " framerate=(fraction)30/1, codec_data=(buffer)" \
                  "01401592ffe10017674d401592540a0fd8088000000300" \
                  "8000001e478b175001000468ee3c80, "\
                  "stream-format=(string)avc, alignment=(string)au"

#define COMMON_TAGS \
    "taglist,title=test_title,"    \
    "artist=test_artist,"          \
    "keywords=\"key1,key2\","      \
    "description=test_desc"

GST_START_TEST (test_common_tags)
{
  if (!gst_registry_check_feature_version (gst_registry_get (), "qtdemux", 0,
          10, 23)) {
    GST_INFO ("Skipping test, qtdemux either not available or too old");
    return;
  }
  test_tags (COMMON_TAGS, H264_CAPS, "qtmux", "qtdemux");
  test_tags (COMMON_TAGS, H264_CAPS, "mp4mux", "qtdemux");
  test_tags (COMMON_TAGS, H264_CAPS, "3gppmux", "qtdemux");
}

GST_END_TEST;

#define GEO_LOCATION_TAGS \
    "taglist,geo-location-country=Brazil,"    \
      "geo-location-city=\"Campina Grande\"," \
      "geo-location-sublocation=Bodocongo,"   \
      "geo-location-latitude=-12.125,"        \
      "geo-location-longitude=56.75,"         \
      "geo-location-elevation=327.5"

GST_START_TEST (test_geo_location_tags)
{
  if (!gst_registry_check_feature_version (gst_registry_get (), "qtdemux", 0,
          10, 23)) {
    GST_INFO ("Skipping test, qtdemux either not available or too old");
    return;
  }
  test_tags (GEO_LOCATION_TAGS, H264_CAPS, "qtmux", "qtdemux");
  test_tags (GEO_LOCATION_TAGS, H264_CAPS, "mp4mux", "qtdemux");
  test_tags (GEO_LOCATION_TAGS, H264_CAPS, "3gppmux", "qtdemux");
}

GST_END_TEST;


#define USER_TAGS \
    "taglist,user-rating=(uint)85"

GST_START_TEST (test_user_tags)
{
  if (!gst_registry_check_feature_version (gst_registry_get (), "qtdemux", 0,
          10, 23)) {
    GST_INFO ("Skipping test, qtdemux either not available or too old");
    return;
  }

  test_tags (USER_TAGS, H264_CAPS, "qtmux", "qtdemux");
  test_tags (USER_TAGS, H264_CAPS, "mp4mux", "qtdemux");
  test_tags (USER_TAGS, H264_CAPS, "3gppmux", "qtdemux");
}

GST_END_TEST;

static Suite *
metadata_suite (void)
{
  Suite *s = suite_create ("tagschecking");

  TCase *tc_chain = tcase_create ("general");

  /* time out after 60s, not the default 3 */
  tcase_set_timeout (tc_chain, 60);

  suite_add_tcase (s, tc_chain);
  tcase_add_test (tc_chain, test_common_tags);
  tcase_add_test (tc_chain, test_geo_location_tags);
  tcase_add_test (tc_chain, test_user_tags);

  return s;
}

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

  Suite *s = metadata_suite ();

  SRunner *sr = srunner_create (s);

  gst_check_init (&argc, &argv);

  srunner_run_all (sr, CK_NORMAL);
  nf = srunner_ntests_failed (sr);
  srunner_free (sr);

  return nf;
}
