/* poppler-media.cc: glib interface to MediaRendition
 *
 * Copyright (C) 2010 Carlos Garcia Campos <carlosgc@gnome.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"

#include <cerrno>

#include <goo/gfile.h>

#include "poppler-media.h"
#include "poppler-private.h"

/**
 * SECTION: poppler-media
 * @short_description: Media
 * @title: PopplerMedia
 */

typedef struct _PopplerMediaClass PopplerMediaClass;

struct _PopplerMedia
{
  GObject   parent_instance;

  gchar  *filename;

  gchar  *mime_type;
  Object stream;
};

struct _PopplerMediaClass
{
  GObjectClass parent_class;
};

G_DEFINE_TYPE (PopplerMedia, poppler_media, G_TYPE_OBJECT)

static void
poppler_media_finalize (GObject *object)
{
  PopplerMedia *media = POPPLER_MEDIA(object);

  if (media->filename) {
    g_free (media->filename);
    media->filename = nullptr;
  }

  if (media->mime_type) {
    g_free (media->mime_type);
    media->mime_type = nullptr;
  }

  media->stream = Object();

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

static void
poppler_media_class_init (PopplerMediaClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = poppler_media_finalize;
}

static void
poppler_media_init (PopplerMedia *media)
{
}

PopplerMedia *
_poppler_media_new (const MediaRendition *poppler_media)
{
  PopplerMedia *media;

  g_assert (poppler_media != nullptr);

  media = POPPLER_MEDIA (g_object_new (POPPLER_TYPE_MEDIA, nullptr));

  if (poppler_media->getIsEmbedded()) {
    const GooString* mime_type;

    media->stream = poppler_media->getEmbbededStreamObject()->copy();
    mime_type = poppler_media->getContentType();
    if (mime_type)
      media->mime_type = g_strdup (mime_type->c_str());
  } else {
    media->filename = g_strdup (poppler_media->getFileName()->c_str());
  }

  return media;
}

/**
* poppler_media_get_filename:
* @poppler_media: a #PopplerMedia
*
* Returns the media clip filename, in case of non-embedded media. filename might be
* a local relative or absolute path or a URI
*
* Return value: a filename, return value is owned by #PopplerMedia and should not be freed
*
* Since: 0.14
*/
const gchar *
poppler_media_get_filename (PopplerMedia *poppler_media)
{
  g_return_val_if_fail (POPPLER_IS_MEDIA (poppler_media), NULL);
  g_return_val_if_fail (!poppler_media->stream.isStream(), NULL);

  return poppler_media->filename;
}

/**
 * poppler_media_is_embedded:
 * @poppler_media: a #PopplerMedia
 *
 * Whether the media clip is embedded in the PDF. If the result is %TRUE, the embedded stream
 * can be saved with poppler_media_save() or poppler_media_save_to_callback() function.
 * If the result is %FALSE, the media clip filename can be retrieved with
 * poppler_media_get_filename() function.
 *
 * Return value: %TRUE if media clip is embedded, %FALSE otherwise
 *
 * Since: 0.14
 */
gboolean
poppler_media_is_embedded (PopplerMedia *poppler_media)
{
  g_return_val_if_fail (POPPLER_IS_MEDIA (poppler_media), FALSE);

  return poppler_media->stream.isStream();
}

/**
 * poppler_media_get_mime_type:
 * @poppler_media: a #PopplerMedia
 *
 * Returns the media clip mime-type
 *
 * Return value: the mime-type, return value is owned by #PopplerMedia and should not be freed
 *
 * Since: 0.14
 */
const gchar *
poppler_media_get_mime_type (PopplerMedia *poppler_media)
{
  g_return_val_if_fail (POPPLER_IS_MEDIA (poppler_media), NULL);

  return poppler_media->mime_type;
}

static gboolean
save_helper (const gchar  *buf,
	     gsize         count,
	     gpointer      data,
	     GError      **error)
{
  FILE *f = (FILE *) data;
  gsize n;

  n = fwrite (buf, 1, count, f);
  if (n != count)
    {
      g_set_error (error,
		   G_FILE_ERROR,
		   g_file_error_from_errno (errno),
		   "Error writing to media file: %s",
		   g_strerror (errno));
      return FALSE;
    }

  return TRUE;
}

/**
 * poppler_media_save:
 * @poppler_media: a #PopplerMedia
 * @filename: name of file to save
 * @error: (allow-none): return location for error, or %NULL.
 *
 * Saves embedded stream of @poppler_media to a file indicated by @filename.
 * If @error is set, %FALSE will be returned.
 * Possible errors include those in the #G_FILE_ERROR domain
 * and whatever the save function generates.
 *
 * Return value: %TRUE, if the file successfully saved
 *
 * Since: 0.14
 */
gboolean
poppler_media_save (PopplerMedia *poppler_media,
		    const char   *filename,
		    GError      **error)
{
  gboolean result;
  FILE *f;

  g_return_val_if_fail (POPPLER_IS_MEDIA (poppler_media), FALSE);
  g_return_val_if_fail (poppler_media->stream.isStream(), FALSE);

  f = openFile (filename, "wb");

  if (f == nullptr)
    {
      gchar *display_name = g_filename_display_name (filename);
      g_set_error (error,
		   G_FILE_ERROR,
		   g_file_error_from_errno (errno),
		   "Failed to open '%s' for writing: %s",
		   display_name,
		   g_strerror (errno));
      g_free (display_name);
      return FALSE;
    }

  result = poppler_media_save_to_callback (poppler_media, save_helper, f, error);

  if (fclose (f) < 0)
    {
      gchar *display_name = g_filename_display_name (filename);
      g_set_error (error,
		   G_FILE_ERROR,
		   g_file_error_from_errno (errno),
		   "Failed to close '%s', all data may not have been saved: %s",
		   display_name,
		   g_strerror (errno));
      g_free (display_name);
      return FALSE;
    }

  return result;
}

#define BUF_SIZE 1024

/**
 * poppler_media_save_to_callback:
 * @poppler_media: a #PopplerMedia
 * @save_func: (scope call): a function that is called to save each block of data that the save routine generates.
 * @user_data: user data to pass to the save function.
 * @error: (allow-none): return location for error, or %NULL.
 *
 * Saves embedded stream of @poppler_media by feeding the produced data to @save_func. Can be used
 * when you want to store the media clip stream to something other than a file, such as
 * an in-memory buffer or a socket. If @error is set, %FALSE will be
 * returned. Possible errors include those in the #G_FILE_ERROR domain and
 * whatever the save function generates.
 *
 * Return value: %TRUE, if the save successfully completed
 *
 * Since: 0.14
 */
gboolean
poppler_media_save_to_callback (PopplerMedia        *poppler_media,
				PopplerMediaSaveFunc save_func,
				gpointer             user_data,
				GError             **error)
{
  Stream *stream;
  gchar buf[BUF_SIZE];
  int i;
  gboolean eof_reached = FALSE;

  g_return_val_if_fail (POPPLER_IS_MEDIA (poppler_media), FALSE);
  g_return_val_if_fail (poppler_media->stream.isStream(), FALSE);

  stream = poppler_media->stream.getStream();
  stream->reset();

  do
    {
      int data;

      for (i = 0; i < BUF_SIZE; i++)
	{
	  data = stream->getChar ();
	  if (data == EOF)
	    {
	      eof_reached = TRUE;
	      break;
	    }
	  buf[i] = data;
	}

      if (i > 0)
	{
	  if (! (save_func) (buf, i, user_data, error))
	    {
	      stream->close ();
	      return FALSE;
	    }
	}
    }
  while (! eof_reached);

  stream->close ();

  return TRUE;
}
