/* ============================================================
 *
 * This file is a part of kipi-plugins project
 * http://www.kipi-plugins.org
 *
 * Date        : 2006-09-15
 * Description : Exiv2 library interface for KDE
 *
 * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
 * Copyright (C) 2006-2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
 *
 * NOTE: Do not use kdDebug() in this implementation because
 *       it will be multithreaded. Use tqDebug() instead.
 *       See B.K.O #133026 for details.
 *
 * 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, 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.
 *
 * ============================================================ */

// C ANSI includes.

extern "C"
{
#include <sys/stat.h>
#include <utime.h>
}

// TQt includes.

#include <tqfile.h>
#include <tqimage.h>
#include <tqsize.h>
#include <tqtextcodec.h>
#include <tqwmatrix.h>
#include <tqfileinfo.h>
#include <tqbuffer.h>

// Local includes.

#include "version.h"
#include "kexiv2private.h"
#include "kexiv2.h"

namespace KExiv2Iface
{

KExiv2::KExiv2()
{
    d = new KExiv2Priv;
}

KExiv2::KExiv2(const TQString& filePath)
{
    d = new KExiv2Priv;
    load(filePath);
}

KExiv2::~KExiv2()
{
    delete d;
}

bool KExiv2::supportMetadataWritting(const TQString& typeMime)
{
    if (typeMime == TQString("image/jpeg"))
    {
        return true;
    }
    else if (typeMime == TQString("image/tiff"))
    {
#if (EXIV2_TEST_VERSION(0,17,91))
        return true;
#else
        return false;
#endif
    }
    else if (typeMime == TQString("image/png"))
    {
#if (EXIV2_TEST_VERSION(0,17,91))
        return true;
#else
        return false;
#endif
    }
    else if (typeMime == TQString("image/jp2"))
    {
#if (EXIV2_TEST_VERSION(0,17,91))
        return true;
#else
        return false;
#endif
    }

    return false;
}

TQString KExiv2::version()
{
  return TQString( kexiv2_version );
}

TQString KExiv2::Exiv2Version()
{
    // Since 0.14.0 release, we can extract run-time version of Exiv2.
    // else we return make version.

#if (EXIV2_TEST_VERSION(0,14,0))
    return TQString(Exiv2::version());
#else
    return TQString("%1.%2.%3").arg(EXIV2_MAJOR_VERSION)
                              .arg(EXIV2_MINOR_VERSION)
                              .arg(EXIV2_PATCH_VERSION);
#endif
}

bool KExiv2::clearComments()
{
    return setComments(TQByteArray());
}

bool KExiv2::clearExif()
{
    try
    {
        d->exifMetadata.clear();
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot clear Exif data using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::clearIptc()
{
    try
    {
        d->iptcMetadata.clear();
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot clear Iptc data using Exiv2 ", e);
    }

    return false;
}

TQString KExiv2::getFilePath() const
{
    return d->filePath;
}

TQByteArray KExiv2::getComments() const
{
    return TQByteArray().duplicate(d->imageComments.data(), d->imageComments.size());
}

TQString KExiv2::getCommentsDecoded() const
{
    return d->detectEncodingAndDecode(d->imageComments);
}

TQByteArray KExiv2::getExif() const
{
    try
    {
        if (!d->exifMetadata.empty())
        {

            Exiv2::ExifData& exif = d->exifMetadata;

#if (EXIV2_TEST_VERSION(0,17,91))
            Exiv2::Blob blob;
            Exiv2::ExifParser::encode(blob, Exiv2::bigEndian, exif);
            TQByteArray ba(blob.size());
            if (ba.size())
                memcpy(ba.data(), (const char*)&blob[0], blob.size());
#else
            Exiv2::DataBuf c2 = exif.copy();
            TQByteArray ba(c2.size_);
            if (ba.size())
                memcpy(ba.data(), c2.pData_, c2.size_);
#endif

            return ba;
        }
    }
    catch( Exiv2::Error &e )
    {
        if (!d->filePath.isEmpty())
            tqDebug ("From file %s", d->filePath.ascii());

        d->printExiv2ExceptionError("Cannot get Exif data using Exiv2 ", e);
    }

    return TQByteArray();
}

TQByteArray KExiv2::getIptc(bool addIrbHeader) const
{
    try
    {
        if (!d->iptcMetadata.empty())
        {
            Exiv2::IptcData& iptc = d->iptcMetadata;
            Exiv2::DataBuf c2;

            if (addIrbHeader)
            {
#if (EXIV2_TEST_VERSION(0,10,0))
                c2 = Exiv2::Photoshop::setIptcIrb(0, 0, iptc);
#else
                tqDebug("Exiv2 version is to old. Cannot add Irb header to IPTC metadata");
                return TQByteArray();
#endif
            }
            else
            {
#if (EXIV2_TEST_VERSION(0,17,91))
                c2 = Exiv2::IptcParser::encode(d->iptcMetadata);
#else
                c2 = iptc.copy();
#endif
            }

            TQByteArray data(c2.size_);
            if (data.size())
                memcpy(data.data(), c2.pData_, c2.size_);
            return data;
        }
    }
    catch( Exiv2::Error &e )
    {
        if (!d->filePath.isEmpty())
            tqDebug ("From file %s", d->filePath.ascii());

        d->printExiv2ExceptionError("Cannot get Iptc data using Exiv2 ",e);
    }

    return TQByteArray();
}

bool KExiv2::setComments(const TQByteArray& data)
{
    d->imageComments = std::string(data.data(), data.size());
    return true;
}

bool KExiv2::setExif(const TQByteArray& data)
{
    try
    {
        if (!data.isEmpty())
        {
#if (EXIV2_TEST_VERSION(0,17,91))
            Exiv2::ExifParser::decode(d->exifMetadata, (const Exiv2::byte*)data.data(), data.size());
            return (!d->exifMetadata.empty());
#else
            if (d->exifMetadata.load((const Exiv2::byte*)data.data(), data.size()) != 0)
                return false;
            else
                return true;
#endif
        }
    }
    catch( Exiv2::Error &e )
    {
        if (!d->filePath.isEmpty())
            tqDebug ("From file %s", d->filePath.ascii());

        d->printExiv2ExceptionError("Cannot set Exif data using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::setIptc(const TQByteArray& data)
{
    try
    {
        if (!data.isEmpty())
        {
#if (EXIV2_TEST_VERSION(0,17,91))
            Exiv2::IptcParser::decode(d->iptcMetadata, (const Exiv2::byte*)data.data(), data.size());
            return (!d->iptcMetadata.empty());
#else
            if (d->iptcMetadata.load((const Exiv2::byte*)data.data(), data.size()) != 0)
                return false;
            else
                return true;
#endif
        }
    }
    catch( Exiv2::Error &e )
    {
        if (!d->filePath.isEmpty())
            tqDebug ("From file %s", d->filePath.ascii());

        d->printExiv2ExceptionError("Cannot set Iptc data using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::load(const TQByteArray& imgData)
{
    if (imgData.isEmpty())
        return false;

    try
    {

        Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((Exiv2::byte*)imgData.data(), imgData.size());
        d->filePath = TQString();
        image->readMetadata();

        // Image comments ---------------------------------

        d->imageComments = image->comment();

        // Exif metadata ----------------------------------

        d->exifMetadata = image->exifData();

        // Iptc metadata ----------------------------------

        d->iptcMetadata = image->iptcData();

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot load metadata using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::load(const TQString& filePath)
{
    TQFileInfo finfo(filePath);
    if (filePath.isEmpty() || !finfo.isReadable())
    {
        tqDebug("File '%s' is not readable.", finfo.fileName().ascii());
        return false;
    }

    try
    {

        Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
                                      (TQFile::encodeName(filePath)));
        d->filePath = filePath;
        image->readMetadata();

        // Image comments ---------------------------------

        d->imageComments = image->comment();

        // Exif metadata ----------------------------------

        d->exifMetadata = image->exifData();

        // Iptc metadata ----------------------------------

        d->iptcMetadata = image->iptcData();

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot load metadata using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::save(const TQString& filePath)
{
    if (filePath.isEmpty())
        return false;

    // NOTE: see B.K.O #137770 & #138540 : never touch the file if is read only.
    TQFileInfo finfo(filePath);
    TQFileInfo dinfo(finfo.dirPath());
    if (!finfo.isWritable())
    {
        tqDebug("File '%s' is read-only. Metadata not saved.", finfo.fileName().ascii());
        return false;
    }
    if (!dinfo.isWritable())
    {
        tqDebug("Dir '%s' is read-only. Metadata not saved.", dinfo.filePath().ascii());
        return false;
    }

    // TIFF/EP Raw files based are supported by Exiv2 0.18 as experimental. We will do touch it for the moment.
    // Metadata writing is supported in implementation from svn trunk.
    TQString rawTiffBased("dng nef pef 3fr arw cr2 dcr erf k25 kdc mos orf raw sr2 srf"); 
    if (rawTiffBased.contains(finfo.extension(false).lower()))
    {
        tqDebug("'%s' is TIFF based RAW file and writing mode is disable with this libkexiv2 version. Metadata not saved.", 
               finfo.fileName().ascii());
        return false;
    }

    try
    {
        Exiv2::AccessMode mode;
        Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
                                      (TQFile::encodeName(filePath)));

        // We need to load target file metadata to merge with new one. It's mandatory with TIFF format:
        // like all tiff file structure is based on Exif.
        image->readMetadata();

        // Image Comments ---------------------------------

        mode = image->checkMode(Exiv2::mdComment);
        if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
        {
            image->setComment(d->imageComments);
        }

        // Exif metadata ----------------------------------

        mode = image->checkMode(Exiv2::mdExif);
        if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
        {
            if (image->mimeType() == "image/tiff")
            {
                // With tiff image we cannot overwrite whole Exif data as well, because 
                // image data are stored in Exif container. We need to take a care about
                // to not lost image data.
                Exiv2::ExifData exif = image->exifData();
                TQStringList untouchedTags;
                untouchedTags << "Exif.Image.ImageWidth";
                untouchedTags << "Exif.Image.ImageLength";
                untouchedTags << "Exif.Image.BitsPerSample";
                untouchedTags << "Exif.Image.Compression";
                untouchedTags << "Exif.Image.PhotometricInterpretation";
                untouchedTags << "Exif.Image.FillOrder";
                untouchedTags << "Exif.Image.SamplesPerPixel";
                untouchedTags << "Exif.Image.StripOffsets";
                untouchedTags << "Exif.Image.RowsPerStrip";
                untouchedTags << "Exif.Image.StripByteCounts";
                untouchedTags << "Exif.Image.XResolution";
                untouchedTags << "Exif.Image.YResolution";
                untouchedTags << "Exif.Image.PlanarConfiguration";
                untouchedTags << "Exif.Image.ResolutionUnit";

                for (Exiv2::ExifData::iterator it = d->exifMetadata.begin(); it != d->exifMetadata.end(); ++it)
                {
                    if (!untouchedTags.contains(it->key().c_str()))
                    {
                        exif[it->key().c_str()] = d->exifMetadata[it->key().c_str()];
                    }
                }

                image->setExifData(exif);
            }
            else
            {
                image->setExifData(d->exifMetadata);
            }
        }

        // Iptc metadata ----------------------------------

        mode = image->checkMode(Exiv2::mdIptc);
        if (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite)
        {
            image->setIptcData(d->iptcMetadata);
        }

        // NOTE: Don't touch access and modification timestamp of file.
        struct stat st;
        ::stat(TQFile::encodeName(filePath), &st);

        struct utimbuf ut;
        ut.modtime = st.st_mtime;
        ut.actime  = st.st_atime;

        image->writeMetadata();

        ::utime(TQFile::encodeName(filePath), &ut);

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot save metadata using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::applyChanges()
{
    if (d->filePath.isEmpty())
	return false;
	
    return save(d->filePath);
}

bool KExiv2::isReadOnly(const TQString& filePath)
{
    if (!canWriteComment(filePath))
        return true;

    if (!canWriteExif(filePath))
        return true;

    if (!canWriteIptc(filePath))
        return true;

    return false;
}

bool KExiv2::canWriteComment(const TQString& filePath)
{
    try
    {
        Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
                                      (TQFile::encodeName(filePath)));

        Exiv2::AccessMode mode = image->checkMode(Exiv2::mdComment);
        return (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite);
    }
    catch( Exiv2::Error &e )
    {
        std::string s(e.what());
        tqDebug("%s (Error #%i: %s)", "Cannot check Comment access mode using Exiv2 ", e.code(), s.c_str());
    }

    return false;
}

bool KExiv2::canWriteExif(const TQString& filePath)
{
    try
    {
        Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
                                      (TQFile::encodeName(filePath)));

        Exiv2::AccessMode mode = image->checkMode(Exiv2::mdExif);
        return (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite);
    }
    catch( Exiv2::Error &e )
    {
        std::string s(e.what());
        tqDebug("%s (Error #%i: %s)", "Cannot check Exif access mode using Exiv2 ", e.code(), s.c_str());
    }

    return false;
}

bool KExiv2::canWriteIptc(const TQString& filePath)
{
    try
    {
        Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
                                      (TQFile::encodeName(filePath)));

        Exiv2::AccessMode mode = image->checkMode(Exiv2::mdIptc);
        return (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite);
    }
    catch( Exiv2::Error &e )
    {
        std::string s(e.what());
        tqDebug("%s (Error #%i: %s)", "Cannot check Iptc access mode using Exiv2 ", e.code(), s.c_str());
    }

    return false;
}

bool KExiv2::setImageProgramId(const TQString& program, const TQString& version)
{
    try
    {
        // Record program info in Exif.Image.ProcessingSoftware tag (only available with Exiv2 >= 0.14.0).

#if (EXIV2_TEST_VERSION(0,14,0))
        TQString software(program);
        software.append("-");
        software.append(version);
        d->exifMetadata["Exif.Image.ProcessingSoftware"] = software.ascii();
#endif

        // See B.K.O #142564: Check if Exif.Image.Software already exist. If yes, do not touch this tag.

        if (!d->exifMetadata.empty())
        {
            Exiv2::ExifData exifData(d->exifMetadata);
            Exiv2::ExifKey key("Exif.Image.Software");
            Exiv2::ExifData::iterator it = exifData.findKey(key);

            if (it == exifData.end())
            {
                TQString software(program);
                software.append("-");
                software.append(version);
                d->exifMetadata["Exif.Image.Software"]      = software.ascii();
            }
        }

        // Record program info in IPTC tags.

        d->iptcMetadata["Iptc.Application2.Program"]        = program.ascii();
        d->iptcMetadata["Iptc.Application2.ProgramVersion"] = version.ascii();
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Program identity into image using Exiv2 ", e);
    }

    return false;
}

TQSize KExiv2::getImageDimensions() const
{
    if (d->exifMetadata.empty())
        return TQSize();

    try
    {
        long width=-1, height=-1;

        // Try to get Exif.Photo tags

        Exiv2::ExifData exifData(d->exifMetadata);
        Exiv2::ExifKey key("Exif.Photo.PixelXDimension");
        Exiv2::ExifData::iterator it = exifData.findKey(key);

        if (it != exifData.end())
            width = it->toLong();

        Exiv2::ExifKey key2("Exif.Photo.PixelYDimension");
        Exiv2::ExifData::iterator it2 = exifData.findKey(key2);

        if (it2 != exifData.end())
            height = it2->toLong();

        if (width != -1 && height != -1)
            return TQSize(width, height);

        // Try to get Exif.Image tags

        width  = -1;
        height = -1;

        Exiv2::ExifKey key3("Exif.Image.ImageWidth");
        Exiv2::ExifData::iterator it3 = exifData.findKey(key3);

        if (it3 != exifData.end())
            width = it3->toLong();

        Exiv2::ExifKey key4("Exif.Image.ImageLength");
        Exiv2::ExifData::iterator it4 = exifData.findKey(key4);

        if (it4 != exifData.end())
            height = it4->toLong();

        if (width != -1 && height != -1)
            return TQSize(width, height);
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot parse image dimensions tag using Exiv2 ", e);
    }

    return TQSize();
}

bool KExiv2::setImageDimensions(const TQSize& size, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        // NOTE: see B.K.O #144604: you a cast to record an unsigned integer value.
        d->exifMetadata["Exif.Image.ImageWidth"]      = static_cast<uint32_t>(size.width());
        d->exifMetadata["Exif.Image.ImageLength"]     = static_cast<uint32_t>(size.height());
        d->exifMetadata["Exif.Photo.PixelXDimension"] = static_cast<uint32_t>(size.width());
        d->exifMetadata["Exif.Photo.PixelYDimension"] = static_cast<uint32_t>(size.height());
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set image dimensions using Exiv2 ", e);
    }

    return false;
}

TQImage KExiv2::getExifThumbnail(bool fixOrientation) const
{
    TQImage thumbnail;

    if (d->exifMetadata.empty())
       return thumbnail;

    try
    {
#if (EXIV2_TEST_VERSION(0,17,91))
        Exiv2::ExifThumbC thumb(d->exifMetadata);
        Exiv2::DataBuf const c1 = thumb.copy();
#else
        Exiv2::DataBuf const c1(d->exifMetadata.copyThumbnail());
#endif
        thumbnail.loadFromData(c1.pData_, c1.size_);

        if (!thumbnail.isNull())
        {
            if (fixOrientation)
            {
                Exiv2::ExifKey key("Exif.Thumbnail.Orientation");
                Exiv2::ExifData exifData(d->exifMetadata);
                Exiv2::ExifData::iterator it = exifData.findKey(key);
                if (it != exifData.end())
                {
                    TQWMatrix matrix;
                    long orientation = it->toLong();
                    tqDebug("Exif Thumbnail Qt::Orientation: %i", (int)orientation);

                    switch (orientation)
                    {
                        case ORIENTATION_HFLIP:
                            matrix.scale(-1, 1);
                            break;

                        case ORIENTATION_ROT_180:
                            matrix.rotate(180);
                            break;

                        case ORIENTATION_VFLIP:
                            matrix.scale(1, -1);
                            break;

                        case ORIENTATION_ROT_90_HFLIP:
                            matrix.scale(-1, 1);
                            matrix.rotate(90);
                            break;

                        case ORIENTATION_ROT_90:
                            matrix.rotate(90);
                            break;

                        case ORIENTATION_ROT_90_VFLIP:
                            matrix.scale(1, -1);
                            matrix.rotate(90);
                            break;

                        case ORIENTATION_ROT_270:
                            matrix.rotate(270);
                            break;

                        default:
                            break;
                    }

                    if ( orientation != ORIENTATION_NORMAL )
                        thumbnail = thumbnail.xForm( matrix );
                }

                return thumbnail;
            }
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot get Exif Thumbnail using Exiv2 ", e);
    }

    return thumbnail;
}

bool KExiv2::setExifThumbnail(const TQImage& thumb, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
#if (EXIV2_TEST_VERSION(0,17,91))
        TQByteArray data;
        TQBuffer buffer(data);
        buffer.open(IO_WriteOnly);
        thumb.save(&buffer, "JPEG");
        Exiv2::ExifThumb thumb(d->exifMetadata);
        thumb.setJpegThumbnail((Exiv2::byte *)data.data(), data.size());
#else
        KTempFile thumbFile(TQString(), "KExiv2ExifThumbnail");
        thumbFile.setAutoDelete(true);
        thumb.save(thumbFile.name(), "JPEG");

        const std::string &fileName( (const char*)(TQFile::encodeName(thumbFile.name())) );
        d->exifMetadata.setJpegThumbnail( fileName );
#endif
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Exif Thumbnail using Exiv2 ", e);
    }

    return false;
}

KExiv2::ImageOrientation KExiv2::getImageOrientation() const
{
    if (d->exifMetadata.empty())
       return ORIENTATION_UNSPECIFIED;

    // Workaround for older Exiv2 versions which do not support
    // Minolta Makernotes and throw an error for such keys.
    bool supportMinolta = true;
    try
    {
        Exiv2::ExifKey minoltaKey1("Exif.MinoltaCs7D.Rotation");
        Exiv2::ExifKey minoltaKey2("Exif.MinoltaCs5D.Rotation");
    }
    catch( Exiv2::Error &e )
    {
        supportMinolta = false;
    }

    try
    {
        Exiv2::ExifData exifData(d->exifMetadata);
        Exiv2::ExifData::iterator it;
        long orientation;
        ImageOrientation imageOrient = ORIENTATION_NORMAL;

        // Because some camera set a wrong standard exif orientation tag,
        // We need to check makernote tags in first!

        // -- Minolta Cameras ----------------------------------

        if (supportMinolta)
        {
            Exiv2::ExifKey minoltaKey1("Exif.MinoltaCs7D.Rotation");
            it = exifData.findKey(minoltaKey1);

            if (it != exifData.end())
            {
                orientation = it->toLong();
                tqDebug("Minolta Makernote Qt::Orientation: %i", (int)orientation);
                switch(orientation)
                {
                    case 76:
                        imageOrient = ORIENTATION_ROT_90;
                        break;
                    case 82:
                        imageOrient = ORIENTATION_ROT_270;
                        break;
                }
                return imageOrient;
            }

            Exiv2::ExifKey minoltaKey2("Exif.MinoltaCs5D.Rotation");
            it = exifData.findKey(minoltaKey2);

            if (it != exifData.end())
            {
                orientation = it->toLong();
                tqDebug("Minolta Makernote Qt::Orientation: %i", (int)orientation);
                switch(orientation)
                {
                    case 76:
                        imageOrient = ORIENTATION_ROT_90;
                        break;
                    case 82:
                        imageOrient = ORIENTATION_ROT_270;
                        break;
                }
                return imageOrient;
            }
        }

        // -- Standard Exif tag --------------------------------

        Exiv2::ExifKey keyStd("Exif.Image.Orientation");
        it = exifData.findKey(keyStd);

        if (it != exifData.end())
        {
            orientation = it->toLong();
            tqDebug("Exif Qt::Orientation: %i", (int)orientation);
            return (ImageOrientation)orientation;
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot parse Exif Qt::Orientation tag using Exiv2 ", e);
    }

    return ORIENTATION_UNSPECIFIED;
}

bool KExiv2::setImageOrientation(ImageOrientation orientation, bool setProgramName)
{
    if (d->exifMetadata.empty())
       return false;

    if (!setProgramId(setProgramName))
        return false;

    // Workaround for older Exiv2 versions which do not support
    // Minolta Makernotes and throw an error for such keys.
    bool supportMinolta = true;
    try
    {
        Exiv2::ExifKey minoltaKey1("Exif.MinoltaCs7D.Rotation");
        Exiv2::ExifKey minoltaKey2("Exif.MinoltaCs5D.Rotation");
    }
    catch( Exiv2::Error &e )
    {
        supportMinolta = false;
    }

    try
    {
        if (orientation < ORIENTATION_UNSPECIFIED || orientation > ORIENTATION_ROT_270)
        {
            tqDebug("Exif orientation tag value is not correct!");
            return false;
        }

        d->exifMetadata["Exif.Image.Orientation"] = static_cast<uint16_t>(orientation);
        tqDebug("Exif orientation tag set to: %i", (int)orientation);

        // -- Minolta Cameras ----------------------------------

        if (supportMinolta)
        {
            // Minolta camera store image rotation in Makernote.
            // We remove these information to prevent duplicate values.

            Exiv2::ExifData::iterator it;

            Exiv2::ExifKey minoltaKey1("Exif.MinoltaCs7D.Rotation");
            it = d->exifMetadata.findKey(minoltaKey1);
            if (it != d->exifMetadata.end())
            {
                d->exifMetadata.erase(it);
                tqDebug("Removing Exif.MinoltaCs7D.Rotation tag");
            }

            Exiv2::ExifKey minoltaKey2("Exif.MinoltaCs5D.Rotation");
            it = d->exifMetadata.findKey(minoltaKey2);
            if (it != d->exifMetadata.end())
            {
                d->exifMetadata.erase(it);
                tqDebug("Removing Exif.MinoltaCs5D.Rotation tag");
            }
        }

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Exif Qt::Orientation tag using Exiv2 ", e);
    }

    return false;
}

KExiv2::ImageColorWorkSpace KExiv2::getImageColorWorkSpace() const
{
    if (!d->exifMetadata.empty())
    {
        long colorSpace;

        if (getExifTagLong("Exif.Photo.ColorSpace", colorSpace))
        {
            switch (colorSpace)
            {
                case 1:
                {
                    return WORKSPACE_SRGB;
                    break;
                }
                case 2:
                {
                    return WORKSPACE_ADOBERGB;
                    break;
                }
                case 65535:
                {
                    // Nikon camera set Exif.Photo.ColorSpace to uncalibrated and
                    // Exif.Nikon3.ColorMode to "MODE2" when users work in AdobRGB color space.
                    if (getExifTagString("Exif.Nikon3.ColorMode").contains("MODE2"))
                        return WORKSPACE_ADOBERGB;

                    // TODO : add more Makernote parsing here ...

                    return WORKSPACE_UNCALIBRATED;
                    break;
                }
                default:
                {
                    return WORKSPACE_UNSPECIFIED;
                    break;
                }
            }
        }
    }

    return WORKSPACE_UNSPECIFIED;
}

bool KExiv2::setImageColorWorkSpace(ImageColorWorkSpace workspace, bool setProgramName)
{
    if (d->exifMetadata.empty())
       return false;

    if (!setProgramId(setProgramName))
        return false;

    try
    {
        d->exifMetadata["Exif.Photo.ColorSpace"] = static_cast<uint16_t>(workspace);
        tqDebug("Exif color workspace tag set to: %i",  (int)workspace);
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Exif color workspace tag using Exiv2 ", e);
    }

    return false;
}

TQDateTime KExiv2::getImageDateTime() const
{
    try
    {
        // In first, trying to get Date & time from Exif tags.

        if (!d->exifMetadata.empty())
        {
            // Try Exif date time original.

            Exiv2::ExifData exifData(d->exifMetadata);
            Exiv2::ExifKey key2("Exif.Photo.DateTimeOriginal");
            Exiv2::ExifData::iterator it2 = exifData.findKey(key2);

            if (it2 != exifData.end())
            {
                TQDateTime dateTime = TQDateTime::fromString(it2->toString().c_str(), Qt::ISODate);

                if (dateTime.isValid())
                {
                    // tqDebug("DateTime (Exif original): %s", dateTime.toString().ascii());
                    return dateTime;
                }
            }

            // Bogus Exif date time original entry. Try Exif date time digitized.

            Exiv2::ExifKey key3("Exif.Photo.DateTimeDigitized");
            Exiv2::ExifData::iterator it3 = exifData.findKey(key3);

            if (it3 != exifData.end())
            {
                TQDateTime dateTime = TQDateTime::fromString(it3->toString().c_str(), Qt::ISODate);

                if (dateTime.isValid())
                {
                    // tqDebug("DateTime (Exif digitalized): %s", dateTime.toString().ascii());
                    return dateTime;
                }
            }

            // Bogus Exif date time digitized. Try standard Exif date time entry.

            Exiv2::ExifKey key("Exif.Image.DateTime");
            Exiv2::ExifData::iterator it = exifData.findKey(key);

            if (it != exifData.end())
            {
                TQDateTime dateTime = TQDateTime::fromString(it->toString().c_str(), Qt::ISODate);

                if (dateTime.isValid())
                {
                    // tqDebug("DateTime (Exif standard): %s", dateTime.toString().ascii());
                    return dateTime;
                }
            }
        }

        // In second, trying to get Date & time from Iptc tags.

        if (!d->iptcMetadata.empty())
        {
            // Try creation Iptc date time entries.

            Exiv2::IptcKey keyDateCreated("Iptc.Application2.DateCreated");
            Exiv2::IptcData iptcData(d->iptcMetadata);
            Exiv2::IptcData::iterator it = iptcData.findKey(keyDateCreated);

            if (it != iptcData.end())
            {
                TQString IptcDateCreated(it->toString().c_str());

                Exiv2::IptcKey keyTimeCreated("Iptc.Application2.TimeCreated");
                Exiv2::IptcData::iterator it2 = iptcData.findKey(keyTimeCreated);

                if (it2 != iptcData.end())
                {
                    TQString IptcTimeCreated(it2->toString().c_str());

                    TQDate date = TQDate::fromString(IptcDateCreated, Qt::ISODate);
                    TQTime time = TQTime::fromString(IptcTimeCreated, Qt::ISODate);
                    TQDateTime dateTime = TQDateTime(date, time);

                    if (dateTime.isValid())
                    {
                        // tqDebug("Date (IPTC created): %s", dateTime.toString().ascii());
                        return dateTime;
                    }
                }
            }

            // Try digitization Iptc date time entries.

            Exiv2::IptcKey keyDigitizationDate("Iptc.Application2.DigitizationDate");
            Exiv2::IptcData::iterator it3 = iptcData.findKey(keyDigitizationDate);

            if (it3 != iptcData.end())
            {
                TQString IptcDateDigitization(it3->toString().c_str());

                Exiv2::IptcKey keyDigitizationTime("Iptc.Application2.DigitizationTime");
                Exiv2::IptcData::iterator it4 = iptcData.findKey(keyDigitizationTime);

                if (it4 != iptcData.end())
                {
                    TQString IptcTimeDigitization(it4->toString().c_str());

                    TQDate date = TQDate::fromString(IptcDateDigitization, Qt::ISODate);
                    TQTime time = TQTime::fromString(IptcTimeDigitization, Qt::ISODate);
                    TQDateTime dateTime = TQDateTime(date, time);

                    if (dateTime.isValid())
                    {
                        //tqDebug("Date (IPTC digitalized): %s", dateTime.toString().ascii());
                        return dateTime;
                    }
                }
            }
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot parse Exif date & time tag using Exiv2 ", e);
    }

    return TQDateTime();
}

bool KExiv2::setImageDateTime(const TQDateTime& dateTime, bool setDateTimeDigitized, bool setProgramName)
{
    if(!dateTime.isValid())
        return false;

    if (!setProgramId(setProgramName))
        return false;

    try
    {
        // In first we write date & time into Exif.

        // DateTimeDigitized is set by slide scanners etc. when a picture is digitized.
        // DateTimeOriginal specifies the date/time when the picture was taken.
        // For digital cameras, these dates should be both set, and identical.
        // Reference: http://www.exif.org/Exif2-2.PDF, chapter 4.6.5, table 4, section F.

        const std::string &exifdatetime(dateTime.toString(TQString("yyyy:MM:dd hh:mm:ss")).ascii());
        d->exifMetadata["Exif.Image.DateTime"]         = exifdatetime;
        d->exifMetadata["Exif.Photo.DateTimeOriginal"] = exifdatetime;
        if(setDateTimeDigitized)
            d->exifMetadata["Exif.Photo.DateTimeDigitized"] = exifdatetime;

        // In Second we write date & time into Iptc.

        const std::string &iptcdate(TQString(dateTime.date().toString(Qt::ISODate)).ascii());
        const std::string &iptctime(TQString(dateTime.time().toString(Qt::ISODate)).ascii());
        d->iptcMetadata["Iptc.Application2.DateCreated"] = iptcdate;
        d->iptcMetadata["Iptc.Application2.TimeCreated"] = iptctime;
        if(setDateTimeDigitized)
        {
            d->iptcMetadata["Iptc.Application2.DigitizationDate"] = iptcdate;
            d->iptcMetadata["Iptc.Application2.DigitizationTime"] = iptctime;
        }

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Date & Time into image using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::getImagePreview(TQImage& preview) const
{
    try
    {
        // In first we trying to get from Iptc preview tag.
        if (preview.loadFromData(getIptcTagData("Iptc.Application2.Preview")) )
            return true;

        // TODO : Added here Makernotes preview extraction when Exiv2 will be fixed for that.
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot get image preview using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::setImagePreview(const TQImage& preview, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        TQByteArray data;
        TQBuffer buffer(data);
        buffer.open(IO_WriteOnly);

        // A little bit compressed preview jpeg image to limit IPTC size.
        preview.save(&buffer, "JPEG");
        tqDebug("JPEG image preview size: (%i x %i) pixels - %i bytes",
               preview.width(), preview.height(), (int)data.size());

        Exiv2::DataValue val;
        val.read((Exiv2::byte *)data.data(), data.size());
        d->iptcMetadata["Iptc.Application2.Preview"] = val;

        // See http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf Appendix A for details.
        d->iptcMetadata["Iptc.Application2.PreviewFormat"]  = 11;  // JPEG
        d->iptcMetadata["Iptc.Application2.PreviewVersion"] = 1;

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot get image preview using Exiv2 ", e);
    }

    return false;
}

TQString KExiv2::getExifTagString(const char* exifTagName, bool escapeCR) const
{
    try
    {
        Exiv2::ExifKey exifKey(exifTagName);
        Exiv2::ExifData exifData(d->exifMetadata);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
        if (it != exifData.end())
        {
#if (EXIV2_TEST_VERSION(0,17,91))
            // See B.K.O #184156 comment #13
            std::string val  = it->print(&exifData);
            TQString tagValue = TQString::fromLocal8Bit(val.c_str());
#else
            std::ostringstream os;
            os << *it;
            TQString tagValue = TQString::fromLocal8Bit(os.str().c_str());
#endif
            if (escapeCR)
                tagValue.replace("\n", " ");

            return tagValue;
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError(TQString("Cannot find Exif key '%1' into image using Exiv2 ")
                                 .arg(exifTagName), e);
    }

    return TQString();
}

bool KExiv2::setExifTagString(const char *exifTagName, const TQString& value, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        d->exifMetadata[exifTagName] = value.ascii();
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Exif tag string into image using Exiv2 ", e);
    }

    return false;
}

TQString KExiv2::getIptcTagString(const char* iptcTagName, bool escapeCR) const
{
    try
    {
        Exiv2::IptcKey iptcKey(iptcTagName);
        Exiv2::IptcData iptcData(d->iptcMetadata);
        Exiv2::IptcData::iterator it = iptcData.findKey(iptcKey);
        if (it != iptcData.end())
        {
            std::ostringstream os;
            os << *it;
            TQString tagValue = TQString::fromLocal8Bit(os.str().c_str());

            if (escapeCR)
                tagValue.replace("\n", " ");

            return tagValue;
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError(TQString("Cannot find Iptc key '%1' into image using Exiv2 ")
                                 .arg(iptcTagName), e);
    }

    return TQString();
}

bool KExiv2::setIptcTagString(const char *iptcTagName, const TQString& value, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        d->iptcMetadata[iptcTagName] = value.ascii();
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Iptc tag string into image using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::getExifTagLong(const char* exifTagName, long &val) const
{
    try
    {
        Exiv2::ExifKey exifKey(exifTagName);
        Exiv2::ExifData exifData(d->exifMetadata);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
        if (it != exifData.end())
        {
            val = it->toLong();
            return true;
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError(TQString("Cannot find Exif key '%1' into image using Exiv2 ")
                                 .arg(exifTagName), e);
    }

    return false;
}

TQByteArray KExiv2::getExifTagData(const char* exifTagName) const
{
    try
    {
        Exiv2::ExifKey exifKey(exifTagName);
        Exiv2::ExifData exifData(d->exifMetadata);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
        if (it != exifData.end())
        {
            TQByteArray data((*it).size());
            if (data.size())
            {
#if (EXIV2_TEST_VERSION(0,17,91))
                (*it).copy((Exiv2::byte*)data.data(), Exiv2::bigEndian);
#else
                (*it).copy((Exiv2::byte*)data.data(), exifData.byteOrder());
#endif
            }
            return data;
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError(TQString("Cannot find Exif key '%1' into image using Exiv2 ")
                                 .arg(exifTagName), e);
    }

    return TQByteArray();
}

TQByteArray KExiv2::getIptcTagData(const char *iptcTagName) const
{
    try
    {
        Exiv2::IptcKey iptcKey(iptcTagName);
        Exiv2::IptcData iptcData(d->iptcMetadata);
        Exiv2::IptcData::iterator it = iptcData.findKey(iptcKey);
        if (it != iptcData.end())
        {
            TQByteArray data((*it).size());
            if (data.size())
                (*it).copy((Exiv2::byte*)data.data(), Exiv2::bigEndian);
            return data;
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError(TQString("Cannot find Iptc key '%1' into image using Exiv2 ")
                                 .arg(iptcTagName), e);
    }

    return TQByteArray();
}

bool KExiv2::getExifTagRational(const char *exifTagName, long int &num, long int &den, int component) const
{
    try
    {
        Exiv2::ExifKey exifKey(exifTagName);
        Exiv2::ExifData exifData(d->exifMetadata);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
        if (it != exifData.end())
        {
            num = (*it).toRational(component).first;
            den = (*it).toRational(component).second;
            return true;
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError(TQString("Cannot find Exif Rational value from key '%1' "
                                         "into image using Exiv2 ").arg(exifTagName), e);
    }

    return false;
}

bool KExiv2::setExifTagLong(const char *exifTagName, long val, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        d->exifMetadata[exifTagName] = static_cast<int32_t>(val);
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Exif tag long value into image using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::setExifTagRational(const char *exifTagName, long int num, long int den, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        d->exifMetadata[exifTagName] = Exiv2::Rational(num, den);
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Exif tag rational value into image using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::setExifTagData(const char *exifTagName, const TQByteArray& data, bool setProgramName)
{
    if (data.isEmpty())
    return false;

    if (!setProgramId(setProgramName))
        return false;

    try
    {
        Exiv2::DataValue val((Exiv2::byte *)data.data(), data.size());
        d->exifMetadata[exifTagName] = val;
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Exif tag data into image using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::setIptcTagData(const char *iptcTagName, const TQByteArray& data, bool setProgramName)
{
    if (data.isEmpty())
    return false;

    if (!setProgramId(setProgramName))
        return false;

    try
    {
        Exiv2::DataValue val((Exiv2::byte *)data.data(), data.size());
        d->iptcMetadata[iptcTagName] = val;
        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Iptc tag data into image using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::removeExifTag(const char *exifTagName, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        Exiv2::ExifKey exifKey(exifTagName);
        Exiv2::ExifData::iterator it = d->exifMetadata.findKey(exifKey);
        if (it != d->exifMetadata.end())
        {
            d->exifMetadata.erase(it);
            return true;
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot remove Exif tag using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::removeIptcTag(const char *iptcTagName, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        Exiv2::IptcData::iterator it = d->iptcMetadata.begin();
        while(it != d->iptcMetadata.end())
        {
            TQString key = TQString::fromLocal8Bit(it->key().c_str());

            if (key == TQString(iptcTagName))
                it = d->iptcMetadata.erase(it);
            else
                ++it;
        };

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot remove Iptc tag using Exiv2 ", e);
    }

    return false;
}

TQString KExiv2::getExifTagTitle(const char *exifTagName)
{
    try
    {
        std::string exifkey(exifTagName);
        Exiv2::ExifKey ek(exifkey);
#if EXIV2_TEST_VERSION(0, 21, 0)
        return TQString::fromLocal8Bit( ek.tagLabel().c_str() );
#else
        return TQString::fromLocal8Bit( Exiv2::ExifTags::tagTitle(ek.tag(), ek.ifdId()) );
#endif
    }
    catch (Exiv2::Error& e)
    {
        d->printExiv2ExceptionError("Cannot get metadata tag title using Exiv2 ", e);
    }

    return TQString();
}

TQString KExiv2::getExifTagDescription(const char *exifTagName)
{
    try
    {
        std::string exifkey(exifTagName);
        Exiv2::ExifKey ek(exifkey);
#if EXIV2_TEST_VERSION(0, 21, 0)
        return TQString::fromLocal8Bit( ek.tagDesc().c_str() );
#else
        return TQString::fromLocal8Bit( Exiv2::ExifTags::tagDesc(ek.tag(), ek.ifdId()) );
#endif
    }
    catch (Exiv2::Error& e)
    {
        d->printExiv2ExceptionError("Cannot get metadata tag description using Exiv2 ", e);
    }

    return TQString();
}

TQString KExiv2::getIptcTagTitle(const char *iptcTagName)
{
    try
    {
        std::string iptckey(iptcTagName);
        Exiv2::IptcKey ik(iptckey);
        return TQString::fromLocal8Bit( Exiv2::IptcDataSets::dataSetTitle(ik.tag(), ik.record()) );
    }
    catch (Exiv2::Error& e)
    {
        d->printExiv2ExceptionError("Cannot get metadata tag title using Exiv2 ", e);
    }

    return TQString();
}

TQString KExiv2::getIptcTagDescription(const char *iptcTagName)
{
    try
    {
        std::string iptckey(iptcTagName);
        Exiv2::IptcKey ik(iptckey);
        return TQString::fromLocal8Bit( Exiv2::IptcDataSets::dataSetDesc(ik.tag(), ik.record()) );
    }
    catch (Exiv2::Error& e)
    {
        d->printExiv2ExceptionError("Cannot get metadata tag description using Exiv2 ", e);
    }

    return TQString();
}

KExiv2::MetaDataMap KExiv2::getExifTagsDataList(const TQStringList &exifKeysFilter, bool invertSelection)
{
    if (d->exifMetadata.empty())
       return MetaDataMap();

    try
    {
        Exiv2::ExifData exifData = d->exifMetadata;
        exifData.sortByKey();

        TQString     ifDItemName;
        MetaDataMap metaDataMap;

        for (Exiv2::ExifData::iterator md = exifData.begin(); md != exifData.end(); ++md)
        {
            TQString key = TQString::fromAscii(md->key().c_str());

            // Decode the tag value with a user friendly output.
            TQString tagValue;
            if (key == "Exif.Photo.UserComment")
            {
                tagValue = d->convertCommentValue(*md);
            }
            else
            {
                std::ostringstream os;
                os << *md;

                // Exif tag contents can be an i18n strings, no only simple ascii.
                tagValue = TQString::fromLocal8Bit(os.str().c_str());
            }
            tagValue.replace("\n", " ");

            // We apply a filter to get only the Exif tags that we need.

            if (!invertSelection)
            {
                if (exifKeysFilter.contains(key.section(".", 1, 1)))
                    metaDataMap.insert(key, tagValue);
            }
            else
            {
                if (!exifKeysFilter.contains(key.section(".", 1, 1)))
                    metaDataMap.insert(key, tagValue);
            }
        }

        return metaDataMap;
    }
    catch (Exiv2::Error& e)
    {
        d->printExiv2ExceptionError("Cannot parse EXIF metadata using Exiv2 ", e);
    }

    return MetaDataMap();
}

KExiv2::MetaDataMap KExiv2::getIptcTagsDataList(const TQStringList &iptcKeysFilter, bool invertSelection)
{
    if (d->iptcMetadata.empty())
       return MetaDataMap();

    try
    {
        Exiv2::IptcData iptcData = d->iptcMetadata;
        iptcData.sortByKey();

        TQString     ifDItemName;
        MetaDataMap metaDataMap;

        for (Exiv2::IptcData::iterator md = iptcData.begin(); md != iptcData.end(); ++md)
        {
            TQString key = TQString::fromAscii(md->key().c_str());

            // Decode the tag value with a user friendly output.
            std::ostringstream os;
            os << *md;
            TQString value = TQString::fromAscii(os.str().c_str());
            // To make a string just on one line.
            value.replace("\n", " ");

            // Some IPTC key are redondancy. check if already one exist...
            MetaDataMap::iterator it = metaDataMap.find(key);

            // We apply a filter to get only the Exif tags that we need.

            if (!invertSelection)
            {
                if (iptcKeysFilter.contains(key.section(".", 1, 1)))
                {
                    if (it == metaDataMap.end())
                        metaDataMap.insert(key, value);
                    else
                    {
                        TQString v = *it;
                        v.append(", ");
                        v.append(value);
                        metaDataMap.replace(key, v);
                    }
                }
            }
            else
            {
                if (!iptcKeysFilter.contains(key.section(".", 1, 1)))
                {
                    if (it == metaDataMap.end())
                        metaDataMap.insert(key, value);
                    else
                    {
                        TQString v = *it;
                        v.append(", ");
                        v.append(value);
                        metaDataMap.replace(key, v);
                    }
                }
            }
        }

        return metaDataMap;
    }
    catch (Exiv2::Error& e)
    {
        d->printExiv2ExceptionError("Cannot parse IPTC metadata using Exiv2 ", e);
    }

    return MetaDataMap();
}

bool KExiv2::getGPSInfo(double& altitude, double& latitude, double& longitude) const
{
    try
    {
        double num, den, min, sec;
        latitude=0.0, longitude=0.0, altitude=0.0;

        // Get the reference in first.

        TQByteArray latRef = getExifTagData("Exif.GPSInfo.GPSLatitudeRef");
        if (latRef.isEmpty()) return false;

        TQByteArray lngRef = getExifTagData("Exif.GPSInfo.GPSLongitudeRef");
        if (lngRef.isEmpty()) return false;

        TQByteArray altRef = getExifTagData("Exif.GPSInfo.GPSAltitudeRef");

        // Latitude decoding.

        Exiv2::ExifKey exifKey("Exif.GPSInfo.GPSLatitude");
        Exiv2::ExifData exifData(d->exifMetadata);
        Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
        if (it != exifData.end())
        {
            num = (double)((*it).toRational(0).first);
            den = (double)((*it).toRational(0).second);
            latitude = num/den;

            num = (double)((*it).toRational(1).first);
            den = (double)((*it).toRational(1).second);
            min = num/den;
            if (min != -1.0)
                latitude = latitude + min/60.0;

            num = (double)((*it).toRational(2).first);
            den = (double)((*it).toRational(2).second);
            sec = num/den;
            if (sec != -1.0)
                latitude = latitude + sec/3600.0;
        }
        else
            return false;

        if (latRef[0] == 'S') latitude *= -1.0;

        // Longitude decoding.

        Exiv2::ExifKey exifKey2("Exif.GPSInfo.GPSLongitude");
        it = exifData.findKey(exifKey2);
        if (it != exifData.end())
        {
            num = (double)((*it).toRational(0).first);
            den = (double)((*it).toRational(0).second);
            longitude = num/den;

            num = (double)((*it).toRational(1).first);
            den = (double)((*it).toRational(1).second);
            min = num/den;
            if (min != -1.0)
                longitude = longitude + min/60.0;

            num = (double)((*it).toRational(2).first);
            den = (double)((*it).toRational(2).second);
            sec = num/den;
            if (sec != -1.0)
                longitude = longitude + sec/3600.0;
        }
        else
            return false;

        if (lngRef[0] == 'W') longitude *= -1.0;

        // Altitude decoding.

        if (!altRef.isEmpty())
        {
            Exiv2::ExifKey exifKey3("Exif.GPSInfo.GPSAltitude");
            it = exifData.findKey(exifKey3);
            if (it != exifData.end())
            {
                num = (double)((*it).toRational(0).first);
                den = (double)((*it).toRational(0).second);
                altitude = num/den;
            }

            if (altRef[0] == '1') altitude *= -1.0;
        }

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot get Exif GPS tag using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::setGPSInfo(double altitude, double latitude, double longitude, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        // In first, we need to clean up all existing GPS info.
        removeGPSInfo();

        char scratchBuf[100];
        long int nom, denom;
        long int deg, min;

        // Do all the easy constant ones first.
        // GPSVersionID tag: standard says is should be four bytes: 02 00 00 00
        // (and, must be present).
        Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
        value->read("2 0 0 0");
        d->exifMetadata.add(Exiv2::ExifKey("Exif.GPSInfo.GPSVersionID"), value.get());

        // Datum: the datum of the measured data. If not given, we insert WGS-84.
        d->exifMetadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84";

        // Now start adding data.

        // ALTITUDE.
        // Altitude reference: byte "00" meaning "above sea level", "01" mening "behing sea level".
        value = Exiv2::Value::create(Exiv2::unsignedByte);
    if (altitude >= 0) value->read("0");
    else               value->read("1");
        d->exifMetadata.add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), value.get());

        // And the actual altitude, as absolute value.
        convertToRational(fabs(altitude), &nom, &denom, 4);
        snprintf(scratchBuf, 100, "%ld/%ld", nom, denom);
        d->exifMetadata["Exif.GPSInfo.GPSAltitude"] = scratchBuf;

        // LATTITUDE
        // Latitude reference: "N" or "S".
        if (latitude < 0)
        {
            // Less than Zero: ie, minus: means
            // Southern hemisphere. Where I live.
            d->exifMetadata["Exif.GPSInfo.GPSLatitudeRef"] = "S";
        }
        else
        {
            // More than Zero: ie, plus: means
            // Northern hemisphere.
            d->exifMetadata["Exif.GPSInfo.GPSLatitudeRef"] = "N";
        }

        // Now the actual lattitude itself.
        // This is done as three rationals.
        // I choose to do it as:
        //   dd/1 - degrees.
        //   mmmm/100 - minutes
        //   0/1 - seconds
        // Exif standard says you can do it with minutes
        // as mm/1 and then seconds as ss/1, but its
        // (slightly) more accurate to do it as
        //  mmmm/100 than to split it.
        // We also absolute the value (with fabs())
        // as the sign is encoded in LatRef.
        // Further note: original code did not translate between
        //   dd.dddddd to dd mm.mm - that's why we now multiply
        //   by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
        deg   = (int)floor(fabs(latitude)); // Slice off after decimal.
        min   = (int)floor((fabs(latitude) - floor(fabs(latitude))) * 60000000);
        snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
        d->exifMetadata["Exif.GPSInfo.GPSLatitude"] = scratchBuf;

        // LONGITUDE
        // Longitude reference: "E" or "W".
        if (longitude < 0)
        {
            // Less than Zero: ie, minus: means
            // Western hemisphere.
            d->exifMetadata["Exif.GPSInfo.GPSLongitudeRef"] = "W";
        }
        else
        {
            // More than Zero: ie, plus: means
            // Eastern hemisphere. Where I live.
            d->exifMetadata["Exif.GPSInfo.GPSLongitudeRef"] = "E";
        }

        // Now the actual longitude itself.
        // This is done as three rationals.
        // I choose to do it as:
        //   dd/1 - degrees.
        //   mmmm/100 - minutes
        //   0/1 - seconds
        // Exif standard says you can do it with minutes
        // as mm/1 and then seconds as ss/1, but its
        // (slightly) more accurate to do it as
        //  mmmm/100 than to split it.
        // We also absolute the value (with fabs())
        // as the sign is encoded in LongRef.
        // Further note: original code did not translate between
        //   dd.dddddd to dd mm.mm - that's why we now multiply
        //   by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
        deg   = (int)floor(fabs(longitude)); // Slice off after decimal.
        min   = (int)floor((fabs(longitude) - floor(fabs(longitude))) * 60000000);
        snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
        d->exifMetadata["Exif.GPSInfo.GPSLongitude"] = scratchBuf;

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Exif GPS tag using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::removeGPSInfo(bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        TQStringList gpsTagsKeys;

        for (Exiv2::ExifData::iterator it = d->exifMetadata.begin();
             it != d->exifMetadata.end(); ++it)
        {
            TQString key = TQString::fromLocal8Bit(it->key().c_str());

            if (key.section(".", 1, 1) == TQString("GPSInfo"))
                gpsTagsKeys.append(key);
        }

        for(TQStringList::Iterator it2 = gpsTagsKeys.begin(); it2 != gpsTagsKeys.end(); ++it2)
        {
            Exiv2::ExifKey gpsKey((*it2).ascii());
            Exiv2::ExifData::iterator it3 = d->exifMetadata.findKey(gpsKey);
            if (it3 != d->exifMetadata.end())
                d->exifMetadata.erase(it3);
        }

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot remove Exif GPS tag using Exiv2 ", e);
    }

    return false;
}

void KExiv2::convertToRational(double number, long int* numerator,
                               long int* denominator, int rounding)
{
    // This function converts the given decimal number
    // to a rational (fractional) number.
    //
    // Examples in comments use Number as 25.12345, Rounding as 4.

    // Split up the number.
    double whole      = trunc(number);
    double fractional = number - whole;

    // Calculate the "number" used for rounding.
    // This is 10^Digits - ie, 4 places gives us 10000.
    double rounder = pow(10.0, rounding);

    // Round the fractional part, and leave the number
    // as greater than 1.
    // To do this we: (for example)
    //  0.12345 * 10000 = 1234.5
    //  floor(1234.5) = 1234 - now bigger than 1 - ready...
    fractional = round(fractional * rounder);

    // Convert the whole thing to a fraction.
    // Fraction is:
    //     (25 * 10000) + 1234   251234
    //     ------------------- = ------ = 25.1234
    //           10000            10000
    double numTemp = (whole * rounder) + fractional;
    double denTemp = rounder;

    // Now we should reduce until we can reduce no more.

    // Try simple reduction...
    // if   Num
    //     ----- = integer out then....
    //      Den
    if (trunc(numTemp / denTemp) == (numTemp / denTemp))
    {
        // Divide both by Denominator.
        numTemp /= denTemp;
        denTemp /= denTemp;
    }

    // And, if that fails, brute force it.
    while (1)
    {
        // Jump out if we can't integer divide one.
        if ((numTemp / 2) != trunc(numTemp / 2)) break;
        if ((denTemp / 2) != trunc(denTemp / 2)) break;
        // Otherwise, divide away.
        numTemp /= 2;
        denTemp /= 2;
    }

    // Copy out the numbers.
    *numerator   = (int)numTemp;
    *denominator = (int)denTemp;
}

TQStringList KExiv2::getImageKeywords() const
{
    try
    {
        if (!d->iptcMetadata.empty())
        {
            TQStringList keywords;
            Exiv2::IptcData iptcData(d->iptcMetadata);

            for (Exiv2::IptcData::iterator it = iptcData.begin(); it != iptcData.end(); ++it)
            {
                TQString key = TQString::fromLocal8Bit(it->key().c_str());

                if (key == TQString("Iptc.Application2.Keywords"))
                {
                    TQString val(it->toString().c_str());
                    keywords.append(val);
                }
            }

            return keywords;
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot get IPTC Keywords from image using Exiv2 ", e);
    }

    return TQStringList();
}

bool KExiv2::setImageKeywords(const TQStringList& oldKeywords, const TQStringList& newKeywords,
                              bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        TQStringList oldkeys = oldKeywords;
        TQStringList newkeys = newKeywords;

        tqDebug("%s ==> Keywords: %s", d->filePath.ascii(), newkeys.join(",").ascii());

        // Remove all old keywords.
        Exiv2::IptcData iptcData(d->iptcMetadata);
        Exiv2::IptcData::iterator it = iptcData.begin();

        while(it != iptcData.end())
        {
            TQString key = TQString::fromLocal8Bit(it->key().c_str());
            TQString val(it->toString().c_str());

            // Also remove new keywords to avoid duplicates. They will be added again below.
            if ( key == TQString("Iptc.Application2.Keywords") &&
                 (oldKeywords.contains(val) || newKeywords.contains(val))
               )
                it = iptcData.erase(it);
            else
                ++it;
        };

        // Add new keywords. Note that Keywords IPTC tag is limited to 64 char but can be redondant.

        Exiv2::IptcKey iptcTag("Iptc.Application2.Keywords");

        for (TQStringList::iterator it = newkeys.begin(); it != newkeys.end(); ++it)
        {
            TQString key = *it;
            key.truncate(64);

            Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
            val->read(key.latin1());
            iptcData.add(iptcTag, val.get());
        }

        d->iptcMetadata = iptcData;

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set IPTC Keywords into image using Exiv2 ", e);
    }

    return false;
}

TQStringList KExiv2::getImageSubjects() const
{
    try
    {
        if (!d->iptcMetadata.empty())
        {
            TQStringList subjects;
            Exiv2::IptcData iptcData(d->iptcMetadata);

            for (Exiv2::IptcData::iterator it = iptcData.begin(); it != iptcData.end(); ++it)
            {
                TQString key = TQString::fromLocal8Bit(it->key().c_str());

                if (key == TQString("Iptc.Application2.Subject"))
                {
                    TQString val(it->toString().c_str());
                    subjects.append(val);
                }
            }

            return subjects;
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot get IPTC Subjects from image using Exiv2 ", e);
    }

    return TQStringList();
}

bool KExiv2::setImageSubjects(const TQStringList& oldSubjects, const TQStringList& newSubjects,
                              bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        TQStringList oldDef = oldSubjects;
        TQStringList newDef = newSubjects;

        // Remove all old subjects.
        Exiv2::IptcData iptcData(d->iptcMetadata);
        Exiv2::IptcData::iterator it = iptcData.begin();

        while(it != iptcData.end())
        {
            TQString key = TQString::fromLocal8Bit(it->key().c_str());
            TQString val(it->toString().c_str());

            if (key == TQString("Iptc.Application2.Subject") && oldDef.contains(val))
                it = iptcData.erase(it);
            else
                ++it;
        };

        // Add new subjects. Note that Keywords IPTC tag is limited to 236 char but can be redondant.

        Exiv2::IptcKey iptcTag("Iptc.Application2.Subject");

        for (TQStringList::iterator it = newDef.begin(); it != newDef.end(); ++it)
        {
            TQString key = *it;
            key.truncate(236);

            Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
            val->read(key.latin1());
            iptcData.add(iptcTag, val.get());
        }

        d->iptcMetadata = iptcData;

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set IPTC Subjects into image using Exiv2 ", e);
    }

    return false;
}

TQStringList KExiv2::getImageSubCategories() const
{
    try
    {
        if (!d->iptcMetadata.empty())
        {
            TQStringList subCategories;
            Exiv2::IptcData iptcData(d->iptcMetadata);

            for (Exiv2::IptcData::iterator it = iptcData.begin(); it != iptcData.end(); ++it)
            {
                TQString key = TQString::fromLocal8Bit(it->key().c_str());

                if (key == TQString("Iptc.Application2.SuppCategory"))
                {
                    TQString val(it->toString().c_str());
                    subCategories.append(val);
                }
            }

            return subCategories;
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot get IPTC Sub Categories from image using Exiv2 ", e);
    }

    return TQStringList();
}

bool KExiv2::setImageSubCategories(const TQStringList& oldSubCategories, const TQStringList& newSubCategories,
                                   bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        TQStringList oldkeys = oldSubCategories;
        TQStringList newkeys = newSubCategories;

        // Remove all old Sub Categories.
        Exiv2::IptcData iptcData(d->iptcMetadata);
        Exiv2::IptcData::iterator it = iptcData.begin();

        while(it != iptcData.end())
        {
            TQString key = TQString::fromLocal8Bit(it->key().c_str());
            TQString val(it->toString().c_str());

            if (key == TQString("Iptc.Application2.SuppCategory") && oldSubCategories.contains(val))
                it = iptcData.erase(it);
            else
                ++it;
        };

        // Add new Sub Categories. Note that SubCategories IPTC tag is limited to 32
        // characters but can be redondant.

        Exiv2::IptcKey iptcTag("Iptc.Application2.SuppCategory");

        for (TQStringList::iterator it = newkeys.begin(); it != newkeys.end(); ++it)
        {
            TQString key = *it;
            key.truncate(32);

            Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string);
            val->read(key.latin1());
            iptcData.add(iptcTag, val.get());
        }

        d->iptcMetadata = iptcData;

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set IPTC Sub Categories into image using Exiv2 ", e);
    }

    return false;
}

TQString KExiv2::getExifComment() const
{
    try
    {
        if (!d->exifMetadata.empty())
        {
            Exiv2::ExifKey key("Exif.Photo.UserComment");
            Exiv2::ExifData exifData(d->exifMetadata);
            Exiv2::ExifData::iterator it = exifData.findKey(key);

            if (it != exifData.end())
            {
                TQString exifComment = d->convertCommentValue(*it);

                // some cameras fill the UserComment with whitespace
                if (!exifComment.isEmpty() && !exifComment.stripWhiteSpace().isEmpty())
                    return exifComment;
            }
        }
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot find Exif User Comment using Exiv2 ", e);
    }

    return TQString();
}

bool KExiv2::setExifComment(const TQString& comment, bool setProgramName)
{
    if (!setProgramId(setProgramName))
        return false;

    try
    {
        if (comment.isEmpty())
            return false;

    // Write as Unicode only when necessary.
    TQTextCodec *latin1Codec = TQTextCodec::codecForName("iso8859-1");
    if (latin1Codec->canEncode(comment))
    {
        // write as ASCII
        std::string exifComment("charset=\"Ascii\" ");
        exifComment += comment.latin1();
        d->exifMetadata["Exif.Photo.UserComment"] = exifComment;
    }
    else
    {
        // write as Unicode (UCS-2)

        // Be aware that we are dealing with a UCS-2 string.
        // Null termination means \0\0, strlen does not work,
        // do not use any const-char*-only methods,
        // pass a std::string and not a const char * to ExifDatum::operator=().
        const unsigned short *ucs2 = comment.ucs2();
        std::string exifComment("charset=\"Unicode\" ");
        exifComment.append((const char*)ucs2, sizeof(unsigned short) * comment.length());
        d->exifMetadata["Exif.Photo.UserComment"] = exifComment;
    }

        return true;
    }
    catch( Exiv2::Error &e )
    {
        d->printExiv2ExceptionError("Cannot set Exif Comment using Exiv2 ", e);
    }

    return false;
}

bool KExiv2::setProgramId(bool /*on*/)
{
    return true;
}

}  // NameSpace KExiv2Iface
