/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * gsf-clip-data.c: clipboard data
 *
 * Copyright (C) 2006 Novell Inc
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

#include <gsf-config.h>
#include <gsf/gsf-clip-data.h>
#include <gsf/gsf.h>

#include <glib/gi18n-lib.h>

/**
 * GsfClipFormatWindows:
 * @GSF_CLIP_FORMAT_WINDOWS_ERROR: error
 * @GSF_CLIP_FORMAT_WINDOWS_UNKNOWN: unknown
 * @GSF_CLIP_FORMAT_WINDOWS_METAFILE: CF_METAFILEPICT
 * @GSF_CLIP_FORMAT_WINDOWS_DIB: CF_DIB
 * @GSF_CLIP_FORMAT_WINDOWS_ENHANCED_METAFILE: CF_ENHMETAFILE
**/

struct _GsfClipDataClass {
	GObjectClass parent_class;
};

/* Private part of the GsfClipData structure */
struct _GsfClipDataPrivate {
	GsfClipFormat format;
	GsfBlob *data_blob;
};

static GObjectClass *gsf_clip_data_parent_class;
static void
gsf_clip_data_finalize (GObject *object)
{
	GsfClipData *clip_data;
	GsfClipDataPrivate *priv;

	clip_data = GSF_CLIP_DATA (object);
	priv = clip_data->priv;

	if (priv->data_blob)
		g_object_unref (priv->data_blob);

	g_free (priv);

	gsf_clip_data_parent_class->finalize (object);
}

static void
gsf_clip_data_init (GsfClipData *clip_data)
{
	GsfClipDataPrivate *priv;

	priv = g_new0 (GsfClipDataPrivate, 1);
	clip_data->priv = priv;
}
static void
gsf_clip_data_class_init (GObjectClass *gobject_class)
{
	gobject_class->finalize = gsf_clip_data_finalize;

	gsf_clip_data_parent_class = g_type_class_peek_parent (gobject_class);
}


GSF_CLASS (GsfClipData, gsf_clip_data,
	   gsf_clip_data_class_init, gsf_clip_data_init,
	   G_TYPE_OBJECT)

/**
 * gsf_clip_data_new:
 * @format: Format for the data inside the @data_blob
 * @data_blob: Object which holds the binary contents for the #GsfClipData
 *
 * Creates a new #GsfClipData object.  This function acquires a reference to the
 * @data_blob, so you should unref the blob on your own if you no longer need it
 * directly.
 *
 * Return value: A newly-created #GsfClipData.
 **/
GsfClipData *
gsf_clip_data_new (GsfClipFormat format, GsfBlob *data_blob)
{
	GsfClipData *clip_data;
	GsfClipDataPrivate *priv;

	g_return_val_if_fail (GSF_IS_BLOB (data_blob), NULL);

	clip_data = g_object_new (GSF_TYPE_CLIP_DATA, NULL);

	priv = clip_data->priv;

	priv->format = format;
	priv->data_blob = g_object_ref (data_blob);

	return clip_data;
}

/**
 * gsf_clip_data_get_format:
 * @clip_data: A #GsfClipData.
 *
 * Queries the clipboard data format of a #GsfClipData.  The format refers to the data
 * blob inside the @clip_data; use gsf_clip_data_get_data_blob() to get that data blob.
 *
 * Return value: The format in which the #GsfClipData's data blob is stored.
 **/
GsfClipFormat
gsf_clip_data_get_format (GsfClipData *clip_data)
{
	GsfClipDataPrivate *priv;

	g_return_val_if_fail (GSF_IS_CLIP_DATA (clip_data), GSF_CLIP_FORMAT_UNKNOWN);

	priv = clip_data->priv;
	return priv->format;
}

/**
 * gsf_clip_data_get_data_blob:
 * @clip_data: A #GsfClipData.
 *
 * Queries the data blob that actually stores a #GsfClipData's binary data.
 *
 * Return value: (transfer full): A new reference to the #GsfBlob that stores this @clip_data's
 * binary data.  You must use g_object_unref() to dispose of that data blob when
 * you are done with it.
 **/
GsfBlob *
gsf_clip_data_get_data_blob (GsfClipData *clip_data)
{
	GsfClipDataPrivate *priv;

	g_return_val_if_fail (GSF_IS_CLIP_DATA (clip_data), NULL);

	priv = clip_data->priv;
	return g_object_ref (priv->data_blob);
}

static void
set_error_missing_clipboard_data (GError **error, const char *format_name, gsize at_least_size)
{
	gchar *size_str;

	size_str = g_strdup_printf ("%" G_GSIZE_FORMAT, at_least_size);
	g_set_error (error,
		     GSF_ERROR,
		     GSF_ERROR_INVALID_DATA,
		     _("The clip_data is in %s, but it is smaller than "
		       "at least %s bytes"),
		     format_name,
		     size_str);
	g_free (size_str);
}

static gsize
get_windows_clipboard_data_offset (GsfClipFormatWindows format)
{
	struct format_offset_pair {
		GsfClipFormatWindows format;
		gsize offset;
	};

	static const struct format_offset_pair pairs[] = {
		{ GSF_CLIP_FORMAT_WINDOWS_UNKNOWN, 4 },
		{ GSF_CLIP_FORMAT_WINDOWS_METAFILE, 12 },
		{ GSF_CLIP_FORMAT_WINDOWS_DIB, 4 },
		{ GSF_CLIP_FORMAT_WINDOWS_ENHANCED_METAFILE, 4 } /* FIXME: does this have a PACKEDMETA in front
								  * as well, similar to GSF_CLIP_FORMAT_WINDOWS_METAFILE? */
	};
	static const int num_pairs = G_N_ELEMENTS (pairs);

	int i;

	for (i = 0; i < num_pairs; i++)
		if (pairs[i].format == format)
			return pairs[i].offset;

	g_assert_not_reached ();
	return 0;
}

/**
 * check_format_windows:
 * @format: #GsfClipFormatWindows
 * @format_name: const char *
 * @blob_size: #gsize
 * @error: (out) (optional) (nullable): place to store a #GError if anything goes wrong
 *
 * Checks that the specified blob size matches the expected size for the format.
 *
 * Returns: the same format if the size is correct, or
 * 	GSF_CLIP_FORMAT_WINDOWS_ERROR if the size is too small.
 **/
static GsfClipFormatWindows
check_format_windows (GsfClipFormatWindows format, const char *format_name, gsize blob_size, GError **error)
{
	gsize offset;

	offset = get_windows_clipboard_data_offset (format);
	if (blob_size <= offset) {
		set_error_missing_clipboard_data (error, format_name, offset + 1);
		format = GSF_CLIP_FORMAT_WINDOWS_ERROR;
	}

	return format;
}

/**
 * gsf_clip_data_get_windows_clipboard_format:
 * @clip_data: A #GsfClipData.
 * @error: (out) (optional) (nullable): place to store a #GError if anything goes wrong
 *
 * Queries the Windows clipboard data format for a #GsfClipData.  The @clip_data must
 * have been created with #GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD.
 *
 * Returns: A #GsfClipFormatWindows value.
 *
 * Possible errors: #GSF_ERROR_INVALID_DATA if the data blob in the @clip_data is
 * smaller than it should be; in this case GSF_CLIP_FORMAT_WINDOWS_ERROR will be returned.
 **/
GsfClipFormatWindows
gsf_clip_data_get_windows_clipboard_format (GsfClipData *clip_data, GError **error)
{
	GsfClipDataPrivate *priv;
	gsize size;
	guint32 value;
	gconstpointer data;
	GsfClipFormatWindows format;

	g_return_val_if_fail (GSF_IS_CLIP_DATA (clip_data), GSF_CLIP_FORMAT_WINDOWS_ERROR);
	g_return_val_if_fail (error == NULL || *error == NULL, GSF_CLIP_FORMAT_WINDOWS_ERROR);

	priv = clip_data->priv;
	g_return_val_if_fail (priv->format == GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD, GSF_CLIP_FORMAT_WINDOWS_ERROR);

	size = gsf_blob_get_size (priv->data_blob);

	if (size < 4) {
		g_set_error (error,
			     GSF_ERROR,
			     GSF_ERROR_INVALID_DATA,
			     _("The clip_data is in Windows clipboard format, but it is smaller than "
			       "the required 4 bytes."));
		return GSF_CLIP_FORMAT_WINDOWS_ERROR;
	}

	data = gsf_blob_peek_data (priv->data_blob);

	value = GSF_LE_GET_GUINT32 (data);

	switch (value) {
	case GSF_CLIP_FORMAT_WINDOWS_METAFILE:
		format = check_format_windows (GSF_CLIP_FORMAT_WINDOWS_METAFILE, _("Windows Metafile format"),
					       size, error);
		break;

	case GSF_CLIP_FORMAT_WINDOWS_DIB:
	case 2: /* CF_BITMAP */
		format = check_format_windows (GSF_CLIP_FORMAT_WINDOWS_DIB, _("Windows DIB or BITMAP format"),
					       size, error);
		break;

	case GSF_CLIP_FORMAT_WINDOWS_ENHANCED_METAFILE:
		format = check_format_windows (GSF_CLIP_FORMAT_WINDOWS_ENHANCED_METAFILE, _("Windows Enhanced Metafile format"),
					       size, error);
		break;

	default:
		format = GSF_CLIP_FORMAT_WINDOWS_UNKNOWN;
		break;
	}

	return format;
}

/**
 * gsf_clip_data_peek_real_data:
 * @clip_data: A #GsfClipData.
 * @ret_size: Location to return the size of the returned data buffer.
 * @error: (out) (optional) (nullable): place to store a #GError if anything goes wrong.
 *
 * Queries a pointer directly to the clipboard data of a #GsfClipData.  The
 * resulting pointer is not necessarily the same data pointer that was passed to
 * gsf_blob_new() prior to creating the @clip_data.  For example, if the data is
 * in #GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD format, then it will have extra header
 * bytes in front of the actual metafile data.  This function will skip over
 * those header bytes if necessary and return a pointer to the "real" data.
 *
 * Return value: (transfer none): Pointer to the real clipboard data.  The size in bytes of this
 * buffer is returned in the @ret_size argument.
 **/
gconstpointer
gsf_clip_data_peek_real_data (GsfClipData *clip_data, gsize *ret_size, GError **error)
{
	GsfClipDataPrivate *priv;
	gconstpointer data;
	gsize offset;

	g_return_val_if_fail (GSF_IS_CLIP_DATA (clip_data), NULL);
	g_return_val_if_fail (ret_size != NULL, NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	priv = clip_data->priv;

	data = gsf_blob_peek_data (priv->data_blob);

	if (priv->format == GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD) {
		GsfClipFormatWindows win_format;

		win_format = gsf_clip_data_get_windows_clipboard_format (clip_data, error);
		if (win_format == GSF_CLIP_FORMAT_WINDOWS_ERROR)
			return NULL;

		/* gsf_clip_data_get_windows_clipboard_format() already did the size checks for us,
		 * so we can jump to the offset right away without doing extra checks.
		 */

		offset = get_windows_clipboard_data_offset (win_format);
	} else
		offset = 0;

	*ret_size = gsf_blob_get_size (priv->data_blob) - offset;
	return (char *) data + offset;
}
