/* GStreamer JPEG parser test utility
 * Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.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.
 */

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

#include <gst/gst.h>
#include <gst/codecparsers/gstjpegparser.h>

#include <stdlib.h>

static GstBuffer *app_segments[16];     /* NULL */

static const gchar *
get_marker_name (guint8 marker)
{
  switch (marker) {
    case GST_JPEG_MARKER_SOF0:
      return "SOF (Baseline)";
    case GST_JPEG_MARKER_SOF1:
      return "SOF (Extended Sequential, Huffman)";
    case GST_JPEG_MARKER_SOF2:
      return "SOF (Extended Progressive, Huffman)";
    case GST_JPEG_MARKER_SOF3:
      return "SOF (Lossless, Huffman)";
    case GST_JPEG_MARKER_SOF5:
      return "SOF (Differential Sequential, Huffman)";
    case GST_JPEG_MARKER_SOF6:
      return "SOF (Differential Progressive, Huffman)";
    case GST_JPEG_MARKER_SOF7:
      return "SOF (Differential Lossless, Huffman)";
    case GST_JPEG_MARKER_SOF9:
      return "SOF (Extended Sequential, Arithmetic)";
    case GST_JPEG_MARKER_SOF10:
      return "SOF (Progressive, Arithmetic)";
    case GST_JPEG_MARKER_SOF11:
      return "SOF (Lossless, Arithmetic)";
    case GST_JPEG_MARKER_SOF13:
      return "SOF (Differential Sequential, Arithmetic)";
    case GST_JPEG_MARKER_SOF14:
      return "SOF (Differential Progressive, Arithmetic)";
    case GST_JPEG_MARKER_SOF15:
      return "SOF (Differential Lossless, Arithmetic)";
    case GST_JPEG_MARKER_DHT:
      return "DHT";
    case GST_JPEG_MARKER_DAC:
      return "DAC";
    case GST_JPEG_MARKER_SOI:
      return "SOI";
    case GST_JPEG_MARKER_EOI:
      return "EOI";
    case GST_JPEG_MARKER_SOS:
      return "SOS";
    case GST_JPEG_MARKER_DQT:
      return "DQT";
    case GST_JPEG_MARKER_DNL:
      return "DNL";
    case GST_JPEG_MARKER_DRI:
      return "DRI";
    case GST_JPEG_MARKER_APP0:
      return "APP0";
    case GST_JPEG_MARKER_APP1:
      return "APP1";
    case GST_JPEG_MARKER_APP2:
      return "APP2";
    case GST_JPEG_MARKER_APP3:
      return "APP3";
    case GST_JPEG_MARKER_APP4:
      return "APP4";
    case GST_JPEG_MARKER_APP5:
      return "APP5";
    case GST_JPEG_MARKER_APP6:
      return "APP6";
    case GST_JPEG_MARKER_APP7:
      return "APP7";
    case GST_JPEG_MARKER_APP8:
      return "APP8";
    case GST_JPEG_MARKER_APP9:
      return "APP9";
    case GST_JPEG_MARKER_APP10:
      return "APP10";
    case GST_JPEG_MARKER_APP11:
      return "APP11";
    case GST_JPEG_MARKER_APP12:
      return "APP12";
    case GST_JPEG_MARKER_APP13:
      return "APP13";
    case GST_JPEG_MARKER_APP14:
      return "APP14";
    case GST_JPEG_MARKER_APP15:
      return "APP15";
    case GST_JPEG_MARKER_COM:
      return "COM";
    default:
      if (marker > GST_JPEG_MARKER_RST_MIN && marker < GST_JPEG_MARKER_RST_MAX)
        return "RST";
      break;
  }
  return "???";
}

static gboolean
parse_jpeg_segment (GstJpegSegment * segment)
{
  switch (segment->marker) {
    case GST_JPEG_MARKER_SOF0:
    case GST_JPEG_MARKER_SOF1:
    case GST_JPEG_MARKER_SOF2:
    case GST_JPEG_MARKER_SOF3:
    case GST_JPEG_MARKER_SOF9:
    case GST_JPEG_MARKER_SOF10:
    case GST_JPEG_MARKER_SOF11:{
      GstJpegFrameHdr hdr;
      int i;

      if (!gst_jpeg_segment_parse_frame_header (segment, &hdr)) {
        g_printerr ("Failed to parse frame header!\n");
        return FALSE;
      }

      g_print ("\t\twidth x height   = %u x %u\n", hdr.width, hdr.height);
      g_print ("\t\tsample precision = %u\n", hdr.sample_precision);
      g_print ("\t\tnum components   = %u\n", hdr.num_components);
      for (i = 0; i < hdr.num_components; ++i) {
        g_print ("\t\t%d: id=%d, h=%d, v=%d, qts=%d\n", i,
            hdr.components[i].identifier, hdr.components[i].horizontal_factor,
            hdr.components[i].vertical_factor,
            hdr.components[i].quant_table_selector);
      }
      break;
    }
    case GST_JPEG_MARKER_DHT:{
      GstJpegHuffmanTables ht;

      if (!gst_jpeg_segment_parse_huffman_table (segment, &ht)) {
        g_printerr ("Failed to parse huffman table!\n");
        return FALSE;
      }
      break;
    }
    case GST_JPEG_MARKER_DQT:{
      GstJpegQuantTables qt;

      if (!gst_jpeg_segment_parse_quantization_table (segment, &qt)) {
        g_printerr ("Failed to parse quantization table!\n");
        return FALSE;
      }
      break;
    }
    case GST_JPEG_MARKER_SOS:{
      GstJpegScanHdr hdr;
      int i;

      if (!gst_jpeg_segment_parse_scan_header (segment, &hdr)) {
        g_printerr ("Failed to parse scan header!\n");
        return FALSE;
      }

      g_print ("\t\tnum components   = %u\n", hdr.num_components);
      for (i = 0; i < hdr.num_components; ++i) {
        g_print ("\t\t  %d: cs=%d, dcs=%d, acs=%d\n", i,
            hdr.components[i].component_selector,
            hdr.components[i].dc_selector, hdr.components[i].ac_selector);
      }
    }
    case GST_JPEG_MARKER_COM:
      /* gst_util_dump_mem (segment->data + segment->offset, segment->size); */
      break;
    default:
      if (segment->marker >= GST_JPEG_MARKER_APP_MIN
          && segment->marker <= GST_JPEG_MARKER_APP_MAX) {
        guint n = segment->marker - GST_JPEG_MARKER_APP_MIN;

        if (app_segments[n] == NULL)
          app_segments[n] = gst_buffer_new ();

        gst_buffer_append_memory (app_segments[n],
            gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY,
                (guint8 *) segment->data + segment->offset,
                segment->size, 0, segment->size, NULL, NULL));
      }
      break;
  }
  return TRUE;
}

static void
parse_jpeg (const guint8 * data, gsize size)
{
  GstJpegSegment segment;
  guint offset = 0;

  while (gst_jpeg_parse (&segment, data, size, offset)) {
    if (segment.offset > offset + 2)
      g_print ("  skipped %u bytes\n", (guint) segment.offset - offset - 2);

    g_print ("%6d bytes at offset %-8u : %s\n", (gint) segment.size,
        segment.offset, get_marker_name (segment.marker));

    if (segment.marker == GST_JPEG_MARKER_EOI)
      break;

    if (offset + segment.size < size &&
        parse_jpeg_segment (&segment) && segment.size >= 0)
      offset = segment.offset + segment.size;
    else
      offset += 2;
  };
}

static void
process_file (const gchar * fn)
{
  GError *err = NULL;
  gchar *data = NULL;
  gsize size = 0;
  guint i;

  g_print ("===============================================================\n");
  g_print (" %s\n", fn);
  g_print ("===============================================================\n");

  if (!g_file_get_contents (fn, &data, &size, &err)) {
    g_error ("%s", err->message);
    g_clear_error (&err);
    return;
  }

  parse_jpeg ((const guint8 *) data, size);

  for (i = 0; i < G_N_ELEMENTS (app_segments); ++i) {
    if (app_segments[i] != NULL) {
      GstMapInfo map = GST_MAP_INFO_INIT;

      /* Could parse/extract tags here */
      gst_buffer_map (app_segments[i], &map, GST_MAP_READ);
      g_print ("\tAPP%-2u : %u bytes\n", i, (guint) map.size);
      gst_util_dump_mem ((guchar *) map.data, MIN (map.size, 16));
      gst_buffer_unmap (app_segments[i], &map);
      gst_buffer_unref (app_segments[i]);
      app_segments[i] = NULL;
    }
  }

  g_free (data);
}

int
main (int argc, gchar ** argv)
{
  gchar **filenames = NULL;
  GOptionEntry options[] = {
    {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL},
    {NULL}
  };
  GOptionContext *ctx;
  GError *err = NULL;
  guint i, num;

  gst_init (&argc, &argv);

  if (argc == 1) {
    g_printerr ("Usage: %s FILE.JPG [FILE2.JPG] [FILE..JPG]\n", argv[0]);
    return -1;
  }

  ctx = g_option_context_new ("JPEG FILES");
  g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
  g_option_context_add_group (ctx, gst_init_get_option_group ());
  if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
    g_print ("Error initializing: %s\n", GST_STR_NULL (err->message));
    g_option_context_free (ctx);
    g_clear_error (&err);
    exit (1);
  }
  g_option_context_free (ctx);

  if (filenames == NULL || *filenames == NULL) {
    g_printerr ("Please provide one or more filenames.");
    return 1;
  }

  num = g_strv_length (filenames);

  for (i = 0; i < num; ++i) {
    process_file (filenames[i]);
  }

  g_strfreev (filenames);

  return 0;
}