/*
 * GStreamer AVTP Plugin
 * Copyright (C) 2019 Intel Corporation
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 */

/**
 * SECTION:element-avtpaafdepay
 * @see_also: avtpaafpay
 *
 * Extract raw audio from AVTPDUs according to IEEE 1722-2016. For detailed
 * information see https://standards.ieee.org/standard/1722-2016.html.
 *
 * <refsect2>
 * <title>Example pipeline</title>
 * |[
 * gst-launch-1.0 avtpsrc ! avtpaafdepay ! autoaudiosink
 * ]| This example pipeline will depayload AVTPDUs. Refer to the avtpaafpay
 * example to create the AVTP stream.
 * </refsect2>
 */

#include <avtp.h>
#include <avtp_aaf.h>
#include <gst/audio/audio-format.h>

#include "gstavtpaafdepay.h"

GST_DEBUG_CATEGORY_STATIC (avtpaafdepay_debug);
#define GST_CAT_DEFAULT (avtpaafdepay_debug)

static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-raw, "
        "format = (string) { S16BE, S24BE, S32BE, F32BE }, "
        "rate = (int) { 8000, 16000, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000 }, "
        "channels = " GST_AUDIO_CHANNELS_RANGE ", "
        "layout = (string) interleaved")
    );

G_DEFINE_TYPE (GstAvtpAafDepay, gst_avtp_aaf_depay,
    GST_TYPE_AVTP_BASE_DEPAYLOAD);
GST_ELEMENT_REGISTER_DEFINE (avtpaafdepay, "avtpaafdepay", GST_RANK_NONE,
    GST_TYPE_AVTP_AAF_DEPAY);

static GstFlowReturn gst_avtp_aaf_depay_process (GstAvtpBaseDepayload *
    basedepay, GstBuffer * buffer);

static void
gst_avtp_aaf_depay_class_init (GstAvtpAafDepayClass * klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
  GstAvtpBaseDepayloadClass *avtpbasedepayload_class =
      GST_AVTP_BASE_DEPAYLOAD_CLASS (klass);

  gst_element_class_add_static_pad_template (element_class, &src_template);

  gst_element_class_set_static_metadata (element_class,
      "AVTP Audio Format (AAF) depayloader",
      "Codec/Depayloader/Network/AVTP",
      "Extracts raw audio from AAF AVTPDUs",
      "Andre Guedes <andre.guedes@intel.com>");

  avtpbasedepayload_class->process = gst_avtp_aaf_depay_process;

  GST_DEBUG_CATEGORY_INIT (avtpaafdepay_debug, "avtpaafdepay", 0,
      "AAF AVTP Depayloader");
}

static void
gst_avtp_aaf_depay_init (GstAvtpAafDepay * avtpaafdepay)
{
  avtpaafdepay->channels = 0;
  avtpaafdepay->depth = 0;
  avtpaafdepay->rate = 0;
  avtpaafdepay->format = 0;
}

static const gchar *
avtp_to_gst_format (int avtp_format)
{
  GstAudioFormat gst_format;

  switch (avtp_format) {
    case AVTP_AAF_FORMAT_INT_16BIT:
      gst_format = GST_AUDIO_FORMAT_S16BE;
      break;
    case AVTP_AAF_FORMAT_INT_24BIT:
      gst_format = GST_AUDIO_FORMAT_S24BE;
      break;
    case AVTP_AAF_FORMAT_INT_32BIT:
      gst_format = GST_AUDIO_FORMAT_S32BE;
      break;
    case AVTP_AAF_FORMAT_FLOAT_32BIT:
      gst_format = GST_AUDIO_FORMAT_F32BE;
      break;
    default:
      gst_format = GST_AUDIO_FORMAT_UNKNOWN;
      break;
  }

  return gst_audio_format_to_string (gst_format);
}

static gint
avtp_to_gst_rate (int rate)
{
  switch (rate) {
    case AVTP_AAF_PCM_NSR_8KHZ:
      return 8000;
    case AVTP_AAF_PCM_NSR_16KHZ:
      return 16000;
    case AVTP_AAF_PCM_NSR_24KHZ:
      return 24000;
    case AVTP_AAF_PCM_NSR_32KHZ:
      return 32000;
    case AVTP_AAF_PCM_NSR_44_1KHZ:
      return 44100;
    case AVTP_AAF_PCM_NSR_48KHZ:
      return 48000;
    case AVTP_AAF_PCM_NSR_88_2KHZ:
      return 88200;
    case AVTP_AAF_PCM_NSR_96KHZ:
      return 96000;
    case AVTP_AAF_PCM_NSR_176_4KHZ:
      return 176400;
    case AVTP_AAF_PCM_NSR_192KHZ:
      return 192000;
    default:
      return 0;
  }
}

static gboolean
gst_avtp_aaf_depay_push_caps_event (GstAvtpAafDepay * avtpaafdepay,
    gint rate, gint depth, gint format, gint channels)
{
  GstCaps *caps;
  GstEvent *event;
  GstAvtpBaseDepayload *avtpbasedepayload =
      GST_AVTP_BASE_DEPAYLOAD (avtpaafdepay);

  caps = gst_caps_new_simple ("audio/x-raw",
      "format", G_TYPE_STRING, avtp_to_gst_format (format),
      "rate", G_TYPE_INT, avtp_to_gst_rate (rate),
      "channels", G_TYPE_INT, channels,
      "layout", G_TYPE_STRING, "interleaved", NULL);

  event = gst_event_new_caps (caps);

  if (!gst_pad_push_event (avtpbasedepayload->srcpad, event)) {
    GST_ERROR_OBJECT (avtpaafdepay, "Failed to push CAPS event");
    gst_caps_unref (caps);
    return FALSE;
  }

  GST_DEBUG_OBJECT (avtpaafdepay, "CAPS event pushed %" GST_PTR_FORMAT, caps);

  avtpaafdepay->rate = rate;
  avtpaafdepay->depth = depth;
  avtpaafdepay->format = format;
  avtpaafdepay->channels = channels;
  gst_caps_unref (caps);
  return TRUE;
}

static gboolean
gst_avtp_aaf_depay_are_audio_features_valid (GstAvtpAafDepay * avtpaafdepay,
    guint64 rate, guint64 depth, guint64 format, guint64 channels)
{
  if (G_UNLIKELY (rate != avtpaafdepay->rate)) {
    GST_INFO_OBJECT (avtpaafdepay, "Rate doesn't match, disarding buffer");
    return FALSE;
  }
  if (G_UNLIKELY (depth != avtpaafdepay->depth)) {
    GST_INFO_OBJECT (avtpaafdepay, "Bit depth doesn't match, disarding buffer");
    return FALSE;
  }
  if (G_UNLIKELY (format != avtpaafdepay->format)) {
    GST_INFO_OBJECT (avtpaafdepay,
        "Sample format doesn't match, disarding buffer");
    return FALSE;
  }
  if (G_UNLIKELY (channels != avtpaafdepay->channels)) {
    GST_INFO_OBJECT (avtpaafdepay,
        "Number of channels doesn't match, disarding buffer");
    return FALSE;
  }

  return TRUE;
}

static GstFlowReturn
gst_avtp_aaf_depay_process (GstAvtpBaseDepayload * avtpbasedepayload,
    GstBuffer * buffer)
{
  int res GST_UNUSED_ASSERT;
  GstMapInfo info;
  guint32 subtype, version;
  GstClockTime ptime;
  GstBuffer *subbuffer;
  struct avtp_stream_pdu *pdu;
  guint64 channels, depth, rate, format, tstamp, seqnum, streamid,
      streamid_valid, data_len;
  GstAvtpAafDepay *avtpaafdepay = GST_AVTP_AAF_DEPAY (avtpbasedepayload);

  if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) {
    GST_ELEMENT_ERROR (avtpaafdepay, RESOURCE, READ, ("Failed to map memory"),
        (NULL));
    gst_buffer_unref (buffer);
    return GST_FLOW_ERROR;
  }

  if (info.size < sizeof (struct avtp_stream_pdu)) {
    GST_DEBUG_OBJECT (avtpaafdepay, "Malformed AVTPDU, discarding it");
    gst_buffer_unmap (buffer, &info);
    goto discard;
  }

  pdu = (struct avtp_stream_pdu *) info.data;
  res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_NSR, &rate);
  g_assert (res == 0);
  res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_FORMAT, &format);
  g_assert (res == 0);
  res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_SEQ_NUM, &seqnum);
  g_assert (res == 0);
  res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_BIT_DEPTH, &depth);
  g_assert (res == 0);
  res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_TIMESTAMP, &tstamp);
  g_assert (res == 0);
  res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_SV, &streamid_valid);
  g_assert (res == 0);
  res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_STREAM_ID, &streamid);
  g_assert (res == 0);
  res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_CHAN_PER_FRAME, &channels);
  g_assert (res == 0);
  res = avtp_aaf_pdu_get (pdu, AVTP_AAF_FIELD_STREAM_DATA_LEN, &data_len);
  g_assert (res == 0);
  res = avtp_pdu_get ((struct avtp_common_pdu *) pdu, AVTP_FIELD_SUBTYPE,
      &subtype);
  g_assert (res == 0);
  res = avtp_pdu_get ((struct avtp_common_pdu *) pdu, AVTP_FIELD_VERSION,
      &version);
  g_assert (res == 0);

  gst_buffer_unmap (buffer, &info);

  if (subtype != AVTP_SUBTYPE_AAF) {
    GST_DEBUG_OBJECT (avtpaafdepay, "Subtype doesn't match, discarding buffer");
    goto discard;
  }
  if (version != 0) {
    GST_DEBUG_OBJECT (avtpaafdepay, "Version doesn't match, discarding buffer");
    goto discard;
  }
  if (streamid_valid != 1 || streamid != avtpbasedepayload->streamid) {
    GST_DEBUG_OBJECT (avtpaafdepay, "Invalid StreamID, discarding buffer");
    goto discard;
  }
  if (gst_buffer_get_size (buffer) < sizeof (*pdu) + data_len) {
    GST_DEBUG_OBJECT (avtpaafdepay, "Incomplete AVTPDU, discarding buffer");
    goto discard;
  }

  if (G_UNLIKELY (!gst_pad_has_current_caps (avtpbasedepayload->srcpad))) {
    if (!gst_avtp_aaf_depay_push_caps_event (avtpaafdepay, rate, depth, format,
            channels)) {
      gst_buffer_unref (buffer);
      return GST_FLOW_NOT_NEGOTIATED;
    }

    avtpbasedepayload->seqnum = seqnum;
  }

  if (G_UNLIKELY (!gst_avtp_aaf_depay_are_audio_features_valid (avtpaafdepay,
              rate, depth, format, channels)))
    goto discard;

  if (seqnum != avtpbasedepayload->seqnum) {
    GST_INFO_OBJECT (avtpaafdepay, "Sequence number mismatch: expected %u"
        " received %" G_GUINT64_FORMAT, avtpbasedepayload->seqnum, seqnum);
    avtpbasedepayload->seqnum = seqnum;
  }
  avtpbasedepayload->seqnum++;

  ptime = gst_avtp_base_depayload_tstamp_to_ptime (avtpbasedepayload, tstamp,
      avtpbasedepayload->last_dts);

  subbuffer = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL,
      sizeof (struct avtp_stream_pdu), data_len);
  GST_BUFFER_PTS (subbuffer) = ptime;
  GST_BUFFER_DTS (subbuffer) = ptime;

  gst_buffer_unref (buffer);

  return gst_avtp_base_depayload_push (avtpbasedepayload, subbuffer);

discard:
  gst_buffer_unref (buffer);
  return GST_FLOW_OK;
}
