/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2006-02-23
 * Description : image metadata interface
 *
 * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
 * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
 *
 * 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.
 *
 * ============================================================ */

// TQt includes.

#include <tqdom.h>
#include <tqfile.h>

// LibKDcraw includes.

#include <libkdcraw/dcrawinfocontainer.h>
#include <libkdcraw/kdcraw.h>

// Local includes.

#include "daboutdata.h"
#include "constants.h"
#include "ddebug.h"
#include "dmetadata.h"

namespace Digikam
{

DMetadata::DMetadata()
         : KExiv2Iface::KExiv2()
{
}

DMetadata::DMetadata(const TQString& filePath)
         : KExiv2Iface::KExiv2()
{
    load(filePath);
}

DMetadata::~DMetadata()
{
}

bool DMetadata::load(const TQString& filePath)
{
    // In first, we trying to get metadata using Exiv2,
    // else we will use dcraw to extract minimal information.

    if (!KExiv2::load(filePath))
    {
        if (!loadUsingDcraw(filePath))
            return false;
    }

    return true;
}

bool DMetadata::loadUsingDcraw(const TQString& filePath)
{
    KDcrawIface::DcrawInfoContainer identify;
    if (KDcrawIface::KDcraw::rawFileIdentify(identify, filePath))
    {
        long int num=1, den=1;

        if (!identify.model.isNull())
            setExifTagString("Exif.Image.Model", identify.model.latin1(), false);

        if (!identify.make.isNull())
            setExifTagString("Exif.Image.Make", identify.make.latin1(), false);

        if (!identify.owner.isNull())
            setExifTagString("Exif.Image.Artist", identify.owner.latin1(), false);

        if (identify.sensitivity != -1)
            setExifTagLong("Exif.Photo.ISOSpeedRatings", identify.sensitivity, false);

        if (identify.dateTime.isValid())
            setImageDateTime(identify.dateTime, false, false);

        if (identify.exposureTime != -1.0)
        {
            convertToRational(1/identify.exposureTime, &num, &den, 8);
            setExifTagRational("Exif.Photo.ExposureTime", num, den, false);
        }

        if (identify.aperture != -1.0)
        {
            convertToRational(identify.aperture, &num, &den, 8);
            setExifTagRational("Exif.Photo.ApertureValue", num, den, false);
        }

        if (identify.focalLength != -1.0)
        {
            convertToRational(identify.focalLength, &num, &den, 8);
            setExifTagRational("Exif.Photo.FocalLength", num, den, false);
        }

        if (identify.imageSize.isValid())
            setImageDimensions(identify.imageSize, false);

        // A RAW image is always uncalibrated. */
        setImageColorWorkSpace(WORKSPACE_UNCALIBRATED, false);

        return true;
    }

    return false;
}

TQString DMetadata::getImageComment() const
{
    if (getFilePath().isEmpty())
        return TQString();

    // In first we trying to get image comments, outside of Exif and IPTC.

    TQString comment = getCommentsDecoded();
    if (!comment.isEmpty())
        return comment;

    // In second, we trying to get Exif comments

    if (!getExif().isEmpty())
    {
        TQString exifComment = getExifComment();
        if (!exifComment.isEmpty())
            return exifComment;
    }

    // In third, we trying to get IPTC comments

    if (!getIptc().isEmpty())
    {
        TQString iptcComment = getIptcTagString("Iptc.Application2.Caption", false);
        if (!iptcComment.isEmpty() && !iptcComment.stripWhiteSpace().isEmpty())
            return iptcComment;
    }

    return TQString();
}

bool DMetadata::setImageComment(const TQString& comment)
{
    //See bug #139313: An empty string is also a valid value
    //if (comment.isEmpty())
    //    return false;

    DDebug() << getFilePath() << " ==> Comment: " << comment << endl;

    if (!setProgramId())
        return false;

    // In first we trying to set image comments, outside of Exif and IPTC.

    if (!setComments(comment.utf8()))
        return false;

    // In Second we write comments into Exif.

    if (!setExifComment(comment))
        return false;

    // In Third we write comments into Iptc.
    // Note that Caption IPTC tag is limited to 2000 char and ASCII charset.

    TQString commentIptc = comment;
    commentIptc.truncate(2000);

    if (!setIptcTagString("Iptc.Application2.Caption", commentIptc))
        return false;

    return true;
}

/*
Iptc.Application2.Urgency <==> digiKam Rating links:

digiKam     IPTC
Rating      Urgency

0 star  <=>  8          // Least important
1 star  <=>  7
1 star  <==  6
2 star  <=>  5
3 star  <=>  4
4 star  <==  3
4 star  <=>  2
5 star  <=>  1          // Most important
*/

int DMetadata::getImageRating() const
{
    if (getFilePath().isEmpty())
        return -1;

    // Check Exif rating tag set by Windows Vista
    // Note : no need to check rating in percent tags (Exif.image.0x4747) here because
    // its appear always with rating tag value (Exif.image.0x4749).

    if (!getExif().isEmpty())
    {
        long rating = -1;
        if (getExifTagLong("Exif.Image.0x4746", rating))
        {
            if (rating >= RatingMin && rating <= RatingMax)
                return rating;
        }
    }

    // Check Iptc Urgency tag content

    if (!getIptc().isEmpty())
    {
        TQString IptcUrgency(getIptcTagData("Iptc.Application2.Urgency"));

        if (!IptcUrgency.isEmpty())
        {
            if (IptcUrgency == TQString("1"))
                return 5;
            else if (IptcUrgency == TQString("2"))
                return 4;
            else if (IptcUrgency == TQString("3"))
                return 4;
            else if (IptcUrgency == TQString("4"))
                return 3;
            else if (IptcUrgency == TQString("5"))
                return 2;
            else if (IptcUrgency == TQString("6"))
                return 1;
            else if (IptcUrgency == TQString("7"))
                return 1;
            else if (IptcUrgency == TQString("8"))
                return 0;
        }
    }

    return -1;
}

bool DMetadata::setImageRating(int rating)
{
    if (rating < RatingMin || rating > RatingMax)
    {
        DDebug() << k_funcinfo << "Rating value to write is out of range!" << endl;
        return false;
    }

    DDebug() << getFilePath() << " ==> Rating: " << rating << endl;

    if (!setProgramId())
        return false;

    // Set Exif rating tag used by Windows Vista.

    if (!setExifTagLong("Exif.Image.0x4746", rating))
        return false;

    // Wrapper around rating percents managed by Windows Vista.
    int ratePercents = 0;
    switch(rating)
    {
        case 0:
            ratePercents = 0;
            break;
        case 1:
            ratePercents = 1;
            break;
        case 2:
            ratePercents = 25;
            break;
        case 3:
            ratePercents = 50;
            break;
        case 4:
            ratePercents = 75;
            break;
        case 5:
            ratePercents = 99;
            break;
    }

    if (!setExifTagLong("Exif.Image.0x4749", ratePercents))
        return false;

    // Set Iptc Urgency tag value.

    TQString urgencyTag;

    switch(rating)
    {
        case 0:
            urgencyTag = TQString("8");
            break;
        case 1:
            urgencyTag = TQString("7");
            break;
        case 2:
            urgencyTag = TQString("5");
            break;
        case 3:
            urgencyTag = TQString("4");
            break;
        case 4:
            urgencyTag = TQString("3");
            break;
        case 5:
            urgencyTag = TQString("1");
            break;
    }

    if (!setIptcTagString("Iptc.Application2.Urgency", urgencyTag))
        return false;

    return true;
}

bool DMetadata::setIptcTag(const TQString& text, int maxLength, const char* debugLabel, const char* tagKey)
{
    TQString truncatedText = text;
    truncatedText.truncate(maxLength);
    DDebug() << getFilePath() << " ==> " << debugLabel << ": " << truncatedText << endl;
    return setIptcTagString(tagKey, truncatedText);    // returns false if failed
}

bool DMetadata::setImagePhotographerId(const TQString& author, const TQString& authorTitle)
{
    if (!setProgramId())
        return false;

    //TODO  Exernalize the hard-coded values
    if (!setIptcTag(author,      32, "Author",       "Iptc.Application2.Byline"))      return false;
    if (!setIptcTag(authorTitle, 32, "Author Title", "Iptc.Application2.BylineTitle")) return false;

    return true;
}

bool DMetadata::setImageCredits(const TQString& credit, const TQString& source, const TQString& copyright)
{
    if (!setProgramId())
        return false;

    //TODO  Exernalize the hard-coded values
    if (!setIptcTag(credit,     32, "Credit",    "Iptc.Application2.Credit"))    return false;
    if (!setIptcTag(source,     32, "Source",    "Iptc.Application2.Source"))    return false;
    if (!setIptcTag(copyright, 128, "Copyright", "Iptc.Application2.Copyright")) return false;

    return true;
}

bool DMetadata::setProgramId(bool on)
{
    if (on)
    {
        TQString version(digikam_version);
        TQString software("digiKam");
        return setImageProgramId(software, version);
    }

    return true;
}

PhotoInfoContainer DMetadata::getPhotographInformations() const
{
    PhotoInfoContainer photoInfo;

    if (!getExif().isEmpty())
    {
        photoInfo.dateTime = getImageDateTime();
        photoInfo.make     = getExifTagString("Exif.Image.Make");
        photoInfo.model    = getExifTagString("Exif.Image.Model");

        photoInfo.aperture = getExifTagString("Exif.Photo.FNumber");
        if (photoInfo.aperture.isEmpty())
            photoInfo.aperture = getExifTagString("Exif.Photo.ApertureValue");

        photoInfo.exposureTime = getExifTagString("Exif.Photo.ExposureTime");
        if (photoInfo.exposureTime.isEmpty())
            photoInfo.exposureTime = getExifTagString("Exif.Photo.ShutterSpeedValue");

        photoInfo.exposureMode    = getExifTagString("Exif.Photo.ExposureMode");
        photoInfo.exposureProgram = getExifTagString("Exif.Photo.ExposureProgram");

        photoInfo.focalLength     = getExifTagString("Exif.Photo.FocalLength");
        photoInfo.focalLength35mm = getExifTagString("Exif.Photo.FocalLengthIn35mmFilm");

        photoInfo.sensitivity = getExifTagString("Exif.Photo.ISOSpeedRatings");
        if (photoInfo.sensitivity.isEmpty())
            photoInfo.sensitivity = getExifTagString("Exif.Photo.ExposureIndex");

        photoInfo.flash = getExifTagString("Exif.Photo.Flash");
        photoInfo.whiteBalance = getExifTagString("Exif.Photo.WhiteBalance");
    }

    return photoInfo;
}

/**
The following methods set and get an XML dataset into a private IPTC.Application2 tags
to backup digiKam image properties. The XML text data are compressed using zlib and stored
like a byte array. The XML text data format are like below:

<?xml version="1.0" encoding="UTF-8"?>
<digikamproperties>
 <comments value="A cool photo from Adrien..." />
 <date value="2006-11-23T13:36:26" />
 <rating value="4" />
 <tagslist>
  <tag path="Gilles/Adrien/testphoto" />
  <tag path="monuments/Trocadero/Tour Eiffel" />
  <tag path="City/Paris" />
 </tagslist>
</digikamproperties>

*/

bool DMetadata::getXMLImageProperties(TQString& comments, TQDateTime& date,
                                      int& rating, TQStringList& tagsPath)
{
    rating = 0;

    TQByteArray data = getIptcTagData("Iptc.Application2.0x00ff");
    if (data.isEmpty())
        return false;
    TQByteArray decompressedData = tqUncompress(data);
    TQString doc;
    TQDataStream ds(decompressedData, IO_ReadOnly);
    ds >> doc;

    TQDomDocument xmlDoc;
    TQString error;
    int row, col;
    if (!xmlDoc.setContent(doc, true, &error, &row, &col))
    {
        DDebug() << doc << endl;
        DDebug() << error << " :: row=" << row << " , col=" << col << endl;
        return false;
    }

    TQDomElement rootElem = xmlDoc.documentElement();
    if (rootElem.tagName() != TQString::fromLatin1("digikamproperties"))
        return false;

    for (TQDomNode node = rootElem.firstChild();
         !node.isNull(); node = node.nextSibling())
    {
        TQDomElement e = node.toElement();
        TQString name  = e.tagName();
        TQString val   = e.attribute(TQString::fromLatin1("value"));

        if (name == TQString::fromLatin1("comments"))
        {
            comments = val;
        }
        else if (name == TQString::fromLatin1("date"))
        {
            if (val.isEmpty()) continue;
            date = TQDateTime::fromString(val, TQt::ISODate);
        }
        else if (name == TQString::fromLatin1("rating"))
        {
            if (val.isEmpty()) continue;
            bool ok=false;
            rating = val.toInt(&ok);
            if (!ok) rating = 0;
        }
        else if (name == TQString::fromLatin1("tagslist"))
        {
            for (TQDomNode node2 = e.firstChild();
                !node2.isNull(); node2 = node2.nextSibling())
            {
                TQDomElement e2 = node2.toElement();
                TQString name2  = e2.tagName();
                TQString val2   = e2.attribute(TQString::fromLatin1("path"));

                if (name2 == TQString::fromLatin1("tag"))
                {
                    if (val2.isEmpty()) continue;
                    tagsPath.append(val2);
                }
            }
        }
    }

    return true;
}

bool DMetadata::setXMLImageProperties(const TQString& comments, const TQDateTime& date,
                                      int rating, const TQStringList& tagsPath)
{
    TQDomDocument xmlDoc;

    xmlDoc.appendChild(xmlDoc.createProcessingInstruction( TQString::fromLatin1("xml"),
                       TQString::fromLatin1("version=\"1.0\" encoding=\"UTF-8\"") ) );

    TQDomElement propertiesElem = xmlDoc.createElement(TQString::fromLatin1("digikamproperties"));
    xmlDoc.appendChild( propertiesElem );

    TQDomElement c = xmlDoc.createElement(TQString::fromLatin1("comments"));
    c.setAttribute(TQString::fromLatin1("value"), comments);
    propertiesElem.appendChild(c);

    TQDomElement d = xmlDoc.createElement(TQString::fromLatin1("date"));
    d.setAttribute(TQString::fromLatin1("value"), date.toString(TQt::ISODate));
    propertiesElem.appendChild(d);

    TQDomElement r = xmlDoc.createElement(TQString::fromLatin1("rating"));
    r.setAttribute(TQString::fromLatin1("value"), rating);
    propertiesElem.appendChild(r);

    TQDomElement tagsElem = xmlDoc.createElement(TQString::fromLatin1("tagslist"));
    propertiesElem.appendChild(tagsElem);

    TQStringList path = tagsPath;
    for ( TQStringList::Iterator it = path.begin(); it != path.end(); ++it )
    {
        TQDomElement e = xmlDoc.createElement(TQString::fromLatin1("tag"));
        e.setAttribute(TQString::fromLatin1("path"), *it);
        tagsElem.appendChild(e);
    }

    TQByteArray  data, compressedData;
    TQDataStream ds(data, IO_WriteOnly);
    ds << xmlDoc.toString();
    compressedData = tqCompress(data);
    return (setIptcTagData("Iptc.Application2.0x00ff", compressedData));
}

}  // NameSpace Digikam
