/* Eye Of Mate - EOM Metadata Details
 *
 * Copyright (C) 2006 The Free Software Foundation
 *
 * Author: Lucas Rocha <lucasr@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 of the License, 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "eom-metadata-details.h"
#include "eom-util.h"

#if HAVE_EXIF
#include <libexif/exif-entry.h>
#include <libexif/exif-utils.h>
#endif
#if HAVE_EXEMPI
#include <exempi/xmp.h>
#include <exempi/xmpconsts.h>
#endif

#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include <string.h>
#include <math.h>

typedef enum {
    EXIF_CATEGORY_CAMERA,
    EXIF_CATEGORY_IMAGE_DATA,
    EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS,
    EXIF_CATEGORY_GPS_DATA,
    EXIF_CATEGORY_MAKER_NOTE,
    EXIF_CATEGORY_OTHER,
#ifdef HAVE_EXEMPI
    XMP_CATEGORY_EXIF,
    XMP_CATEGORY_IPTC,
    XMP_CATEGORY_RIGHTS,
    XMP_CATEGORY_OTHER
#endif
} MetadataCategory;

typedef struct {
    char *label;
    char *path;
} ExifCategoryInfo;

static ExifCategoryInfo exif_categories[] = {
    { N_("Camera"),                  "0" },
    { N_("Image Data"),              "1" },
    { N_("Image Taking Conditions"), "2" },
    { N_("GPS Data"),                "3" },
    { N_("Maker Note"),              "4" },
    { N_("Other"),                   "5" },
#ifdef HAVE_EXEMPI
    { N_("XMP Exif"),                "6" },
    { N_("XMP IPTC"),                "7" },
    { N_("XMP Rights Management"),   "8" },
    { N_("XMP Other"),               "9" },
#endif
    { NULL, NULL }
};

typedef struct {
    int id;
    MetadataCategory category;
} ExifTagCategory;

#ifdef HAVE_EXIF
static ExifTagCategory exif_tag_category_map[] = {
    { EXIF_TAG_INTEROPERABILITY_INDEX,       EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_INTEROPERABILITY_VERSION,     EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_IMAGE_WIDTH,                  EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_IMAGE_LENGTH,                 EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_BITS_PER_SAMPLE,              EXIF_CATEGORY_CAMERA },
    { EXIF_TAG_COMPRESSION,                  EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_PHOTOMETRIC_INTERPRETATION,   EXIF_CATEGORY_OTHER},
    { EXIF_TAG_FILL_ORDER,                   EXIF_CATEGORY_OTHER},
    { EXIF_TAG_DOCUMENT_NAME,                EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_IMAGE_DESCRIPTION,            EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_MAKE,                         EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_MODEL,                        EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_STRIP_OFFSETS,                EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_ORIENTATION,                  EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_SAMPLES_PER_PIXEL,            EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_ROWS_PER_STRIP,               EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_STRIP_BYTE_COUNTS,            EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_X_RESOLUTION,                 EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_Y_RESOLUTION,                 EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_PLANAR_CONFIGURATION,         EXIF_CATEGORY_OTHER},
    { EXIF_TAG_RESOLUTION_UNIT,              EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_TRANSFER_FUNCTION,            EXIF_CATEGORY_OTHER},
    { EXIF_TAG_SOFTWARE,                     EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_DATE_TIME,                    EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_ARTIST,                       EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_WHITE_POINT,                  EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_PRIMARY_CHROMATICITIES,       EXIF_CATEGORY_OTHER},
    { EXIF_TAG_TRANSFER_RANGE,               EXIF_CATEGORY_OTHER},
    { EXIF_TAG_JPEG_PROC,                    EXIF_CATEGORY_OTHER},
    { EXIF_TAG_JPEG_INTERCHANGE_FORMAT,      EXIF_CATEGORY_OTHER},
    { EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, },
    { EXIF_TAG_YCBCR_COEFFICIENTS,           EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_YCBCR_SUB_SAMPLING,           EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_YCBCR_POSITIONING,            EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_REFERENCE_BLACK_WHITE,        EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_RELATED_IMAGE_FILE_FORMAT,    EXIF_CATEGORY_OTHER},
    { EXIF_TAG_RELATED_IMAGE_WIDTH,          EXIF_CATEGORY_OTHER},
    { EXIF_TAG_RELATED_IMAGE_LENGTH,         EXIF_CATEGORY_OTHER},
    { EXIF_TAG_CFA_REPEAT_PATTERN_DIM,       EXIF_CATEGORY_OTHER},
    { EXIF_TAG_CFA_PATTERN,                  EXIF_CATEGORY_OTHER},
    { EXIF_TAG_BATTERY_LEVEL,                EXIF_CATEGORY_OTHER},
    { EXIF_TAG_COPYRIGHT,                    EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_EXPOSURE_TIME,                EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_FNUMBER,                      EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_IPTC_NAA,                     EXIF_CATEGORY_OTHER},
    { EXIF_TAG_EXIF_IFD_POINTER,             EXIF_CATEGORY_OTHER},
    { EXIF_TAG_INTER_COLOR_PROFILE,          EXIF_CATEGORY_OTHER},
    { EXIF_TAG_EXPOSURE_PROGRAM,             EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_SPECTRAL_SENSITIVITY,         EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_GPS_INFO_IFD_POINTER,         EXIF_CATEGORY_OTHER},
    { EXIF_TAG_ISO_SPEED_RATINGS,            EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_OECF,                         EXIF_CATEGORY_OTHER},
    { EXIF_TAG_EXIF_VERSION,                 EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_DATE_TIME_ORIGINAL,           EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_DATE_TIME_DIGITIZED,          EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_COMPONENTS_CONFIGURATION,     EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_COMPRESSED_BITS_PER_PIXEL,    EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_SHUTTER_SPEED_VALUE,          EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_APERTURE_VALUE,               EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_BRIGHTNESS_VALUE,             EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_EXPOSURE_BIAS_VALUE,          EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_MAX_APERTURE_VALUE,           EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_SUBJECT_DISTANCE,             EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_METERING_MODE,                EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_LIGHT_SOURCE,                 EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_FLASH,                        EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_FOCAL_LENGTH,                 EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_SUBJECT_AREA,                 EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_MAKER_NOTE,                   EXIF_CATEGORY_OTHER},
    { EXIF_TAG_USER_COMMENT,                 EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_SUBSEC_TIME,                  EXIF_CATEGORY_OTHER},
    { EXIF_TAG_SUB_SEC_TIME_ORIGINAL,        EXIF_CATEGORY_OTHER},
    { EXIF_TAG_SUB_SEC_TIME_DIGITIZED,       EXIF_CATEGORY_OTHER},
    { EXIF_TAG_FLASH_PIX_VERSION,            EXIF_CATEGORY_OTHER},
    { EXIF_TAG_COLOR_SPACE,                  EXIF_CATEGORY_OTHER},
    { EXIF_TAG_PIXEL_X_DIMENSION,            EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_PIXEL_Y_DIMENSION,            EXIF_CATEGORY_IMAGE_DATA},
    { EXIF_TAG_RELATED_SOUND_FILE,           EXIF_CATEGORY_OTHER},
    { EXIF_TAG_INTEROPERABILITY_IFD_POINTER, EXIF_CATEGORY_OTHER},
    { EXIF_TAG_FLASH_ENERGY,                 EXIF_CATEGORY_OTHER },
    { EXIF_TAG_SPATIAL_FREQUENCY_RESPONSE,   EXIF_CATEGORY_OTHER},
    { EXIF_TAG_FOCAL_PLANE_X_RESOLUTION,     EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_FOCAL_PLANE_Y_RESOLUTION,     EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_FOCAL_PLANE_RESOLUTION_UNIT,  EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_SUBJECT_LOCATION,             EXIF_CATEGORY_OTHER},
    { EXIF_TAG_EXPOSURE_INDEX,               EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_SENSING_METHOD,               EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_FILE_SOURCE,                  EXIF_CATEGORY_OTHER},
    { EXIF_TAG_SCENE_TYPE,                   EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_NEW_CFA_PATTERN,              EXIF_CATEGORY_OTHER},
    { EXIF_TAG_CUSTOM_RENDERED,              EXIF_CATEGORY_OTHER},
    { EXIF_TAG_EXPOSURE_MODE,                EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_WHITE_BALANCE,                EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_DIGITAL_ZOOM_RATIO,           EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_FOCAL_LENGTH_IN_35MM_FILM,    EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_SCENE_CAPTURE_TYPE,           EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_GAIN_CONTROL,                 EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_CONTRAST,                     EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_SATURATION,                   EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_SHARPNESS,                    EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_DEVICE_SETTING_DESCRIPTION,   EXIF_CATEGORY_CAMERA},
    { EXIF_TAG_SUBJECT_DISTANCE_RANGE,       EXIF_CATEGORY_IMAGE_TAKING_CONDITIONS},
    { EXIF_TAG_IMAGE_UNIQUE_ID,              EXIF_CATEGORY_IMAGE_DATA},
    { -1, -1 }
};
#endif

#define MODEL_COLUMN_ATTRIBUTE 0
#define MODEL_COLUMN_VALUE     1

struct _EomMetadataDetailsPrivate {
    GtkTreeModel *model;

    GHashTable   *id_path_hash;
    GHashTable   *id_path_hash_mnote;
};

static char*  set_row_data (GtkTreeStore *store, char *path, char *parent, const char *attribute, const char *value);

static void eom_metadata_details_reset (EomMetadataDetails *exif_details);

G_DEFINE_TYPE_WITH_PRIVATE (EomMetadataDetails, eom_metadata_details, GTK_TYPE_TREE_VIEW)

static void
eom_metadata_details_dispose (GObject *object)
{
    EomMetadataDetailsPrivate *priv;

    priv = EOM_METADATA_DETAILS (object)->priv;

    if (priv->model) {
        g_object_unref (priv->model);
        priv->model = NULL;
    }

    if (priv->id_path_hash) {
        g_hash_table_destroy (priv->id_path_hash);
        priv->id_path_hash = NULL;
    }

    if (priv->id_path_hash_mnote) {
        g_hash_table_destroy (priv->id_path_hash_mnote);
        priv->id_path_hash_mnote = NULL;
    }
    G_OBJECT_CLASS (eom_metadata_details_parent_class)->dispose (object);
}

static void
eom_metadata_details_init (EomMetadataDetails *details)
{
    EomMetadataDetailsPrivate *priv;
    GtkTreeViewColumn *column;
    GtkCellRenderer *cell;

    details->priv = eom_metadata_details_get_instance_private (details);

    priv = details->priv;

    priv->model = GTK_TREE_MODEL (gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_STRING));
    priv->id_path_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
    priv->id_path_hash_mnote = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);

    /* Tag name column */
    cell = gtk_cell_renderer_text_new ();
    column = gtk_tree_view_column_new_with_attributes (_("Tag"), cell,
                                                       "text", MODEL_COLUMN_ATTRIBUTE,
                                                       NULL);
    gtk_tree_view_append_column (GTK_TREE_VIEW (details), column);

    /* Value column */
    cell = gtk_cell_renderer_text_new ();
    g_object_set (cell,
                  "editable", TRUE,
                  NULL);
    column = gtk_tree_view_column_new_with_attributes (_("Value"), cell,
                                                       "text", MODEL_COLUMN_VALUE,
                                                       NULL);
    gtk_tree_view_append_column (GTK_TREE_VIEW (details), column);

    eom_metadata_details_reset (details);

    gtk_tree_view_set_model (GTK_TREE_VIEW (details),
                             GTK_TREE_MODEL (priv->model));
}

static void
eom_metadata_details_class_init (EomMetadataDetailsClass *klass)
{
    GObjectClass *object_class = (GObjectClass*) klass;

    object_class->dispose = eom_metadata_details_dispose;
}

#ifdef HAVE_EXIF
static MetadataCategory
get_exif_category (ExifEntry *entry)
{
    MetadataCategory cat = EXIF_CATEGORY_OTHER;
    int i;

    /* Some GPS tag IDs overlap with other ones, so check the IFD */
    if (exif_entry_get_ifd (entry) == EXIF_IFD_GPS) {
        return EXIF_CATEGORY_GPS_DATA;
    }

    for (i = 0; exif_tag_category_map [i].id != -1; i++) {
        if (exif_tag_category_map[i].id == (int) entry->tag) {
            cat = exif_tag_category_map[i].category;
            break;
        }
    }

    return cat;
}
#endif

static char*
set_row_data (GtkTreeStore *store, char *path, char *parent, const char *attribute, const char *value)
{
    GtkTreeIter iter;
    gchar *utf_attribute = NULL;
    gchar *utf_value = NULL;
    gboolean iter_valid = FALSE;

    if (!attribute) return NULL;

    if (path != NULL) {
        iter_valid = gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store), &iter, path);
    }

    if (!iter_valid) {
        GtkTreePath *tree_path;
        GtkTreeIter parent_iter;
        gboolean parent_valid = FALSE;

        if (parent != NULL) {
            parent_valid = gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store),
                                                                &parent_iter,
                                                                parent);
        }

        gtk_tree_store_append (store, &iter, parent_valid ? &parent_iter : NULL);

        if (path == NULL) {
            tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);

            if (tree_path != NULL) {
                path = gtk_tree_path_to_string (tree_path);
                gtk_tree_path_free (tree_path);
            }
        }
    }

    utf_attribute = eom_util_make_valid_utf8 (attribute);

    gtk_tree_store_set (store, &iter, MODEL_COLUMN_ATTRIBUTE, utf_attribute, -1);
    g_free (utf_attribute);

    if (value != NULL) {
        utf_value = eom_util_make_valid_utf8 (value);
        gtk_tree_store_set (store, &iter, MODEL_COLUMN_VALUE, utf_value, -1);
        g_free (utf_value);
    }

    return path;
}

#ifdef HAVE_EXIF

static const char *
eom_exif_entry_get_value (ExifEntry    *e,
                          char         *buf,
                          guint         n_buf)
{
    ExifByteOrder bo;

    /* For now we only want to reformat some GPS values */
    if (G_LIKELY (exif_entry_get_ifd (e) != EXIF_IFD_GPS))
        return exif_entry_get_value (e, buf, n_buf);

    bo = exif_data_get_byte_order (e->parent->parent);

    /* Cast to number to avoid warnings about values not in enumeration */
    switch ((guint16) e->tag) {
        case EXIF_TAG_GPS_LATITUDE:
        case EXIF_TAG_GPS_LONGITUDE:
        {
            gsize rational_size;
            ExifRational r;
            gfloat h = 0., m = 0., s = 0.;


            rational_size = exif_format_get_size (EXIF_FORMAT_RATIONAL);
            if (G_UNLIKELY (e->components != 3 ||
                e->format != EXIF_FORMAT_RATIONAL))
                return exif_entry_get_value (e, buf, n_buf);

            r = exif_get_rational (e->data, bo);
            if (r.denominator != 0)
                h = (gfloat)r.numerator / r.denominator;

            r = exif_get_rational (e->data + rational_size, bo);
			if (r.denominator != 0) {
				if (r.numerator != 0) {
					m = (gfloat)r.numerator /
					    (gfloat)r.denominator;
				} else {
					double integ;

					m = (gfloat)(modf (h, &integ) * 60.0);
					h = (gfloat) integ;
				}
			}

            r = exif_get_rational (e->data + (2 * rational_size),
                           bo);
			if (r.denominator != 0) {
				if (r.numerator != 0) {
					s = (gfloat)r.numerator /
					    (gfloat)r.denominator;
				} else {
					double integ;

					s = (gfloat)(modf (m, &integ) * 60.0);
					m = (gfloat) integ;
				}
			}

			if (s != 0.0) {
                g_snprintf (buf, n_buf,
                        "%.0f° %.0f' %.2f\"",
                        h, m, s);
            } else {
                g_snprintf (buf, n_buf,
                        "%.0f° %.2f'",
                        h, m);
            }

            break;
        }
        case EXIF_TAG_GPS_LATITUDE_REF:
        case EXIF_TAG_GPS_LONGITUDE_REF:
        {
            if (G_UNLIKELY (e->components != 2 ||
                e->format != EXIF_FORMAT_ASCII))
                return exif_entry_get_value (e, buf, n_buf);

            switch (e->data[0]) {
            case 'N':
                g_snprintf (buf, n_buf, "%s", _("North"));
                break;
            case 'E':
                g_snprintf (buf, n_buf, "%s", _("East"));
                break;
            case 'W':
                g_snprintf (buf, n_buf, "%s", _("West"));
                break;
            case 'S':
                g_snprintf (buf, n_buf, "%s", _("South"));
                break;
            default:
                return exif_entry_get_value (e, buf, n_buf);
                break;
            }
            break;
        }
        default:
            return exif_entry_get_value (e, buf, n_buf);
            break;
    }

    return buf;
}

static void
exif_entry_cb (ExifEntry *entry, gpointer data)
{
    GtkTreeStore *store;
    EomMetadataDetails *view;
    EomMetadataDetailsPrivate *priv;
    MetadataCategory cat;
    ExifIfd ifd = exif_entry_get_ifd (entry);
    char *path;
    char b[1024];
    const gint key = ifd << 16 | entry->tag;

    /* This should optimize away if comparision is correct */
    g_warn_if_fail (EXIF_IFD_COUNT <= G_MAXUINT16);

    view = EOM_METADATA_DETAILS (data);
    priv = view->priv;

    store = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view)));

    /* Take the tag's IFD into account when caching their GtkTreePaths.
     * That should fix key collisions for tags that have the same number
     * but are stored in different IFDs. Exif tag numbers are 16-bit
     * values so we should be able to set the high word to the IFD number.
     */
    path = g_hash_table_lookup (priv->id_path_hash, GINT_TO_POINTER (key));

    if (path != NULL) {
        set_row_data (store,
                      path,
                      NULL,
                      exif_tag_get_name_in_ifd (entry->tag, ifd),
                      eom_exif_entry_get_value (entry, b, sizeof(b)));
    } else {

        ExifMnoteData *mnote = (entry->tag == EXIF_TAG_MAKER_NOTE ?
                                exif_data_get_mnote_data (entry->parent->parent) : NULL);

        if (mnote) {
            // Supported MakerNote Found
            unsigned int i, c = exif_mnote_data_count (mnote);

            for (i = 0; i < c; i++) {
                path = g_hash_table_lookup (priv->id_path_hash_mnote, GINT_TO_POINTER (i));
                if (path != NULL) {
                    set_row_data (store, path, NULL,
                                  exif_mnote_data_get_title (mnote, i),
                                  exif_mnote_data_get_value (mnote, i, b, sizeof(b)));
                } else {
                    path = set_row_data (store,
                                         NULL,
                                         exif_categories[EXIF_CATEGORY_MAKER_NOTE].path,
                                         exif_mnote_data_get_title (mnote, i),
                                         exif_mnote_data_get_value (mnote, i, b, sizeof(b)));
                    g_hash_table_insert (priv->id_path_hash_mnote, GINT_TO_POINTER (i), path);
                }
            }
        } else {
            cat = get_exif_category (entry);

            path = set_row_data (store,
                                 NULL,
                                 exif_categories[cat].path,
                                 exif_tag_get_name_in_ifd (entry->tag, ifd),
                                 eom_exif_entry_get_value (entry, b,
                                 sizeof(b)));

            g_hash_table_insert (priv->id_path_hash,
                                 GINT_TO_POINTER (key),
                                 path);
        }
    }
}
#endif

#ifdef HAVE_EXIF
static void
exif_content_cb (ExifContent *content, gpointer data)
{
    exif_content_foreach_entry (content, exif_entry_cb, data);
}
#endif

GtkWidget *
eom_metadata_details_new (void)
{
    GObject *object;

    object = g_object_new (EOM_TYPE_METADATA_DETAILS, NULL);

    return GTK_WIDGET (object);
}

static void
eom_metadata_details_reset (EomMetadataDetails *details)
{
    EomMetadataDetailsPrivate *priv = details->priv;
    int i;

    gtk_tree_store_clear (GTK_TREE_STORE (priv->model));

    g_hash_table_remove_all (priv->id_path_hash);
    g_hash_table_remove_all (priv->id_path_hash_mnote);

    for (i = 0; exif_categories [i].label != NULL; i++) {
        char *translated_string;

        translated_string = gettext (exif_categories[i].label);

        set_row_data (GTK_TREE_STORE (priv->model),
                      exif_categories[i].path,
                      NULL,
                      translated_string,
                      NULL);
    }
}

#ifdef HAVE_EXIF
void
eom_metadata_details_update (EomMetadataDetails *details, ExifData *data)
{
    g_return_if_fail (EOM_IS_METADATA_DETAILS (details));

    eom_metadata_details_reset (details);
    if (data) {
        exif_data_foreach_content (data, exif_content_cb, details);
    }
}
#endif /* HAVE_EXIF */

#ifdef HAVE_EXEMPI
typedef struct {
    const char *id;
    MetadataCategory category;
} XmpNsCategory;

static XmpNsCategory xmp_ns_category_map[] = {
    { NS_EXIF,                  XMP_CATEGORY_EXIF},
    { NS_TIFF,                  XMP_CATEGORY_EXIF},
    { NS_XAP,                   XMP_CATEGORY_EXIF},
    { NS_XAP_RIGHTS,            XMP_CATEGORY_RIGHTS},
    { NS_EXIF_AUX,              XMP_CATEGORY_EXIF},
    { NS_DC,                    XMP_CATEGORY_IPTC},
    { NS_IPTC4XMP,              XMP_CATEGORY_IPTC},
    { NS_CC,                    XMP_CATEGORY_RIGHTS},
    { NULL, -1}
};

static MetadataCategory
get_xmp_category (XmpStringPtr schema)
{
    MetadataCategory cat = XMP_CATEGORY_OTHER;
    const char *s = xmp_string_cstr(schema);
    int i;

    for (i = 0; xmp_ns_category_map[i].id != NULL; i++) {
        if (strcmp (xmp_ns_category_map[i].id, s) == 0) {
            cat = xmp_ns_category_map[i].category;
            break;
        }
    }

    return cat;
}

static void
xmp_entry_insert (EomMetadataDetails *view, XmpStringPtr xmp_schema,
                  XmpStringPtr xmp_path, XmpStringPtr xmp_prop)
{
    GtkTreeStore *store;
    EomMetadataDetailsPrivate *priv;
    MetadataCategory cat;
    char *path;
    gchar *key;

    priv = view->priv;

    key = g_strconcat (xmp_string_cstr (xmp_schema), ":",
                       xmp_string_cstr (xmp_path), NULL);

    store = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view)));

    path = g_hash_table_lookup (priv->id_path_hash, key);

    if (path != NULL) {
        set_row_data (store, path, NULL,
                      xmp_string_cstr (xmp_path),
                      xmp_string_cstr (xmp_prop));

        g_free(key);
    }
    else {
        cat = get_xmp_category (xmp_schema);

        path = set_row_data (store, NULL, exif_categories[cat].path,
                             xmp_string_cstr(xmp_path),
                             xmp_string_cstr(xmp_prop));

        g_hash_table_insert (priv->id_path_hash, key, path);
    }
}

void
eom_metadata_details_xmp_update (EomMetadataDetails *view, XmpPtr data)
{
    g_return_if_fail (EOM_IS_METADATA_DETAILS (view));

    if (data) {
        XmpIteratorPtr iter = xmp_iterator_new(data, NULL, NULL, XMP_ITER_JUSTLEAFNODES);
        XmpStringPtr the_schema = xmp_string_new ();
        XmpStringPtr the_path = xmp_string_new ();
        XmpStringPtr the_prop = xmp_string_new ();

        while (xmp_iterator_next (iter, the_schema, the_path, the_prop, NULL)) {
            xmp_entry_insert (view, the_schema, the_path, the_prop);
        }

        xmp_string_free (the_prop);
        xmp_string_free (the_path);
        xmp_string_free (the_schema);
        xmp_iterator_free (iter);
    }
}
#endif