/*
 * This file is part of the Nice GLib ICE library.
 *
 * (C) 2015 Kurento.
 *  Contact: Jose Antonio Santos Cadenas
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Nice GLib ICE library.
 *
 * The Initial Developers of the Original Code are Collabora Ltd and Nokia
 * Corporation. All Rights Reserved.
 *
 * Contributors:
 *   Jose Antonio Santos Cadenas, Kurento.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
 * case the provisions of LGPL are applicable instead of those above. If you
 * wish to allow use of your version of this file only under the terms of the
 * LGPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replace
 * them with the notice and other provisions required by the LGPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the LGPL.
 */

#include <gst/check/gstcheck.h>
#include "agent.h"

#define RTP_HEADER_SIZE 12
#define RTP_PAYLOAD_SIZE 1024

static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS_ANY);

static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS_ANY);

GMainLoop *loop;
static gint ready = 0;


static GCond cond;
static guint bytes_received;
static guint data_size;


static gboolean
count_bytes (GstBuffer ** buffer, guint idx, gpointer data)
{
  gsize size = gst_buffer_get_size (*buffer);

  g_debug ("received %" G_GSIZE_FORMAT " bytes", size);
  g_mutex_lock(&mutex);
  bytes_received += size;
  g_cond_signal (&cond);
  g_mutex_unlock (&mutex);

  return TRUE;
}

static GstFlowReturn
sink_chain_list_function (GstPad * pad, GstObject * parent,
    GstBufferList * list)
{
  gst_buffer_list_foreach (list, count_bytes, NULL);

  gst_buffer_list_unref (list);

  return GST_FLOW_OK;
}

static GstFlowReturn
sink_chain_function (GstPad * pad, GstObject * parent, GstBuffer * buffer)
{
  gsize size = gst_buffer_get_size (buffer);

  g_debug ("received %" G_GSIZE_FORMAT " bytes", size);
  g_mutex_lock(&mutex);
  bytes_received += size;
  g_cond_signal (&cond);
  g_mutex_unlock (&mutex);

  gst_buffer_unref (buffer);

  return GST_FLOW_OK;
}

/*
 * This function is get from gst-plugins-good tests tests/check/elements/udpsink.c
 */
static GstBufferList *
create_buffer_list (void)
{
  GstBufferList *list;
  GstBuffer *rtp_buffer;
  GstBuffer *data_buffer;

  list = gst_buffer_list_new ();

  /*** First group, i.e. first packet. **/

  /* Create the RTP header buffer */
  rtp_buffer = gst_buffer_new_allocate (NULL, RTP_HEADER_SIZE, NULL);
  gst_buffer_memset (rtp_buffer, 0, 0, RTP_HEADER_SIZE);

  /* Create the buffer that holds the payload */
  data_buffer = gst_buffer_new_allocate (NULL, RTP_PAYLOAD_SIZE, NULL);
  gst_buffer_memset (data_buffer, 0, 0, RTP_PAYLOAD_SIZE);

  /* Create a new group to hold the rtp header and the payload */
  gst_buffer_list_add (list, gst_buffer_append (rtp_buffer, data_buffer));

  /***  Second group, i.e. second packet. ***/

  /* Create the RTP header buffer */
  rtp_buffer = gst_buffer_new_allocate (NULL, RTP_HEADER_SIZE, NULL);
  gst_buffer_memset (rtp_buffer, 0, 0, RTP_HEADER_SIZE);

  /* Create the buffer that holds the payload */
  data_buffer = gst_buffer_new_allocate (NULL, RTP_PAYLOAD_SIZE, NULL);
  gst_buffer_memset (data_buffer, 0, 0, RTP_PAYLOAD_SIZE);

  /* Create a new group to hold the rtp header and the payload */
  gst_buffer_list_add (list, gst_buffer_append (rtp_buffer, data_buffer));

  /* Calculate the size of the data */
  data_size = 2 * RTP_HEADER_SIZE + 2 * RTP_PAYLOAD_SIZE;

  return list;
}

static void
recv_cb (NiceAgent * agent,
    guint stream_id, guint component_id, guint len, gchar * buf, gpointer data)
{
  g_debug ("Received data on agent %p, stream: %d, compoment: %d", agent,
      stream_id, component_id);
}

static void
print_candidate (gpointer data, gpointer user_data)
{
  NiceCandidate *cand = data;
  gchar str_addr[INET6_ADDRSTRLEN];

  nice_address_to_string (&cand->addr, str_addr);
  g_debug ("Candidate: %s:%d", str_addr, nice_address_get_port (&cand->addr));
}

static void
cb_candidate_gathering_done (NiceAgent * agent, guint stream_id, gpointer data)
{
  GSList *candidates;

  g_debug ("Candidates gathered on agent %p, stream: %d",
      agent, stream_id);

  candidates = nice_agent_get_local_candidates (agent, stream_id, 1);

  nice_agent_set_remote_candidates (NICE_AGENT (data), stream_id, 1,
      candidates);

  g_debug ("Got %d candidates", g_slist_length (candidates));
  g_slist_foreach (candidates, print_candidate, NULL);

  g_slist_free_full (candidates, (GDestroyNotify) nice_candidate_free);
}

static void
credentials_negotiation (NiceAgent * a_agent, NiceAgent * b_agent,
    guint a_stream, guint b_stream)
{
  gchar *user = NULL;
  gchar *passwd = NULL;

  nice_agent_get_local_credentials (a_agent, a_stream, &user, &passwd);
  nice_agent_set_remote_credentials (b_agent, b_stream, user, passwd);
  g_debug ("Agent: %p User: %s", a_agent, user);
  g_debug ("Agent: %p Passwd: %s", a_agent, passwd);

  g_free (user);
  g_free (passwd);
}

static void
cb_component_state_changed (NiceAgent * agent, guint stream_id,
    guint component_id, guint state, gpointer user_data)
{
  g_debug ("State changed: %p to %s", agent,
      nice_component_state_to_string (state));

  if (state == NICE_COMPONENT_STATE_READY) {
    ready++;
    if (ready >= 2) {
      g_main_loop_quit (loop);
    }
  }
}

GST_START_TEST (buffer_list_test)
{
  GstSegment segment;
  GstElement *nicesink, *nicesrc;
  GstPad *srcpad, *sinkpad;
  GstBufferList *list;
  NiceAgent *sink_agent, *src_agent;
  guint sink_stream, src_stream;
  NiceAddress *addr;

  loop = g_main_loop_new (NULL, TRUE);

  /* Initialize nice agents */
  addr = nice_address_new ();
  nice_address_set_from_string (addr, "127.0.0.1");

  sink_agent = nice_agent_new (NULL, NICE_COMPATIBILITY_RFC5245);
  src_agent = nice_agent_new (NULL, NICE_COMPATIBILITY_RFC5245);

  g_object_set (G_OBJECT (sink_agent), "upnp", FALSE, NULL);
  g_object_set (G_OBJECT (src_agent), "upnp", FALSE, NULL);

  nice_agent_add_local_address (sink_agent, addr);
  nice_agent_add_local_address (src_agent, addr);

  sink_stream = nice_agent_add_stream (sink_agent, 1);
  src_stream = nice_agent_add_stream (src_agent, 1);

  nice_agent_attach_recv (sink_agent, sink_stream, NICE_COMPONENT_TYPE_RTP,
      NULL, recv_cb, NULL);
  nice_agent_attach_recv (src_agent, src_stream, NICE_COMPONENT_TYPE_RTP,
      NULL, recv_cb, NULL);

  g_signal_connect (G_OBJECT (sink_agent), "candidate-gathering-done",
      G_CALLBACK (cb_candidate_gathering_done), src_agent);
  g_signal_connect (G_OBJECT (src_agent), "candidate-gathering-done",
      G_CALLBACK (cb_candidate_gathering_done), sink_agent);

  g_signal_connect (G_OBJECT (sink_agent), "component-state-changed",
      G_CALLBACK (cb_component_state_changed), NULL);
  g_signal_connect (G_OBJECT (src_agent), "component-state-changed",
      G_CALLBACK (cb_component_state_changed), NULL);

  credentials_negotiation (sink_agent, src_agent, sink_stream, src_stream);
  credentials_negotiation (src_agent, sink_agent, src_stream, src_stream);

  nice_agent_gather_candidates (sink_agent, sink_stream);
  nice_agent_gather_candidates (src_agent, src_stream);

  /* Create gstreamer elements */
  nicesink = gst_check_setup_element ("nicesink");
  nicesrc = gst_check_setup_element ("nicesrc");

  g_object_set (nicesink, "agent", sink_agent, "stream", sink_stream,
      "component", 1, NULL);
  g_object_set (nicesrc, "agent", src_agent, "stream", src_stream, "component",
      1, NULL);

  srcpad = gst_check_setup_src_pad_by_name (nicesink, &srctemplate, "sink");
  sinkpad = gst_check_setup_sink_pad_by_name (nicesrc, &sinktemplate, "src");

  gst_pad_set_chain_list_function_full (sinkpad, sink_chain_list_function, NULL,
      NULL);
  gst_pad_set_chain_function_full (sinkpad, sink_chain_function, NULL, NULL);

  gst_element_set_state (nicesink, GST_STATE_PLAYING);
  gst_pad_set_active (srcpad, TRUE);

  gst_element_set_state (nicesrc, GST_STATE_PLAYING);
  gst_pad_set_active (sinkpad, TRUE);

  gst_pad_push_event (srcpad, gst_event_new_stream_start ("test"));

  gst_segment_init (&segment, GST_FORMAT_TIME);
  gst_pad_push_event (srcpad, gst_event_new_segment (&segment));

  list = create_buffer_list ();

  g_debug ("Waiting for agents to be ready ready");

  g_main_loop_run (loop);

  fail_unless_equals_int (gst_pad_push_list (srcpad, list), GST_FLOW_OK);

  g_debug ("Waiting for buffers");

  g_mutex_lock (&mutex);
  while (bytes_received < data_size) {
    g_cond_wait (&cond, &mutex);
  }
  g_mutex_unlock (&mutex);

  g_assert_cmpuint (bytes_received, ==, data_size);
  g_debug ("We received expected data size");

  fail_unless_equals_int (data_size, bytes_received);

  gst_check_teardown_pad_by_name (nicesink, "sink");
  gst_check_teardown_element (nicesink);

  gst_check_teardown_pad_by_name (nicesrc, "src");
  gst_check_teardown_element (nicesrc);

  nice_address_free (addr);
  g_main_loop_unref (loop);
}

GST_END_TEST;

static Suite *
udpsink_suite (void)
{
  Suite *s = suite_create ("nice_gstreamer_test");
  TCase *tc_chain = tcase_create ("nice");

  suite_add_tcase (s, tc_chain);

  tcase_add_test (tc_chain, buffer_list_test);

  return s;
}

GST_CHECK_MAIN (udpsink)
