#include <config.h>
#include "lib/lib.h"
#include "lib/util.h"
#include "lib/gth-metadata.h"


enum  {
	GTH_METADATA_DUMMY_PROPERTY,
	GTH_METADATA_ID,
	GTH_METADATA_DESCRIPTION,
	GTH_METADATA_RAW,
	GTH_METADATA_STRING_LIST,
	GTH_METADATA_FORMATTED,
	GTH_METADATA_VALUE_TYPE,
	GTH_METADATA_CATEGORY,
};


struct _GthMetadataPrivate {
	GthMetadataType  data_type;
	char            *id;
	char            *description;
	char            *raw;
	char            *formatted;
	char            *value_type;
	char            *category;
	GthStringList   *list;
	double           x;
	double           y;
};


G_DEFINE_TYPE_WITH_CODE (GthMetadata,
			 gth_metadata,
			 G_TYPE_OBJECT,
			 G_ADD_PRIVATE (GthMetadata))


static void
gth_metadata_get_property (GObject    *object,
			   guint       property_id,
			   GValue     *value,
			   GParamSpec *pspec)
{
	GthMetadata *self;

	self = GTH_METADATA (object);
	switch (property_id) {
	case GTH_METADATA_ID:
		g_value_set_string (value, self->priv->id);
		break;
	case GTH_METADATA_DESCRIPTION:
		g_value_set_string (value, self->priv->description);
		break;
	case GTH_METADATA_RAW:
		g_value_set_string (value, self->priv->raw);
		break;
	case GTH_METADATA_STRING_LIST:
		g_value_set_object (value, self->priv->list);
		break;
	case GTH_METADATA_FORMATTED:
		g_value_set_string (value, self->priv->formatted);
		break;
	case GTH_METADATA_VALUE_TYPE:
		g_value_set_string (value, self->priv->value_type);
		break;
	case GTH_METADATA_CATEGORY:
		g_value_set_string (value, self->priv->category);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}


static void
gth_metadata_set_property (GObject      *object,
			   guint         property_id,
			   const GValue *value,
			   GParamSpec   *pspec)
{
	GthMetadata *self;

	self = GTH_METADATA (object);
	switch (property_id) {
	case GTH_METADATA_ID:
		_g_str_set (&self->priv->id, g_value_get_string (value));
		break;
	case GTH_METADATA_DESCRIPTION:
		_g_str_set (&self->priv->description, g_value_get_string (value));
		break;
	case GTH_METADATA_RAW:
		_g_str_set (&self->priv->raw, g_value_get_string (value));
		self->priv->data_type = GTH_METADATA_TYPE_STRING;
		break;
	case GTH_METADATA_STRING_LIST:
		GthStringList *string_list = GTH_STRING_LIST (g_value_get_object (value));
		if (string_list != self->priv->list) {
			_g_object_unref (self->priv->list);
			if (string_list != NULL) {
				self->priv->list = gth_string_list_new (gth_string_list_get_list (string_list));
				self->priv->data_type = GTH_METADATA_TYPE_STRING_LIST;
			}
			else {
				self->priv->list = NULL;
				self->priv->data_type = GTH_METADATA_TYPE_STRING;
			}
		}
		break;
	case GTH_METADATA_FORMATTED:
		_g_str_set (&self->priv->formatted, g_value_get_string (value));
		break;
	case GTH_METADATA_VALUE_TYPE:
		_g_str_set (&self->priv->value_type, g_value_get_string (value));
		break;
	case GTH_METADATA_CATEGORY:
		_g_str_set (&self->priv->category, g_value_get_string (value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}


static void
gth_metadata_finalize (GObject *obj)
{
	GthMetadata *self;

	self = GTH_METADATA (obj);

	g_free (self->priv->id);
	g_free (self->priv->description);
	g_free (self->priv->raw);
	_g_object_unref (self->priv->list);
	g_free (self->priv->formatted);
	g_free (self->priv->value_type);

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


static void
gth_metadata_class_init (GthMetadataClass *klass)
{
	G_OBJECT_CLASS (klass)->get_property = gth_metadata_get_property;
	G_OBJECT_CLASS (klass)->set_property = gth_metadata_set_property;
	G_OBJECT_CLASS (klass)->finalize = gth_metadata_finalize;

	g_object_class_install_property (G_OBJECT_CLASS (klass),
					 GTH_METADATA_ID,
					 g_param_spec_string ("id",
					 		      "ID",
					 		      "Metadata unique identifier",
					 		      NULL,
					 		      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE));
	g_object_class_install_property (G_OBJECT_CLASS (klass),
					 GTH_METADATA_DESCRIPTION,
					 g_param_spec_string ("description",
					 		      "Description",
					 		      "Metadata description",
					 		      NULL,
					 		      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE));
	g_object_class_install_property (G_OBJECT_CLASS (klass),
					 GTH_METADATA_RAW,
					 g_param_spec_string ("raw",
					 		      "Raw value",
					 		      "Metadata raw value",
					 		      NULL,
					 		      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE));
	g_object_class_install_property (G_OBJECT_CLASS (klass),
					 GTH_METADATA_STRING_LIST,
					 g_param_spec_object ("string-list",
					 		      "String list",
					 		      "Metadata value as a list of strings (if available)",
					 		      GTH_TYPE_STRING_LIST,
					 		      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE));
	g_object_class_install_property (G_OBJECT_CLASS (klass),
					 GTH_METADATA_FORMATTED,
					 g_param_spec_string ("formatted",
					 		      "Formatted value",
					 		      "Metadata formatted value",
					 		      NULL,
					 		      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE));
	g_object_class_install_property (G_OBJECT_CLASS (klass),
					 GTH_METADATA_VALUE_TYPE,
					 g_param_spec_string ("value-type",
					 		      "Type",
					 		      "Metadata type description",
					 		      NULL,
					 		      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE));
	g_object_class_install_property (G_OBJECT_CLASS (klass),
					 GTH_METADATA_CATEGORY,
					 g_param_spec_string ("category",
							      "Category",
							      "Metadata category",
							      NULL,
							      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE));
}


static void
gth_metadata_init (GthMetadata *self)
{
	self->priv = gth_metadata_get_instance_private (self);
	self->priv->data_type = GTH_METADATA_TYPE_STRING;
	self->priv->id = NULL;
	self->priv->description = NULL;
	self->priv->raw = NULL;
	self->priv->list = NULL;
	self->priv->formatted = NULL;
	self->priv->value_type = NULL;
}


GthMetadata *
gth_metadata_new (void)
{
	return g_object_new (GTH_TYPE_METADATA, NULL);
}


GthMetadata *
gth_metadata_new_typed (const char *value_type,
			const char *raw,
			const char *formatted)
{
	return g_object_new (GTH_TYPE_METADATA,
		"value-type", value_type,
		"raw", raw,
		"formatted", (formatted != NULL ? formatted : raw),
		NULL);
}


GthMetadata *
gth_metadata_new_for_string (const char *raw,
			     const char *formatted)
{
	return g_object_new (GTH_TYPE_METADATA,
		"raw", raw,
		"formatted", (formatted != NULL ? formatted : raw),
		NULL);
}


GthMetadata *
gth_metadata_new_for_string_list (GthStringList *list)
{
	return g_object_new (GTH_TYPE_METADATA, "string-list", list, NULL);
}


GthMetadata *
gth_metadata_new_for_point (double x,
			    double y)
{
	GthMetadata *self = (GthMetadata *) g_object_new (GTH_TYPE_METADATA, NULL);
	self->priv->data_type = GTH_METADATA_TYPE_POINT;
	self->priv->x = x;
	self->priv->y = y;
	return self;

}


GthMetadata *
gth_metadata_new_sRGBColorSpace ()
{
	return g_object_new (GTH_TYPE_METADATA,
		"id", "Exif::Photo::ColorSpace",
		"raw", "1",
		"formatted", "sRGB",
		"value-type", "Short",
		NULL);
}


GthMetadataType
gth_metadata_get_data_type (GthMetadata *metadata)
{
	return metadata->priv->data_type;
}


const char *
gth_metadata_get_id (GthMetadata *metadata)
{
	return metadata->priv->id;
}


const char *
gth_metadata_get_description (GthMetadata *metadata)
{
	return metadata->priv->description;
}


const char *
gth_metadata_get_raw (GthMetadata *metadata)
{
	return metadata->priv->raw;
}


const char *
gth_metadata_get_formatted (GthMetadata *metadata)
{
	return metadata->priv->formatted;
}


const char *
gth_metadata_get_value_type (GthMetadata *metadata)
{
	return metadata->priv->value_type;
}


const char *
gth_metadata_get_category (GthMetadata *metadata)
{
	return metadata->priv->category;
}


GthStringList *
gth_metadata_get_string_list (GthMetadata *metadata)
{
	if (metadata == NULL) {
		return NULL;
	}
	else if (metadata->priv->data_type == GTH_METADATA_TYPE_STRING_LIST) {
		return metadata->priv->list;
	}
	else {
		return NULL;
	}
}


gboolean
gth_metadata_get_point (GthMetadata     *metadata,
			double          *x,
			double          *y)
{
	if (metadata == NULL) {
		return FALSE;
	}
	else if (metadata->priv->data_type == GTH_METADATA_TYPE_POINT) {
		if (x != NULL)
			*x = metadata->priv->x;
		if (y != NULL)
			*y = metadata->priv->y;
		return TRUE;
	}
	else {
		return FALSE;
	}
}


void
set_attribute_from_string (GFileInfo  *info,
			   const char *key,
			   const char *raw,
			   const char *formatted)
{
	GthMetadata *metadata;

	metadata = g_object_new (
		GTH_TYPE_METADATA,
		"id", key,
		"raw", raw,
		"formatted", (formatted != NULL ? formatted : raw),
		NULL);
	g_file_info_set_attribute_object (info, key, G_OBJECT (metadata));

	g_object_unref (metadata);
}


static GPtrArray *metadata_info_array;
static GHashTable *metadata_info_hash;
static gboolean metadata_info_sorted;
static GMutex metadata_info_mutex;
static int metadata_info_last_sort_order;


void gth_metadata_info_init () {
	metadata_info_array = g_ptr_array_new ();
	metadata_info_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
	metadata_info_sorted = FALSE;
	metadata_info_last_sort_order = 0;
}


GthMetadataInfo * gth_metadata_info_get (const char *id) {
	return (id != NULL) ? g_hash_table_lookup (metadata_info_hash, id) : NULL;
}


GPtrArray * gth_metadata_info_get_all () {
	return metadata_info_array;
}


GthMetadataInfo * gth_metadata_info_register (const char *id, const char *display_name, const char *category, GthMetadataFlags flags, const char *type) {
	if ((display_name != NULL) && (strstr (display_name, "0x") != NULL))
		flags = GTH_METADATA_HIDDEN;

	g_mutex_lock (&metadata_info_mutex);

	GthMetadataInfo *info = g_new0 (GthMetadataInfo, 1);
	if (id != NULL)
		info->id = g_strdup (id);
	if (display_name != NULL)
		info->display_name = g_strdup (display_name);
	if (category != NULL)
		info->category = g_strdup (category);
	info->flags = flags;
	if (type != NULL)
		info->type = g_strdup (type);
	info->sort_order = metadata_info_last_sort_order++;

	g_ptr_array_add (metadata_info_array, info);
	g_hash_table_insert (metadata_info_hash, (gpointer) info->id, info);
	metadata_info_sorted = FALSE;

	g_mutex_unlock (&metadata_info_mutex);

	return info;
}


void gth_metadata_info_register_from_metadata (GthMetadata *metadata) {
	GthMetadataInfo *metadata_info = gth_metadata_info_get (metadata->priv->id);
	if ((metadata_info == NULL) && (metadata->priv->category != NULL)) {
		metadata_info = gth_metadata_info_register (
			metadata->priv->id,
			metadata->priv->description,
			metadata->priv->category,
			GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW,
			metadata->priv->value_type
		);
		metadata_info->sort_order = 500;
	}

	if ((metadata_info != NULL)
		&& (metadata_info->type == NULL)
		&& (metadata->priv->value_type != NULL))
	{
		metadata_info->type = g_strdup (metadata->priv->value_type);
	}

	if ((metadata_info != NULL)
		&& (metadata_info->display_name == NULL)
		&& (metadata->priv->description != NULL))
	{
		metadata_info->display_name = g_strdup (metadata->priv->description);
	}
}


GthMetadataInfo * gth_metadata_info_copy (GthMetadataInfo *info) {
	GthMetadataInfo *new_info = g_new0 (GthMetadataInfo, 1);
	if (info->id != NULL)
		new_info->id = g_strdup (info->id);
	if (info->type != NULL)
		new_info->type = g_strdup (info->type);
	if (info->display_name != NULL)
		new_info->display_name = g_strdup (info->display_name);
	if (info->category != NULL)
		new_info->category = g_strdup (info->category);
	new_info->sort_order = info->sort_order;
	new_info->flags = info->flags;
	return new_info;
}


void gth_metadata_info_destroy (GthMetadataInfo *info) {
	if (info != NULL) {
		g_free (info->id);
		g_free (info->type);
		g_free (info->display_name);
		g_free (info->category);
		g_free (info);
	}
}


static GPtrArray *metadata_category_array;
static GHashTable *metadata_category_hash;
static GMutex metadata_category_mutex;
static int metadata_category_next_sort_order;


void gth_metadata_category_init () {
	metadata_category_array = g_ptr_array_new ();
	metadata_category_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
	metadata_category_next_sort_order = 0;
}


GthMetadataCategory * gth_metadata_category_get (const char *id) {
	return (id != NULL) ? g_hash_table_lookup (metadata_category_hash, id) : NULL;
}


void gth_metadata_category_register (const char *id, const char *display_name) {
	g_mutex_lock (&metadata_category_mutex);

	if (gth_metadata_category_get (id) == NULL) {
		GthMetadataCategory *category = gth_metadata_category_new (id, display_name, metadata_category_next_sort_order++);
		g_ptr_array_add (metadata_category_array, category);
		g_hash_table_insert (metadata_category_hash, (gpointer) category->id, category);
	}

	g_mutex_unlock (&metadata_category_mutex);
}


GthMetadataCategory * gth_metadata_category_new (const char *id, const char *display_name, int sort_order) {
	GthMetadataCategory *new_category = g_new0 (GthMetadataCategory, 1);
	if (id != NULL)
		new_category->id = g_strdup (id);
	if (display_name != NULL)
		new_category->display_name = g_strdup (display_name);
	new_category->sort_order = sort_order;
	return new_category;
}


GthMetadataCategory * gth_metadata_category_copy (GthMetadataCategory *category) {
	return gth_metadata_category_new (
		category->id,
		category->display_name,
		category->sort_order);
}


void gth_metadata_category_free (GthMetadataCategory *category) {
	if (category != NULL) {
		g_free (category->id);
		g_free (category->display_name);
		g_free (category);
	}
}
