/* GStreamer
 * Copyright (C) 2010 David Schleef <ds@schleef.org>
 *
 * 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:element-diracparse
 * @title: gstdiracparse
 *
 * The gstdiracparse element does FIXME stuff.
 *
 * ## Example launch line
 * |[
 * gst-launch-1.0 -v fakesrc ! gstdiracparse ! FIXME ! fakesink
 * ]|
 * FIXME Describe what the pipeline does.
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/gst.h>
#include <gst/base/base.h>
#include <gst/pbutils/pbutils.h>
#include <string.h>
#include "gstdiracparse.h"
#include "dirac_parse.h"

/* prototypes */


static void gst_dirac_parse_set_property (GObject * object,
    guint property_id, const GValue * value, GParamSpec * pspec);
static void gst_dirac_parse_get_property (GObject * object,
    guint property_id, GValue * value, GParamSpec * pspec);
static void gst_dirac_parse_dispose (GObject * object);
static void gst_dirac_parse_finalize (GObject * object);

static gboolean gst_dirac_parse_start (GstBaseParse * parse);
static gboolean gst_dirac_parse_stop (GstBaseParse * parse);
static gboolean gst_dirac_parse_set_sink_caps (GstBaseParse * parse,
    GstCaps * caps);
static GstCaps *gst_dirac_parse_get_sink_caps (GstBaseParse * parse,
    GstCaps * filter);
static GstFlowReturn gst_dirac_parse_handle_frame (GstBaseParse * parse,
    GstBaseParseFrame * frame, gint * skipsize);
static gboolean gst_dirac_parse_convert (GstBaseParse * parse,
    GstFormat src_format, gint64 src_value, GstFormat dest_format,
    gint64 * dest_value);
static GstFlowReturn gst_dirac_parse_pre_push_frame (GstBaseParse * parse,
    GstBaseParseFrame * frame);

enum
{
  PROP_0
};

/* pad templates */

static GstStaticPadTemplate gst_dirac_parse_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-dirac")
    );

static GstStaticPadTemplate gst_dirac_parse_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-dirac, parsed=(boolean)TRUE, "
        "width=(int)[1,MAX], height=(int)[1,MAX], "
        "framerate=(fraction)[0/1,MAX], "
        "pixel-aspect-ratio=(fraction)[0/1,MAX], "
        "interlace-mode=(string) { progressive, interleaved }, "
        "profile=(string){ vc2-low-delay, vc2-simple, vc2-main, main }, "
        "level=(string) { 0, 1, 128}")
    );

/* class initialization */

#define parent_class gst_dirac_parse_parent_class
G_DEFINE_TYPE (GstDiracParse, gst_dirac_parse, GST_TYPE_BASE_PARSE);

static void
gst_dirac_parse_class_init (GstDiracParseClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
  GstBaseParseClass *base_parse_class = GST_BASE_PARSE_CLASS (klass);

  gobject_class->set_property = gst_dirac_parse_set_property;
  gobject_class->get_property = gst_dirac_parse_get_property;
  gobject_class->dispose = gst_dirac_parse_dispose;
  gobject_class->finalize = gst_dirac_parse_finalize;

  gst_element_class_add_static_pad_template (element_class,
      &gst_dirac_parse_src_template);
  gst_element_class_add_static_pad_template (element_class,
      &gst_dirac_parse_sink_template);

  gst_element_class_set_static_metadata (element_class, "Dirac parser",
      "Codec/Parser/Video", "Parses Dirac streams",
      "David Schleef <ds@schleef.org>");

  base_parse_class->start = GST_DEBUG_FUNCPTR (gst_dirac_parse_start);
  base_parse_class->stop = GST_DEBUG_FUNCPTR (gst_dirac_parse_stop);
  base_parse_class->set_sink_caps =
      GST_DEBUG_FUNCPTR (gst_dirac_parse_set_sink_caps);
  base_parse_class->get_sink_caps =
      GST_DEBUG_FUNCPTR (gst_dirac_parse_get_sink_caps);
  base_parse_class->handle_frame =
      GST_DEBUG_FUNCPTR (gst_dirac_parse_handle_frame);
  base_parse_class->convert = GST_DEBUG_FUNCPTR (gst_dirac_parse_convert);
  base_parse_class->pre_push_frame =
      GST_DEBUG_FUNCPTR (gst_dirac_parse_pre_push_frame);

}

static void
gst_dirac_parse_init (GstDiracParse * diracparse)
{
  gst_base_parse_set_min_frame_size (GST_BASE_PARSE (diracparse), 13);
  gst_base_parse_set_pts_interpolation (GST_BASE_PARSE (diracparse), FALSE);
  GST_PAD_SET_ACCEPT_INTERSECT (GST_BASE_PARSE_SINK_PAD (diracparse));
  GST_PAD_SET_ACCEPT_TEMPLATE (GST_BASE_PARSE_SINK_PAD (diracparse));
}

void
gst_dirac_parse_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  g_return_if_fail (GST_IS_DIRAC_PARSE (object));

  switch (property_id) {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

void
gst_dirac_parse_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  g_return_if_fail (GST_IS_DIRAC_PARSE (object));

  switch (property_id) {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

void
gst_dirac_parse_dispose (GObject * object)
{
  g_return_if_fail (GST_IS_DIRAC_PARSE (object));

  /* clean up as possible.  may be called multiple times */

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

void
gst_dirac_parse_finalize (GObject * object)
{
  g_return_if_fail (GST_IS_DIRAC_PARSE (object));

  /* clean up object here */

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


static gboolean
gst_dirac_parse_start (GstBaseParse * parse)
{
  GstDiracParse *diracparse = GST_DIRAC_PARSE (parse);

  gst_base_parse_set_min_frame_size (parse, 13);

  diracparse->sent_codec_tag = FALSE;

  return TRUE;
}

static gboolean
gst_dirac_parse_stop (GstBaseParse * parse)
{
  return TRUE;
}

static gboolean
gst_dirac_parse_set_sink_caps (GstBaseParse * parse, GstCaps * caps)
{
  /* Called when sink caps are set */
  return TRUE;
}

static const gchar *
get_profile_name (int profile)
{
  switch (profile) {
    case 0:
      return "vc2-low-delay";
    case 1:
      return "vc2-simple";
    case 2:
      return "vc2-main";
    case 8:
      return "main";
    default:
      break;
  }
  return "unknown";
}

static const gchar *
get_level_name (int level)
{
  switch (level) {
    case 0:
      return "0";
    case 1:
      return "1";
    case 128:
      return "128";
    default:
      break;
  }
  /* need to add it to template caps, so return 0 for now */
  GST_WARNING ("unhandled dirac level %u", level);
  return "0";
}

static GstFlowReturn
gst_dirac_parse_handle_frame (GstBaseParse * parse,
    GstBaseParseFrame * frame, gint * skipsize)
{
  int off;
  guint32 next_header;
  GstMapInfo map;
  guint8 *data;
  gsize size;
  gboolean have_picture = FALSE;
  int offset;
  guint framesize = 0;

  gst_buffer_map (frame->buffer, &map, GST_MAP_READ);
  data = map.data;
  size = map.size;

  if (G_UNLIKELY (size < 13)) {
    *skipsize = 1;
    goto out;
  }

  GST_DEBUG ("%" G_GSIZE_FORMAT ": %02x %02x %02x %02x", size, data[0], data[1],
      data[2], data[3]);

  if (GST_READ_UINT32_BE (data) != 0x42424344) {
    GstByteReader reader;

    gst_byte_reader_init (&reader, data, size);
    off = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff,
        0x42424344, 0, size);

    if (off < 0) {
      *skipsize = size - 3;
      goto out;
    }

    GST_LOG_OBJECT (parse, "possible sync at buffer offset %d", off);

    GST_DEBUG ("skipping %d", off);
    *skipsize = off;
    goto out;
  }

  /* have sync, parse chunks */

  offset = 0;
  while (!have_picture) {
    GST_DEBUG ("offset %d:", offset);

    if (offset + 13 >= size) {
      framesize = offset + 13;
      goto out;
    }

    GST_DEBUG ("chunk type %02x", data[offset + 4]);

    if (GST_READ_UINT32_BE (data + offset) != 0x42424344) {
      GST_DEBUG ("bad header");
      *skipsize = 3;
      goto out;
    }

    next_header = GST_READ_UINT32_BE (data + offset + 5);
    GST_DEBUG ("next_header %d", next_header);
    if (next_header == 0)
      next_header = 13;

    if (SCHRO_PARSE_CODE_IS_PICTURE (data[offset + 4])) {
      have_picture = TRUE;
    }

    offset += next_header;
    if (offset >= size) {
      framesize = offset;
      goto out;
    }
  }

  gst_buffer_unmap (frame->buffer, &map);

  framesize = offset;
  GST_DEBUG ("framesize %d", framesize);

  g_assert (framesize <= size);

  if (data[4] == SCHRO_PARSE_CODE_SEQUENCE_HEADER) {
    GstCaps *caps;
    GstDiracParse *diracparse = GST_DIRAC_PARSE (parse);
    DiracSequenceHeader sequence_header;
    int ret;

    ret = dirac_sequence_header_parse (&sequence_header, data + 13, size - 13);
    if (ret) {
      memcpy (&diracparse->sequence_header, &sequence_header,
          sizeof (sequence_header));
      caps = gst_caps_new_simple ("video/x-dirac",
          "width", G_TYPE_INT, sequence_header.width,
          "height", G_TYPE_INT, sequence_header.height,
          "framerate", GST_TYPE_FRACTION,
          sequence_header.frame_rate_numerator,
          sequence_header.frame_rate_denominator,
          "pixel-aspect-ratio", GST_TYPE_FRACTION,
          sequence_header.aspect_ratio_numerator,
          sequence_header.aspect_ratio_denominator,
          "interlace-mode", G_TYPE_STRING,
          sequence_header.interlaced ? "interleaved" : "progressive",
          "profile", G_TYPE_STRING, get_profile_name (sequence_header.profile),
          "level", G_TYPE_STRING, get_level_name (sequence_header.level), NULL);
      gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps);
      gst_caps_unref (caps);

      gst_base_parse_set_frame_rate (parse,
          sequence_header.frame_rate_numerator,
          sequence_header.frame_rate_denominator, 0, 0);
    }
  }

  gst_base_parse_set_min_frame_size (parse, 13);

  return gst_base_parse_finish_frame (parse, frame, framesize);

out:
  gst_buffer_unmap (frame->buffer, &map);
  if (framesize)
    gst_base_parse_set_min_frame_size (parse, framesize);
  return GST_FLOW_OK;
}

static gboolean
gst_dirac_parse_convert (GstBaseParse * parse, GstFormat src_format,
    gint64 src_value, GstFormat dest_format, gint64 * dest_value)
{
  /* Convert between formats */

  return FALSE;
}

static GstFlowReturn
gst_dirac_parse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
{
  GstDiracParse *diracparse = GST_DIRAC_PARSE (parse);

  if (!diracparse->sent_codec_tag) {
    GstTagList *taglist;
    GstCaps *caps;

    /* codec tag */
    caps = gst_pad_get_current_caps (GST_BASE_PARSE_SRC_PAD (parse));
    if (G_UNLIKELY (caps == NULL)) {
      if (GST_PAD_IS_FLUSHING (GST_BASE_PARSE_SRC_PAD (parse))) {
        GST_INFO_OBJECT (parse, "Src pad is flushing");
        return GST_FLOW_FLUSHING;
      } else {
        GST_INFO_OBJECT (parse, "Src pad is not negotiated!");
        return GST_FLOW_NOT_NEGOTIATED;
      }
    }

    taglist = gst_tag_list_new_empty ();
    gst_pb_utils_add_codec_description_to_tag_list (taglist,
        GST_TAG_VIDEO_CODEC, caps);
    gst_caps_unref (caps);

    gst_base_parse_merge_tags (parse, taglist, GST_TAG_MERGE_REPLACE);
    gst_tag_list_unref (taglist);

    /* also signals the end of first-frame processing */
    diracparse->sent_codec_tag = TRUE;
  }

  return GST_FLOW_OK;
}

static void
remove_fields (GstCaps * caps)
{
  guint i, n;

  n = gst_caps_get_size (caps);
  for (i = 0; i < n; i++) {
    GstStructure *s = gst_caps_get_structure (caps, i);

    gst_structure_remove_field (s, "parsed");
  }
}

static GstCaps *
gst_dirac_parse_get_sink_caps (GstBaseParse * parse, GstCaps * filter)
{
  GstCaps *peercaps, *templ;
  GstCaps *res;

  templ = gst_pad_get_pad_template_caps (GST_BASE_PARSE_SINK_PAD (parse));
  if (filter) {
    GstCaps *fcopy = gst_caps_copy (filter);
    /* Remove the fields we convert */
    remove_fields (fcopy);
    peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), fcopy);
    gst_caps_unref (fcopy);
  } else
    peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), NULL);

  if (peercaps) {
    /* Remove the parsed field */
    peercaps = gst_caps_make_writable (peercaps);
    remove_fields (peercaps);

    res = gst_caps_intersect_full (peercaps, templ, GST_CAPS_INTERSECT_FIRST);
    gst_caps_unref (peercaps);
    gst_caps_unref (templ);
  } else {
    res = templ;
  }

  if (filter) {
    GstCaps *intersection;

    intersection =
        gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST);
    gst_caps_unref (res);
    res = intersection;
  }

  return res;
}
