/*
 * Purple - Internet Messaging Library
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here. Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This library 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 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 General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <birb.h>

#include "purpleimage.h"

struct _PurpleImage {
	GObject parent;

	char *filename;

	GBytes *contents;
};

enum {
	PROP_0,
	PROP_CONTENTS,
	PROP_DATA,
	PROP_FILENAME,
	PROP_SIZE,
	N_PROPERTIES,
};
static GParamSpec *properties[N_PROPERTIES] = {NULL, };

/******************************************************************************
 * Helpers
 *****************************************************************************/
static void
purple_image_set_filename(PurpleImage *image, const char *filename) {
	g_return_if_fail(PURPLE_IS_IMAGE(image));

	if(g_set_str(&image->filename, filename)) {
		g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_FILENAME]);
	}
}

static void
purple_image_set_contents(PurpleImage *image, GBytes *contents) {
	g_return_if_fail(PURPLE_IS_IMAGE(image));

	if(image->contents != contents) {
		GObject *obj = G_OBJECT(image);

		g_clear_pointer(&image->contents, g_bytes_unref);
		if(contents != NULL) {
			image->contents = g_bytes_ref(contents);
		}

		g_object_freeze_notify(obj);
		g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_CONTENTS]);
		g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_DATA]);
		g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_SIZE]);
		g_object_thaw_notify(obj);
	}
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
G_DEFINE_FINAL_TYPE(PurpleImage, purple_image, G_TYPE_OBJECT);

static void
purple_image_finalize(GObject *obj) {
	PurpleImage *image = PURPLE_IMAGE(obj);

	g_clear_pointer(&image->contents, g_bytes_unref);
	g_clear_pointer(&image->filename, g_free);

	G_OBJECT_CLASS(purple_image_parent_class)->finalize(obj);
}

static void
purple_image_get_property(GObject *obj, guint param_id, GValue *value,
                          GParamSpec *pspec)
{
	PurpleImage *image = PURPLE_IMAGE(obj);
	gsize size = 0;

	switch (param_id) {
	case PROP_CONTENTS:
		g_value_set_boxed(value, purple_image_get_contents(image));
		break;
	case PROP_DATA:
		g_value_set_pointer(value,
		                    (gpointer)purple_image_get_data(image, NULL));
		break;
	case PROP_FILENAME:
		g_value_set_string(value, purple_image_get_filename(image));
		break;
	case PROP_SIZE:
		purple_image_get_data(image, &size);
		g_value_set_uint64(value, size);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
		break;
	}
}

static void
purple_image_set_property(GObject *obj, guint param_id, const GValue *value,
                          GParamSpec *pspec)
{
	PurpleImage *image = PURPLE_IMAGE(obj);

	switch (param_id) {
	case PROP_FILENAME:
		purple_image_set_filename(image, g_value_get_string(value));
		break;
	case PROP_CONTENTS:
		purple_image_set_contents(image, g_value_get_boxed(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
		break;
	}
}

static void
purple_image_init(G_GNUC_UNUSED PurpleImage *image) {
}

static void
purple_image_class_init(PurpleImageClass *klass) {
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);

	obj_class->finalize = purple_image_finalize;
	obj_class->get_property = purple_image_get_property;
	obj_class->set_property = purple_image_set_property;

	/**
	 * PurpleImage:contents:
	 *
	 * The contents of the image.
	 *
	 * Since: 3.0
	 */
	properties[PROP_CONTENTS] = g_param_spec_boxed(
		"contents", NULL, NULL,
		G_TYPE_BYTES,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS |
		G_PARAM_EXPLICIT_NOTIFY);

	/**
	 * PurpleImage:data:
	 *
	 * The raw image data.
	 *
	 * Generally the [property@Image:contents] property should be used, but if
	 * just the data is necessary this saves a step.
	 *
	 * Since: 3.0
	 */
	properties[PROP_DATA] = g_param_spec_pointer(
		"data", NULL, NULL,
		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleImage:filename:
	 *
	 * The filename for the image if one was provided.
	 *
	 * Since: 3.0
	 */
	properties[PROP_FILENAME] = g_param_spec_string(
		"filename", NULL, NULL,
		NULL,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS |
		G_PARAM_EXPLICIT_NOTIFY);

	/**
	 * PurpleImage:size:
	 *
	 * The size of the image in bytes.
	 *
	 * Since: 3.0
	 */
	properties[PROP_SIZE] = g_param_spec_uint64(
		"size", NULL, NULL,
		0, G_MAXUINT64, 0,
		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
}

/******************************************************************************
 * Public API
 *****************************************************************************/
GBytes *
purple_image_get_contents(PurpleImage *image) {
	g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL);

	return image->contents;
}

gconstpointer
purple_image_get_data(PurpleImage *image, gsize *size) {
	g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL);

	if(image->contents != NULL) {
		return g_bytes_get_data(image->contents, size);
	}

	return NULL;
}

const char *
purple_image_get_filename(PurpleImage *image) {
	g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL);

	return image->filename;
}

PurpleImage *
purple_image_new_from_bytes(GBytes *bytes) {
	g_return_val_if_fail(bytes != NULL, NULL);

	return g_object_new(
		PURPLE_TYPE_IMAGE,
		"contents", bytes,
		NULL);
}

PurpleImage *
purple_image_new_from_data(gconstpointer data, gsize size) {
	PurpleImage *image = NULL;
	GBytes *contents = NULL;

	g_return_val_if_fail(data != NULL, NULL);
	g_return_val_if_fail(size > 0, NULL);

	contents = g_bytes_new(data, size);

	image = g_object_new(
		PURPLE_TYPE_IMAGE,
		"contents", contents,
		NULL);

	g_bytes_unref(contents);

	return image;
}

PurpleImage *
purple_image_new_from_filename(const char *filename, GError **error) {
	PurpleImage *image = NULL;
	GBytes *bytes = NULL;
	GError *local_error = NULL;
	char *contents = NULL;
	gsize length = 0;

	if(!g_file_get_contents(filename, &contents, &length, &local_error)) {
		g_propagate_error(error, local_error);

		return NULL;
	}

	bytes = g_bytes_new_take(contents, length);

	image = g_object_new(
		PURPLE_TYPE_IMAGE,
		"contents", bytes,
		"filename", filename,
		NULL
	);

	g_bytes_unref(bytes);

	return image;
}

PurpleImage *
purple_image_new_from_resource(const char *path, GError **error) {
	PurpleImage *image = NULL;
	GBytes *contents = NULL;
	GError *local_error = NULL;

	g_return_val_if_fail(!birb_str_is_empty(path), NULL);

	contents = g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE,
	                                   &local_error);

	if(local_error != NULL) {
		g_clear_pointer(&contents, g_bytes_unref);

		g_propagate_error(error, local_error);

		return NULL;
	}

	image = g_object_new(
		PURPLE_TYPE_IMAGE,
		"contents", contents,
		NULL);

	g_clear_pointer(&contents, g_bytes_unref);

	return image;
}

gboolean
purple_image_save(PurpleImage *image, const char *filename, GError **error) {
	gconstpointer data = NULL;
	gsize size = 0;

	g_return_val_if_fail(PURPLE_IS_IMAGE(image), FALSE);
	g_return_val_if_fail(!birb_str_is_empty(filename), FALSE);

	data = g_bytes_get_data(image->contents, &size);

	return g_file_set_contents(filename, data, size, error);
}
