/*
 * gexiv2-metadata.cpp
 *
 * Author(s)
 * 	Mike Gemuende <mike@gemuende.de>
 *  Jim Nelson <jim@yorba.org>
 *
 * This is free software. See COPYING for details.
 */

#include "gexiv2-metadata.h"
#include "gexiv2-metadata-private.h"
#include "gexiv2-stream-io.h"
#include "gexiv2-managed-stream.h"
#include "gexiv2-preview-properties.h"
#include "gexiv2-preview-properties-private.h"
#include "gexiv2-preview-image.h"
#include "gexiv2-preview-image-private.h"
#include <string>
#include <glib-object.h>
#include <gio/gio.h>
#include <exiv2/image.hpp>
#include <exiv2/exif.hpp>
#include <exiv2/iptc.hpp>
#include <exiv2/xmp.hpp>
#include <exiv2/preview.hpp>
#include <exiv2/jpgimage.hpp>


G_BEGIN_DECLS
G_DEFINE_TYPE (GExiv2Metadata, gexiv2_metadata, G_TYPE_OBJECT);

static void gexiv2_metadata_finalize (GObject *object);
static void gexiv2_metadata_set_comment_internal (GExiv2Metadata *self, const gchar *new_comment);

static gboolean gexiv2_metadata_open_internal (GExiv2Metadata *self, GError **error);
static gboolean gexiv2_metadata_save_internal (GExiv2Metadata *self, Exiv2::Image::AutoPtr image, GError **error);



static void
gexiv2_metadata_init (GExiv2Metadata *self)
{
	GExiv2MetadataPrivate *priv;
	self->priv = priv = GEXIV2_METADATA_GET_PRIVATE (self);

	/* Initialize members */
	self->priv->comment = NULL;
	self->priv->mime_type = NULL;
	self->priv->preview_manager = NULL;
	self->priv->preview_properties = NULL;
	self->priv->pixel_width = -1;
	self->priv->pixel_height = -1;
	
	/* the others are static members and need not to be initialized */
}


static void
gexiv2_metadata_class_init (GExiv2MetadataClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
	
	g_type_class_add_private (klass, sizeof	(GExiv2MetadataPrivate));
	
	gobject_class->finalize = gexiv2_metadata_finalize;
}


static void
gexiv2_metadata_finalize (GObject *object)
{
	GExiv2Metadata *self = GEXIV2_METADATA (object);
	
	g_free (self->priv->comment);
	g_free (self->priv->mime_type);
	delete self->priv->preview_manager;
	
	if (self->priv->preview_properties != NULL) {
		int ctr = 0;
		while (self->priv->preview_properties[ctr] != NULL)
			gexiv2_preview_properties_free(self->priv->preview_properties[ctr++]);
		
		g_free (self->priv->preview_properties);
	}
	
	self->priv->image.reset();
	
	G_OBJECT_CLASS (gexiv2_metadata_parent_class)->finalize (object);
}


GExiv2Metadata*
gexiv2_metadata_new ()
{
	return GEXIV2_METADATA (g_object_new (GEXIV2_TYPE_METADATA, NULL));
}


void
gexiv2_metadata_free (GExiv2Metadata *self)
{
	g_return_if_fail(GEXIV2_IS_METADATA(self));
	
	g_object_unref(self);
}


static void
gexiv2_metadata_set_comment_internal (GExiv2Metadata *self, const gchar *new_comment)
{
	g_return_if_fail (GEXIV2_IS_METADATA (self));
	
	g_free (self->priv->comment);
	
	self->priv->comment = g_strdup (new_comment);
}


static void
gexiv2_metadata_init_internal (GExiv2Metadata *self)
{
	GExiv2MetadataPrivate* priv = self->priv;
	
	gexiv2_metadata_set_comment_internal (self, priv->image->comment ().c_str ());
	priv->mime_type = g_strdup (priv->image->mimeType ().c_str ());
		
	priv->pixel_width = priv->image->pixelWidth ();
	priv->pixel_height = priv->image->pixelHeight ();
		
	Exiv2::AccessMode mode = priv->image->checkMode (Exiv2::mdExif);
	priv->supports_exif = (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite);
	
	mode = priv->image->checkMode (Exiv2::mdXmp);
	priv->supports_xmp = (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite);
	
	mode = priv->image->checkMode (Exiv2::mdIptc);
	priv->supports_iptc = (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite);
	
	priv->preview_manager = new Exiv2::PreviewManager(*priv->image.get());
	
	Exiv2::PreviewPropertiesList list = priv->preview_manager->getPreviewProperties();
	int count = list.size();
	if (count > 0) {
		priv->preview_properties = g_new(GExiv2PreviewProperties *, count + 1);
		for (int ctr = 0; ctr < count; ctr++)
			priv->preview_properties[ctr] = gexiv2_preview_properties_new(list[ctr]);
		priv->preview_properties[count] = NULL;
	}
}

static gboolean
gexiv2_metadata_open_internal (GExiv2Metadata *self, GError **error)
{
	if (self->priv->image.get () == NULL || ! self->priv->image->good ()) {
		g_set_error_literal (error, g_quark_from_string ("GExiv2"),
							501, "unsupported format");
		return false;
	}
	
	self->priv->image->readMetadata ();
	gexiv2_metadata_init_internal(self);
	
	return true;
}


gboolean
gexiv2_metadata_open_path (GExiv2Metadata *self, const gchar *path, GError **error)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), false);

	try {
		Exiv2::BasicIo::AutoPtr file (new Exiv2::FileIo (path));
		self->priv->image = Exiv2::ImageFactory::open (file);
		
		return gexiv2_metadata_open_internal (self, error);
		
	} catch (Exiv2::Error &e) {
		g_set_error_literal (error, g_quark_from_string ("GExiv2"), e.code (), e.what ());
	}
	
	return false;
}


gboolean
gexiv2_metadata_open_buf (GExiv2Metadata *self, const guint8 *data, glong n_data, GError **error)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), false);
	
	try {
		self->priv->image = Exiv2::ImageFactory::open (data, n_data);
		
		return gexiv2_metadata_open_internal (self, error);
		
	} catch (Exiv2::Error &e) {
		g_set_error_literal (error, g_quark_from_string ("GExiv2"), 501, "unsupported format");
	}
	
	return false;
}


gboolean
gexiv2_metadata_open_stream (GExiv2Metadata *self, ManagedStreamCallbacks* cb, GError **error)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), false);
	
	try {
		Exiv2::BasicIo::AutoPtr stream_ptr (new StreamIo (cb));
		self->priv->image = Exiv2::ImageFactory::open (stream_ptr);
		
		return gexiv2_metadata_open_internal (self, error);
	
	} catch (Exiv2::Error &e) {
		g_set_error_literal (error, g_quark_from_string ("GExiv2"), e.code (), e.what ());
	}
	
	return false;
}

// Exiv2 does not today offer a clean way to decode a buffer with only the JFIF APP1 segment,
// where EXIF lives.  This is a common situation when reading EXIF metadata straight from a
// camera (i.e. via gPhoto) where accessing the entire JPEG image is inconvenient.
//
// This method of directly decoding the buffer from offset 10 (where the EXIF data looks like
// a TIFF image) comes from this ticket: http://dev.exiv2.org/issues/show/465  When this ticket
// is closed this code can revert to a direct image decode.  To be more flexible for other
// file formats that encode EXIF via other means (such as PNG), search for the TIFF header and
// decode from there.
gboolean
gexiv2_metadata_from_app1_segment(GExiv2Metadata *self, const guint8 *data, glong n_data, GError **error)
{
	g_return_val_if_fail(GEXIV2_IS_METADATA(self), false);
	g_return_val_if_fail(data != NULL, false);
	g_return_val_if_fail(n_data > 0, false);
	
	int offset = 0;
	while (offset < n_data - 1) {
		if ((data[offset] == 0x4D && data[offset + 1] == 0x4D)
			|| (data[offset] == 0x49 && data[offset + 1] == 0x49))
			break;
		
		offset++;
	}
	
	if (offset >= n_data - 1) {
		g_set_error_literal(error, g_quark_from_string("GExiv2"), 501, "unsupported format");\
		
		return false;
	}
	
	try {
		self->priv->image = Exiv2::ImageFactory::create(Exiv2::ImageType::jpeg);
		Exiv2::ExifParser::decode(self->priv->image->exifData(), data + offset, n_data - offset);
		gexiv2_metadata_init_internal(self);
		
		return true;
	} catch (Exiv2::Error &e) {
		g_set_error_literal(error, g_quark_from_string("GExiv2"), e.code(), e.what());
		
		return false;
	}
}


static gboolean
gexiv2_metadata_save_internal (GExiv2Metadata *self, Exiv2::Image::AutoPtr image, GError **error)
{
	if (image.get () == NULL || ! image->good ()) {
		g_set_error_literal (error, g_quark_from_string ("GExiv2"),
							501, "format seems not to be supported");
		return false;
	}
	
	image->readMetadata ();
		
	Exiv2::AccessMode mode = image->checkMode (Exiv2::mdExif);
	if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite) {
	
		/* For tiff some image data is stored in exif. This should not
		   be overwritten. (see libkexiv2/kexiv2.cpp)
		 */
		if (image->mimeType () == "image/tiff") {
			// FIXME
			/*Exiv2::ExifData new_exif_data = self->priv->exif_data;
			Exiv2::ExifData old_exif_data = image->getExifData ();
			
            new_exif_data["Exif.Image.ImageWidth"];
			new_exif_data["Exif.Image.ImageLength"];
            new_exif_data["Exif.Image.BitsPerSample"];
            new_exif_data["Exif.Image.Compression"];
            new_exif_data["Exif.Image.PhotometricInterpretation"];
            new_exif_data["Exif.Image.FillOrder"];
            new_exif_data["Exif.Image.SamplesPerPixel"];
            new_exif_data["Exif.Image.StripOffsets"];
            new_exif_data["Exif.Image.RowsPerStrip"];
            new_exif_data["Exif.Image.StripByteCounts"];
            new_exif_data["Exif.Image.XResolution"];
            new_exif_data["Exif.Image.YResolution"];
            new_exif_data["Exif.Image.PlanarConfiguration"];
            new_exif_data["Exif.Image.ResolutionUnit"];
            */
            
            image->setExifData (self->priv->image->exifData());
		} else {
			image->setExifData (self->priv->image->exifData());
		}
	}
		
	mode = image->checkMode (Exiv2::mdXmp);
	if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
		image->setXmpData (self->priv->image->xmpData());
	
	mode = image->checkMode (Exiv2::mdIptc);
	if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
		image->setIptcData (self->priv->image->iptcData());
	
	mode = image->checkMode (Exiv2::mdComment);
	if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
		image->setComment (self->priv->comment);
	
    image->writeMetadata ();
    
    return true;
}


gboolean
gexiv2_metadata_save_file (GExiv2Metadata *self, const gchar *path, GError **error)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), false);
	
	try {
		return gexiv2_metadata_save_internal (self, Exiv2::ImageFactory::open (path), error);

	} catch (Exiv2::Error e) {
		g_set_error_literal (error, g_quark_from_string ("GExiv2"), e.code (), e.what ());
	}
	
	return false;
}

gboolean
gexiv2_metadata_save_stream (GExiv2Metadata *self, ManagedStreamCallbacks* cb, GError **error)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), false);
	
	try {
		Exiv2::BasicIo::AutoPtr stream_ptr (new StreamIo (cb));
		
		return gexiv2_metadata_save_internal (self, Exiv2::ImageFactory::open (stream_ptr), error);
	
	} catch (Exiv2::Error &e) {
		g_set_error_literal (error, g_quark_from_string ("GExiv2"), e.code (), e.what ());
	}
	
	return false;
}



gboolean
gexiv2_metadata_has_tag(GExiv2Metadata *self, const gchar* tag)
{
    g_return_val_if_fail(GEXIV2_IS_METADATA(self), false);
    g_return_val_if_fail(tag != NULL, false);
    
    if (gexiv2_metadata_is_xmp_tag(tag))
        return gexiv2_metadata_has_xmp_tag(self, tag);
    
    if (gexiv2_metadata_is_exif_tag(tag))
        return gexiv2_metadata_has_exif_tag(self, tag);
    
    if (gexiv2_metadata_is_iptc_tag(tag))
        return gexiv2_metadata_has_iptc_tag(self, tag);
    
    return false;
}


gboolean
gexiv2_metadata_clear_tag(GExiv2Metadata *self, const gchar* tag)
{
    g_return_val_if_fail(GEXIV2_IS_METADATA(self), false);
    g_return_val_if_fail(tag != NULL, false);
    
    if (gexiv2_metadata_is_xmp_tag(tag))
    	return gexiv2_metadata_clear_xmp_tag(self, tag);
    
    if (gexiv2_metadata_is_exif_tag(tag))
    	return gexiv2_metadata_clear_exif_tag(self, tag);
    
    if (gexiv2_metadata_is_iptc_tag(tag))
    	return gexiv2_metadata_clear_iptc_tag(self, tag);
	
	return false;
}


void
gexiv2_metadata_clear (GExiv2Metadata *self)
{
	g_return_if_fail (GEXIV2_IS_METADATA (self));
	
	gexiv2_metadata_clear_exif (self);
	gexiv2_metadata_clear_xmp (self);
	gexiv2_metadata_clear_iptc (self);
	gexiv2_metadata_clear_comment (self);
	
	self->priv->image->clearMetadata();
}


const gchar*
gexiv2_metadata_get_mime_type (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), NULL);
	
	return self->priv->mime_type;
}


gchar*
gexiv2_metadata_get_date_time (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), NULL);
	
	Exiv2::ExifData& exif_data = self->priv->image->exifData();
	
	if (!exif_data.empty ()) {
		Exiv2::ExifKey key ("Exif.Photo.DateTimeOriginal");
		Exiv2::ExifData::iterator it = exif_data.findKey (key);
		if (it != exif_data.end ())
			return g_strdup (it->toString ().c_str ());
				
		key = Exiv2::ExifKey ("Exif.Image.DateTime");
		it = exif_data.findKey (key);
		if (it != exif_data.end ())
			return g_strdup (it->toString ().c_str ());
		
		key = Exiv2::ExifKey ("Exif.Photo.DateTimeDigitized");
		it = exif_data.findKey (key);
		if (it != exif_data.end ())
			return g_strdup (it->toString ().c_str ());
	}
	
	Exiv2::XmpData& xmp_data = self->priv->image->xmpData();
	
	if (!xmp_data.empty ()) {
		Exiv2::XmpKey key ("Xmp.exif.DateTimeOriginal");
		Exiv2::XmpData::iterator it = xmp_data.findKey (key);
		if (it != xmp_data.end ())
			return g_strdup (it->toString ().c_str ());
		
		key = Exiv2::XmpKey ("Xmp.xmp.CreateDate");
		it = xmp_data.findKey (key);
		if (it != xmp_data.end ())
			return g_strdup (it->toString ().c_str ());
		
		key = Exiv2::XmpKey ("Xmp.exif.DateTimeDigitized");
		it = xmp_data.findKey (key);
		if (it != xmp_data.end ())
			return g_strdup (it->toString ().c_str ());
		
		key = Exiv2::XmpKey ("Xmp.tiff.DateTime");
		it = xmp_data.findKey (key);
		if (it != xmp_data.end ())
			return g_strdup (it->toString ().c_str ());
		
		key = Exiv2::XmpKey ("Xmp.photoshop.DateCreated");
		it = xmp_data.findKey (key);
		if (it != xmp_data.end ())
			return g_strdup (it->toString ().c_str ());
		
		key = Exiv2::XmpKey ("Xmp.xmp.ModifyDate");
		it = xmp_data.findKey (key);
		if (it != xmp_data.end ())
			return g_strdup (it->toString ().c_str ());
		
		key = Exiv2::XmpKey ("Xmp.xmp.MetadataDate");
		it = xmp_data.findKey (key);
		if (it != xmp_data.end ())
			return g_strdup (it->toString ().c_str ());
	}
	
	/* FIXME: handle IPTC Date */
	
	return NULL;
}


void
gexiv2_metadata_set_date_time (GExiv2Metadata *self, const gchar* datetime)
{
	g_return_if_fail (GEXIV2_IS_METADATA (self));
	
	Exiv2::ExifData& exif_data = self->priv->image->exifData();
	Exiv2::XmpData& xmp_data = self->priv->image->xmpData();
	
	exif_data["Exif.Image.DateTime"] = datetime;
	exif_data["Exif.Photo.DateTimeOriginal"] = datetime;
	/* FIXME: Maybe make set of Digitized-Time optional */
	exif_data["Exif.Photo.DateTimeDigitized"] = datetime;
	
	xmp_data["Xmp.exif.DateTimeOriginal"] = datetime;
	/* FIXME: Maybe make set of Digitized-Time optional */
	xmp_data["Xmp.exif.DateTimeDigitized"] = datetime;
	xmp_data["Xmp.xmp.CreateDate"] = datetime;
	xmp_data["Xmp.tiff.DateTime"] = datetime;
	xmp_data["Xmp.photoshop.DateCreated"] = datetime;
	xmp_data["Xmp.xmp.ModifyDate"] = datetime;
	xmp_data["Xmp.xmp.MetadataDate"] = datetime;
	
	/* TODO: handle IPTC data */
}


GExiv2Orientation
gexiv2_metadata_get_orientation (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), GEXIV2_ORIENTATION_UNSPECIFIED);
	
	Exiv2::ExifData& exif_data = self->priv->image->exifData();
	
	if (!exif_data.empty ()) {
	
		/* Because some camera set a wrong standard exif orientation tag,
		   We need to check makernote tags first!
		 */
		Exiv2::ExifKey minolta_key1 ("Exif.MinoltaCs7D.Rotation");
		Exiv2::ExifData::iterator it = exif_data.findKey (minolta_key1);
		if (it != exif_data.end ()) {
			switch (it->toLong ()) {
				case 76:
					return GEXIV2_ORIENTATION_ROT_90;
				case 82:
					return GEXIV2_ORIENTATION_ROT_270;
				default:
					return GEXIV2_ORIENTATION_NORMAL;
			}
		}
		
		Exiv2::ExifKey minolta_key2 ("Exif.MinoltaCs5D.Rotation");
		it = exif_data.findKey (minolta_key2);
		if (it != exif_data.end ()) {
			switch (it->toLong ()) {
				case 76:
					return GEXIV2_ORIENTATION_ROT_90;
				case 82:
					return GEXIV2_ORIENTATION_ROT_270;
				default:
					return GEXIV2_ORIENTATION_NORMAL;
			}
		}
	
		Exiv2::ExifKey std_key ("Exif.Image.Orientation");
		it = exif_data.findKey (std_key);
		if (it != exif_data.end ()) {
            GExiv2Orientation orientation = (GExiv2Orientation) it->toLong ();

			return ((orientation < GEXIV2_ORIENTATION_MIN || 
                    orientation > GEXIV2_ORIENTATION_MAX) ?
                    GEXIV2_ORIENTATION_UNSPECIFIED : orientation);
        }
	}
	
	Exiv2::XmpData& xmp_data = self->priv->image->xmpData();
	
	if (!xmp_data.empty ()) {
		Exiv2::XmpKey tiff_width_key ("Xmp.tiff.ImageWidth");
		Exiv2::XmpData::iterator it = xmp_data.findKey (tiff_width_key);
		if (it != xmp_data.end ()) {
			GExiv2Orientation orientation = (GExiv2Orientation) it->toLong ();

			return ((orientation < GEXIV2_ORIENTATION_MIN || 
                    orientation > GEXIV2_ORIENTATION_MAX) ?
                    GEXIV2_ORIENTATION_UNSPECIFIED : orientation);
        }
	}
	
	return GEXIV2_ORIENTATION_UNSPECIFIED;
}


gboolean
gexiv2_metadata_get_supports_exif (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), false);
	
	return self->priv->supports_exif;
}


gboolean
gexiv2_metadata_get_supports_xmp (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), false);
	
	return self->priv->supports_xmp;
}


gboolean
gexiv2_metadata_get_supports_iptc (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), false);
	
	return self->priv->supports_iptc;
}


void
gexiv2_metadata_set_orientation (GExiv2Metadata *self, GExiv2Orientation orientation)
{
	g_return_if_fail (GEXIV2_IS_METADATA (self));
	g_return_if_fail (orientation <= GEXIV2_ORIENTATION_ROT_270);
	g_return_if_fail (orientation >= GEXIV2_ORIENTATION_UNSPECIFIED);
	
	Exiv2::ExifData& exif_data = self->priv->image->exifData();
	Exiv2::XmpData& xmp_data = self->priv->image->xmpData();
	
	exif_data["Exif.Image.Orientation"] = static_cast<uint16_t> (orientation);
	xmp_data["Xmp.tiff.ImageWidth"] = static_cast<uint16_t> (orientation);
	
	Exiv2::ExifKey minolta_key1 ("Exif.MinoltaCs7D.Rotation");
	Exiv2::ExifData::iterator it = exif_data.findKey (minolta_key1);
	if (it != exif_data.end ())
		exif_data.erase (it);
	
	Exiv2::ExifKey minolta_key2 ("Exif.MinoltaCs5D.Rotation");
	it = exif_data.findKey (minolta_key2);
	if (it != exif_data.end ())
		exif_data.erase (it);
}


gint
gexiv2_metadata_get_pixel_width (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), -1);
	
	return self->priv->pixel_width;
}


gint
gexiv2_metadata_get_pixel_height (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), -1);
	
	return self->priv->pixel_height;
}


gint
gexiv2_metadata_get_metadata_pixel_width (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), -1);
	
	Exiv2::ExifData& exif_data = self->priv->image->exifData();
	
	if (!exif_data.empty ()) {
		Exiv2::ExifKey pixel_dim_key ("Exif.Photo.PixelXDimension");
		Exiv2::ExifData::iterator it = exif_data.findKey (pixel_dim_key);
		if (it != exif_data.end ())
			return it->toLong ();
	
		Exiv2::ExifKey image_width_key ("Exif.Image.ImageWidth");
		it = exif_data.findKey (image_width_key);
		if (it != exif_data.end ())
			return it->toLong ();
	}
	
	Exiv2::XmpData& xmp_data = self->priv->image->xmpData();
	
	if (!exif_data.empty ()) {
		Exiv2::XmpKey tiff_width_key ("Xmp.tiff.ImageWidth");
		Exiv2::XmpData::iterator it = xmp_data.findKey (tiff_width_key);
		if (it != xmp_data.end ())
			return it->toLong ();
	
		Exiv2::XmpKey xmp_exif_key ("Xmp.exif.PixelXDimension");
		it = xmp_data.findKey (xmp_exif_key);
		if (it != xmp_data.end ())
			return it->toLong ();
	}
	
	return -1;
}


gint
gexiv2_metadata_get_metadata_pixel_height (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), -1);
	
	Exiv2::ExifData& exif_data = self->priv->image->exifData();
	
	if (!exif_data.empty ()) {
		Exiv2::ExifKey pixel_dim_key ("Exif.Photo.PixelYDimension");
		Exiv2::ExifData::iterator it = exif_data.findKey (pixel_dim_key);
		if (it != exif_data.end ())
			return it->toLong ();
	
		Exiv2::ExifKey image_height_key ("Exif.Image.ImageHeight");
		it = exif_data.findKey (image_height_key);
		if (it != exif_data.end ())
			return it->toLong ();
	}
	
	Exiv2::XmpData& xmp_data = self->priv->image->xmpData();
	
	if (!exif_data.empty ()) {
		Exiv2::XmpKey tiff_height_key ("Xmp.tiff.ImageHeight");
		Exiv2::XmpData::iterator it = xmp_data.findKey (tiff_height_key);
		if (it != xmp_data.end ())
			return it->toLong ();
	
		Exiv2::XmpKey xmp_exif_key ("Xmp.exif.PixelYDimension");
		it = xmp_data.findKey (xmp_exif_key);
		if (it != xmp_data.end ())
			return it->toLong ();
	}
	
	return -1;
}


void
gexiv2_metadata_set_metadata_pixel_width (GExiv2Metadata *self, gint width)
{
	g_return_if_fail (GEXIV2_IS_METADATA (self));
	
	Exiv2::ExifData& exif_data = self->priv->image->exifData();
	Exiv2::XmpData& xmp_data = self->priv->image->xmpData();
	
	exif_data["Exif.Photo.PixelXDimension"] = static_cast<uint32_t>(width);
	exif_data["Exif.Image.ImageWidth"] = static_cast<uint32_t>(width);
	xmp_data["Xmp.tiff.ImageWidth"] = static_cast<uint32_t>(width);
	xmp_data["Xmp.exif.PixelYDimension"] = static_cast<uint32_t>(width);
}


void
gexiv2_metadata_set_metadata_pixel_height (GExiv2Metadata *self, gint height)
{
	g_return_if_fail (GEXIV2_IS_METADATA (self));
	
	Exiv2::ExifData& exif_data = self->priv->image->exifData();
	Exiv2::XmpData& xmp_data = self->priv->image->xmpData();
	
	exif_data ["Exif.Photo.PixelXDimension"] = static_cast<uint32_t>(height);
	exif_data ["Exif.Image.ImageHeight"] = static_cast<uint32_t>(height);
	xmp_data ["Xmp.tiff.ImageHeight"] = static_cast<uint32_t>(height);
	xmp_data ["Xmp.exif.PixelXDimension"] = static_cast<uint32_t>(height);
}


gchar*
gexiv2_metadata_get_comment (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), NULL);
	
	gchar* str = self->priv->comment;
	if (str != NULL && *str != '\0')
		return g_strdup(str);
	
	str = gexiv2_metadata_get_exif_tag_interpreted_string (self, "Exif.Image.ImageDescription");
	if (str != NULL && *str != '\0')
		return str;
	else
		g_free (str);
	
	str = gexiv2_metadata_get_exif_tag_interpreted_string (self, "Exif.Photo.UserComment");
	if (str != NULL && *str != '\0')
		return str;
	else
		g_free (str);
	
	str = gexiv2_metadata_get_exif_tag_interpreted_string (self, "Exif.Image.XPComment");
	if (str != NULL && *str != '\0')
		return str;
	else
		g_free (str);
	
	str = gexiv2_metadata_get_exif_tag_interpreted_string (self, "Iptc.Application2.Caption");
	if (str != NULL && *str != '\0')
		return str;
	else
		g_free (str);
	
	return NULL;
}


void
gexiv2_metadata_set_comment (GExiv2Metadata *self, const gchar* comment)
{
	g_return_if_fail (GEXIV2_IS_METADATA (self));
	
	Exiv2::ExifData& exif_data = self->priv->image->exifData();
	Exiv2::IptcData& iptc_data = self->priv->image->iptcData();
	
	gexiv2_metadata_set_comment_internal (self, (comment != NULL) ? comment : "");
	exif_data ["Exif.Image.ImageDescription"] = comment;
	exif_data ["Exif.Photo.UserComment"] = comment;
	exif_data ["Exif.Image.XPComment"] = comment;
	exif_data ["Iptc.Application2.Caption"] = comment;
}


void
gexiv2_metadata_clear_comment (GExiv2Metadata *self)
{
	g_return_if_fail (GEXIV2_IS_METADATA (self));
	
	/* dont'delete the comment field */
	gexiv2_metadata_set_comment_internal (self, "");
}


gchar**
gexiv2_metadata_get_keywords (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), NULL);
	
	gchar** keywords = gexiv2_metadata_get_xmp_tag_multiple (self, "Xmp.dc.subject");
	if (keywords != NULL && (*keywords) != NULL)
		return keywords;
	
	return gexiv2_metadata_get_iptc_keywords (self);
}


void
gexiv2_metadata_set_keywords (GExiv2Metadata *self, const gchar** keywords)
{
	g_return_if_fail (GEXIV2_IS_METADATA (self));
	
	gexiv2_metadata_set_xmp_tag_multiple (self, "Xmp.dc.subject", keywords);
	gexiv2_metadata_set_iptc_tag_multiple (self, "Iptc.Application2.Keywords", keywords);
}


guint
gexiv2_metadata_get_rating (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), 0);
	
	guint rating = gexiv2_metadata_get_xmp_tag_long (self, "Xmp.xmp.Rating");
	
	if (rating != 0)
		return rating;
		
	rating = gexiv2_metadata_get_exif_tag_long (self, "Exif.Image.Rating");
	
	return rating;
}


void
gexiv2_metadata_set_rating (GExiv2Metadata *self, guint rating)
{
	gexiv2_metadata_set_xmp_tag_long (self, "Xmp.xmp.Rating", rating);
	gexiv2_metadata_set_exif_tag_long (self, "Exif.Image.Rating", rating);
}


gboolean
gexiv2_metadata_is_exif_tag(const gchar* tag)
{
    g_return_val_if_fail(tag != NULL, false);
    
    return g_ascii_strncasecmp("Exif.", tag, 4) == 0;
}


gboolean
gexiv2_metadata_is_xmp_tag(const gchar* tag)
{
    g_return_val_if_fail(tag != NULL, false);
    
    return g_ascii_strncasecmp("Xmp.", tag, 4) == 0;
}


gboolean
gexiv2_metadata_is_iptc_tag(const gchar* tag)
{
    g_return_val_if_fail(tag != NULL, false);
    
    return g_ascii_strncasecmp("Iptc.", tag, 5) == 0;
}


gchar*
gexiv2_metadata_get_tag_string (GExiv2Metadata *self, const gchar* tag)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), NULL);
	g_return_val_if_fail (tag != NULL, NULL);
	
	if (gexiv2_metadata_is_xmp_tag(tag))
		return gexiv2_metadata_get_xmp_tag_string (self, tag);
		
	if (gexiv2_metadata_is_exif_tag(tag))
		return gexiv2_metadata_get_exif_tag_string (self, tag);
		
	if (gexiv2_metadata_is_iptc_tag(tag))
		return gexiv2_metadata_get_iptc_tag_string (self, tag);
	
	return NULL;
}


gboolean
gexiv2_metadata_set_tag_string (GExiv2Metadata *self, const gchar* tag, const gchar* value)
{
    g_return_val_if_fail(GEXIV2_IS_METADATA(self), false);
    g_return_val_if_fail(tag != NULL, false);
    
    if (gexiv2_metadata_is_xmp_tag(tag))
        return gexiv2_metadata_set_xmp_tag_string(self, tag, value);
    
    if (gexiv2_metadata_is_exif_tag(tag))
        return gexiv2_metadata_set_exif_tag_string(self, tag, value);
    
    if (gexiv2_metadata_is_iptc_tag(tag))
        return gexiv2_metadata_set_iptc_tag_string(self, tag, value);
    
    return false;
}


gchar*
gexiv2_metadata_get_tag_interpreted_string (GExiv2Metadata *self, const gchar* tag)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), NULL);
	g_return_val_if_fail (tag != NULL, NULL);
	
	if (gexiv2_metadata_is_xmp_tag(tag))
		return gexiv2_metadata_get_xmp_tag_interpreted_string (self, tag);
		
	if (gexiv2_metadata_is_exif_tag(tag))
		return gexiv2_metadata_get_exif_tag_interpreted_string (self, tag);
		
	if (gexiv2_metadata_is_iptc_tag(tag))
		return gexiv2_metadata_get_iptc_tag_interpreted_string (self, tag);
	
	return NULL;
}


gchar**
gexiv2_metadata_get_tag_multiple(GExiv2Metadata *self, const gchar* tag)
{
	g_return_val_if_fail(GEXIV2_IS_METADATA(self), NULL);
	g_return_val_if_fail(tag != NULL, NULL);
	
	if (gexiv2_metadata_is_xmp_tag(tag))
		return gexiv2_metadata_get_xmp_tag_multiple(self, tag);
	
	if (gexiv2_metadata_is_iptc_tag(tag))
		return gexiv2_metadata_get_iptc_tag_multiple(self, tag);
	
	return NULL;
}


gboolean
gexiv2_metadata_set_tag_multiple(GExiv2Metadata *self, const gchar* tag, const gchar** values)
{
	g_return_val_if_fail(GEXIV2_IS_METADATA(self), false);
	g_return_val_if_fail(tag != NULL, false);
	g_return_val_if_fail(values != NULL, false);
	
	if (gexiv2_metadata_is_xmp_tag(tag))
		return gexiv2_metadata_set_xmp_tag_multiple(self, tag, values);
	
	if (gexiv2_metadata_is_iptc_tag(tag))
		return gexiv2_metadata_set_iptc_tag_multiple(self, tag, values);
	
	return false;
}

glong
gexiv2_metadata_get_tag_long(GExiv2Metadata *self, const gchar* tag)
{
    g_return_val_if_fail(GEXIV2_IS_METADATA(self), 0);
    g_return_val_if_fail(tag != NULL, 0);
    
    if (gexiv2_metadata_is_xmp_tag(tag))
        return gexiv2_metadata_get_xmp_tag_long(self, tag);
    
    if (gexiv2_metadata_is_exif_tag(tag))
        return gexiv2_metadata_get_exif_tag_long(self, tag);
    
    return 0;
}


gboolean
gexiv2_metadata_set_tag_long(GExiv2Metadata *self, const gchar* tag, glong value)
{
    g_return_val_if_fail(GEXIV2_IS_METADATA(self), false);
    g_return_val_if_fail(tag != NULL, false);
    
    if (gexiv2_metadata_is_xmp_tag(tag))
        return gexiv2_metadata_set_xmp_tag_long(self, tag, value);
    
    if (gexiv2_metadata_is_exif_tag(tag))
        return gexiv2_metadata_set_exif_tag_long(self, tag, value);
    
    return false;
}


gboolean
gexiv2_metadata_get_exposure_time (GExiv2Metadata *self, gint *nom, gint *den)
{
	return gexiv2_metadata_get_exif_tag_rational (self, "Exif.Photo.ExposureTime", nom, den);
}


gdouble
gexiv2_metadata_get_fnumber (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), -1.0);
	
	gint nom;
	gint den;
	
	if ( ! gexiv2_metadata_get_exif_tag_rational (self, "Exif.Photo.ISOSpeedRatings", &nom, &den))
		return -1.0;
	
	if (den == 0.0)
		return -1.0;
		
	return (nom / den);
}


gdouble
gexiv2_metadata_get_focal_length (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), NULL);
	
	gint nom;
	gint den;
	
	if ( ! gexiv2_metadata_get_exif_tag_rational (self, "Exif.Photo.FocalLength", &nom, &den))
		return -1.0;
	
	if (den == 0.0)
		return -1.0;
		
	return (nom / den);
}


gint gexiv2_metadata_get_iso_speed (GExiv2Metadata *self)
{
	g_return_val_if_fail (GEXIV2_IS_METADATA (self), NULL);
	
	return (gint) gexiv2_metadata_get_exif_tag_long (self, "Exif.Photo.ISOSpeedRatings");
}


gchar*
gexiv2_metadata_get_camera_model (GExiv2Metadata *self)
{
	return gexiv2_metadata_get_exif_tag_string (self, "Exif.Image.Model");
}


GExiv2PreviewProperties**
gexiv2_metadata_get_preview_properties (GExiv2Metadata *self)
{
	g_return_val_if_fail(GEXIV2_IS_METADATA(self), NULL);
	
	return self->priv->preview_properties;
}


GExiv2PreviewImage*
gexiv2_metadata_get_preview_image (GExiv2Metadata *self, GExiv2PreviewProperties *props)
{
	g_return_val_if_fail(GEXIV2_IS_METADATA(self), NULL);
	g_return_val_if_fail(GEXIV2_IS_PREVIEW_PROPERTIES(props), NULL);
	
	return gexiv2_preview_image_new(self->priv->preview_manager, *props->priv->props);
}


gboolean
gexiv2_metadata_get_exif_thumbnail (GExiv2Metadata *self, guint8** buffer, gint *size)
{
	g_return_val_if_fail(GEXIV2_IS_METADATA(self), FALSE);
	g_return_val_if_fail(buffer != NULL, FALSE);
	g_return_val_if_fail(size != NULL, FALSE);
	
	Exiv2::ExifThumb thumb = Exiv2::ExifThumb(self->priv->image->exifData());
	Exiv2::DataBuf data = thumb.copy();
	if (data.pData_ == NULL)
		return FALSE;
	
	*buffer = (guint8*) g_malloc(data.size_);
	memcpy(*buffer, data.pData_, data.size_);
	*size = data.size_;
	
	return true;
}


gboolean
gexiv2_metadata_set_exif_thumbnail_from_file (GExiv2Metadata *self, const gchar *path, GError **error)
{
	g_return_val_if_fail(GEXIV2_IS_METADATA(self), false);
	g_return_val_if_fail(path != NULL && g_utf8_strlen(path, -1) > 0, false);
	
	Exiv2::ExifThumb thumb = Exiv2::ExifThumb(self->priv->image->exifData());
	try {
		thumb.setJpegThumbnail(std::string(path));
		
		return true;
	} catch (Exiv2::Error &e) {
		g_set_error_literal (error, g_quark_from_string ("GExiv2"), e.code (), e.what ());
	}
	
	return false;
}


void
gexiv2_metadata_set_exif_thumbnail_from_buffer (GExiv2Metadata *self, const guint8 *buffer, gint size)
{
	g_return_if_fail(GEXIV2_IS_METADATA(self));
	g_return_if_fail(buffer != NULL);
	g_return_if_fail(size > 0);
	
	Exiv2::ExifThumb thumb = Exiv2::ExifThumb(self->priv->image->exifData());
	thumb.setJpegThumbnail(buffer, size);
}


void
gexiv2_metadata_erase_exif_thumbnail (GExiv2Metadata *self)
{
	g_return_if_fail(GEXIV2_IS_METADATA(self));
	
	Exiv2::ExifThumb thumb = Exiv2::ExifThumb(self->priv->image->exifData());
	thumb.erase();
}


const gchar*
gexiv2_metadata_get_tag_label (const gchar *tag)
{
	g_return_val_if_fail(tag != NULL, NULL);
	
	if (gexiv2_metadata_is_xmp_tag(tag))
		return gexiv2_metadata_get_xmp_tag_label(tag);
	
	if (gexiv2_metadata_is_exif_tag(tag))
		return gexiv2_metadata_get_exif_tag_label(tag);
	
	if (gexiv2_metadata_is_iptc_tag(tag))
		return gexiv2_metadata_get_iptc_tag_label(tag);
	
	return NULL;
}


const gchar*
gexiv2_metadata_get_tag_description (const gchar *tag)
{
	g_return_val_if_fail(tag != NULL, NULL);
	
	if (gexiv2_metadata_is_xmp_tag(tag))
		return gexiv2_metadata_get_xmp_tag_description(tag);
	
	if (gexiv2_metadata_is_exif_tag(tag))
		return gexiv2_metadata_get_exif_tag_description(tag);
	
	if (gexiv2_metadata_is_iptc_tag(tag))
		return gexiv2_metadata_get_iptc_tag_description(tag);
	
	return NULL;
}

G_END_DECLS
