/*
 * y4mreader.c - Y4M parser
 *
 * Copyright (C) 2015 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
 */

#include "y4mreader.h"

#include <stdio.h>

/* format documentation:
 * http://wiki.multimedia.cx/index.php?title=YUV4MPEG2 */

static inline gboolean
parse_int (const gchar * str, guint * out_value_ptr)
{
  gint saved_errno;
  glong value;
  gboolean ret;

  if (!str)
    return FALSE;
  str += 1;
  if (*str == '\0')
    return FALSE;

  saved_errno = errno;
  errno = 0;
  value = strtol (str, NULL, 0);
  ret = (errno == 0);
  errno = saved_errno;
  if (value > 0 && value <= G_MAXUINT)
    *out_value_ptr = value;
  else
    ret = FALSE;

  return ret;
}

static gboolean
parse_header (Y4MReader * file)
{
  gint i, j, b;
  guint8 header[BUFSIZ];
  size_t s;
  gchar *str;

  memset (header, 0, BUFSIZ);
  s = fread (header, 1, 9, file->fp);
  if (s < 9)
    return FALSE;

  if (memcmp (header, "YUV4MPEG2", 9) != 0)
    return FALSE;

  for (i = 9; i < BUFSIZ - 1; i++) {
    b = fgetc (file->fp);
    if (b == EOF)
      return FALSE;
    if (b == 0xa)
      break;
    header[i] = b;
  }

  if (i == BUFSIZ - 1)
    return FALSE;

  j = 9;
  while (j < i) {
    if ((header[j] != 0x20) && (header[j - 1] == 0x20)) {
      switch (header[j]) {
        case 'W':
          if (!parse_int ((gchar *) & header[j], &file->width))
            return FALSE;
          break;
        case 'H':
          if (!parse_int ((gchar *) & header[j], &file->height))
            return FALSE;
          break;
        case 'C':
          str = (char *) &header[j + 1];
          if (strncmp (str, "420", 3) != 0) {
            g_warning ("Unsupported chroma subsampling.");
            return FALSE;       /* unsupported chroma subsampling */
          }
          break;
        case 'I':
          str = (char *) &header[j + 1];
          if (*str != 'p' && *str != '?') {
            g_warning ("Interlaced content are not supported.");
            return FALSE;       /* interlaced is unsupported */
          }
          break;
        case 'F':              /* frame rate ratio */
        {
          guint num, den;

          if (!parse_int ((gchar *) & header[j], &num))
            return FALSE;
          while ((header[j] != ':') && (j < i))
            j++;
          if (!parse_int ((gchar *) & header[j], &den))
            return FALSE;

          if (num <= 0 || den <= 0) {
            file->fps_n = 30;   /* default to 30 fps */
            file->fps_d = 1;
          } else {
            file->fps_n = num;
            file->fps_d = den;
          }
          break;
        }
        case 'A':              /* sample aspect ration */
          break;
        case 'X':              /* metadata */
          break;
        default:
          break;
      }
    }
    j++;
  }

  return TRUE;
}

Y4MReader *
y4m_reader_open (const gchar * filename)
{
  Y4MReader *imagefile;

  imagefile = g_slice_new0 (Y4MReader);

  if (filename) {
    imagefile->fp = fopen (filename, "r");
    if (!imagefile->fp) {
      g_warning ("open file %s error", filename);
      goto bail;
    }
  } else {
    imagefile->fp = stdin;
  }

  if (!parse_header (imagefile))
    goto bail;

  return imagefile;

bail:
  if (imagefile->fp && imagefile->fp != stdin)
    fclose (imagefile->fp);

  g_slice_free (Y4MReader, imagefile);
  return NULL;
}

void
y4m_reader_close (Y4MReader * file)
{
  g_return_if_fail (file);

  if (file->fp && file->fp != stdin)
    fclose (file->fp);

  g_slice_free (Y4MReader, file);
}

static gboolean
skip_frame_header (Y4MReader * file)
{
  gint i, b;
  guint8 header[BUFSIZ];
  size_t s;

  memset (header, 0, BUFSIZ);
  s = fread (header, 1, 5, file->fp);
  if (s < 5)
    return FALSE;

  if (memcmp (header, "FRAME", 5) != 0)
    return FALSE;

  for (i = 5; i < BUFSIZ - 1; i++) {
    b = fgetc (file->fp);
    if (b == EOF)
      return FALSE;
    if (b == 0xa)
      break;
    header[i] = b;
  }

  return (i < BUFSIZ - 1);
}

gboolean
y4m_reader_load_image (Y4MReader * file, GstVaapiImage * image)
{
  guint8 *plane;
  size_t s;
  guint frame_size, stride, i;

  g_return_val_if_fail (gst_vaapi_image_is_mapped (image), FALSE);
  g_return_val_if_fail (file && file->fp, FALSE);

  /* only valid for I420 */
  frame_size = file->height * file->width * 3 / 2;
  if (gst_vaapi_image_get_data_size (image) < frame_size)
    return FALSE;
  if (gst_vaapi_image_get_plane_count (image) != 3)
    return FALSE;

  if (!skip_frame_header (file))
    return FALSE;

  /* Y plane */
  plane = gst_vaapi_image_get_plane (image, 0);
  stride = gst_vaapi_image_get_pitch (image, 0);
  for (i = 0; i < file->height; i++) {
    s = fread (plane, 1, file->width, file->fp);
    if (s != file->width)
      return FALSE;
    plane += stride;
  }

  /* U plane */
  plane = gst_vaapi_image_get_plane (image, 1);
  stride = gst_vaapi_image_get_pitch (image, 1);
  for (i = 0; i < file->height / 2; i++) {
    s = fread (plane, 1, file->width / 2, file->fp);
    if (s != file->width / 2)
      return FALSE;
    plane += stride;
  }

  /* V plane */
  plane = gst_vaapi_image_get_plane (image, 2);
  stride = gst_vaapi_image_get_pitch (image, 2);
  for (i = 0; i < file->height / 2; i++) {
    s = fread (plane, 1, file->width / 2, file->fp);
    if (s != file->width / 2)
      return FALSE;
    plane += stride;
  }

  return TRUE;
}
