/*
 *  HDRMerge - HDR exposure merging software.
 *  Copyright 2012 Javier Celaya
 *  jcelaya@gmail.com
 *
 *  This file is part of HDRMerge.
 *
 *  HDRMerge 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  HDRMerge 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 HDRMerge. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <exiv2/exiv2.hpp>
#include <iostream>
#include "ExifTransfer.hpp"
#include "Log.hpp"
using namespace hdrmerge;
using namespace Exiv2;
using namespace std;


class ExifTransfer {
public:
    ExifTransfer(const QString & srcFile, const QString & dstFile,
                 const uint8_t * data, size_t dataSize)
    : srcFile(srcFile), dstFile(dstFile), data(data), dataSize(dataSize) {}

    void copyMetadata();

private:
    QString srcFile, dstFile;
    const uint8_t * data;
    size_t dataSize;
    Exiv2::Image::AutoPtr src, dst;

    void copyXMP();
    void copyIPTC();
    void copyEXIF();
};


void hdrmerge::Exif::transfer(const QString & srcFile, const QString & dstFile,
                 const uint8_t * data, size_t dataSize) {
    ExifTransfer exif(srcFile, dstFile, data, dataSize);
    exif.copyMetadata();
}


void ExifTransfer::copyMetadata() {
    try {
        dst = Exiv2::ImageFactory::open(BasicIo::AutoPtr(new MemIo(data, dataSize)));
        dst->readMetadata();
    } catch (Exiv2::Error & e) {
        std::cerr << "Exiv2 error: " << e.what() << std::endl;
        return;
    }
    try {
        src = Exiv2::ImageFactory::open(srcFile.toLocal8Bit().constData());
        src->readMetadata();
        copyXMP();
        copyIPTC();
        copyEXIF();
    } catch (Exiv2::Error & e) {
        std::cerr << "Exiv2 error: " << e.what() << std::endl;
        // At least we have to set the SubImage1 file type to Primary Image
        dst->exifData()["Exif.SubImage1.NewSubfileType"] = 0;
    }
    try {
        dst->writeMetadata();
        FileIo fileIo(dstFile.toLocal8Bit().constData());
        fileIo.open("wb");
        fileIo.write(dst->io());
        fileIo.close();
    } catch (Exiv2::Error & e) {
        std::cerr << "Exiv2 error: " << e.what() << std::endl;
    }
}


void ExifTransfer::copyXMP() {
    const Exiv2::XmpData & srcXmp = src->xmpData();
    Exiv2::XmpData & dstXmp = dst->xmpData();
    for (const auto & datum : srcXmp) {
        if (datum.groupName() != "tiff" && dstXmp.findKey(Exiv2::XmpKey(datum.key())) == dstXmp.end()) {
            dstXmp.add(datum);
        }
    }
}


void ExifTransfer::copyIPTC() {
    const Exiv2::IptcData & srcIptc = src->iptcData();
    Exiv2::IptcData & dstIptc = dst->iptcData();
    for (const auto & datum : srcIptc) {
        if (dstIptc.findKey(Exiv2::IptcKey(datum.key())) == dstIptc.end()) {
            dstIptc.add(datum);
        }
    }
}


static bool excludeExifDatum(const Exifdatum & datum) {
    static const char * previewKeys[] {
        "Exif.OlympusCs.PreviewImageStart",
        "Exif.OlympusCs.PreviewImageLength",
        "Exif.Thumbnail.JPEGInterchangeFormat",
        "Exif.Thumbnail.JPEGInterchangeFormatLength",
        "Exif.NikonPreview.JPEGInterchangeFormat",
        "Exif.NikonPreview.JPEGInterchangeFormatLength",
        "Exif.Pentax.PreviewOffset",
        "Exif.Pentax.PreviewLength",
        "Exif.PentaxDng.PreviewOffset",
        "Exif.PentaxDng.PreviewLength",
        "Exif.Minolta.ThumbnailOffset",
        "Exif.Minolta.ThumbnailLength",
        "Exif.SonyMinolta.ThumbnailOffset",
        "Exif.SonyMinolta.ThumbnailLength",
        "Exif.Olympus.ThumbnailImage",
        "Exif.Olympus2.ThumbnailImage",
        "Exif.Minolta.Thumbnail",
        "Exif.PanasonicRaw.PreviewImage",
        "Exif.SamsungPreview.JPEGInterchangeFormat",
        "Exif.SamsungPreview.JPEGInterchangeFormatLength"
    };
    for (const char * pkey : previewKeys) {
        if (datum.key() == pkey) {
            return true;
        }
    }
    return
        datum.groupName().substr(0, 5) == "Thumb" ||
        datum.groupName().substr(0, 8) == "SubThumb" ||
        datum.groupName().substr(0, 5) == "Image" ||
        datum.groupName().substr(0, 8) == "SubImage";
}


void ExifTransfer::copyEXIF() {
    static const char * includeImageKeys[] = {
        // Correct Make and Model, from the input files
        // It is needed so that makernote tags are correctly copied
        "Exif.Image.Make",
        "Exif.Image.Model",
        "Exif.Image.Artist",
        "Exif.Image.Copyright",
        "Exif.Image.DNGPrivateData",
        // Opcodes generated by Adobe DNG converter
        "Exif.SubImage1.OpcodeList1",
        "Exif.SubImage1.OpcodeList2",
        "Exif.SubImage1.OpcodeList3"
    };

    const Exiv2::ExifData & srcExif = src->exifData();
    Exiv2::ExifData & dstExif = dst->exifData();

    for (const char * keyName : includeImageKeys) {
        auto iterator = srcExif.findKey(Exiv2::ExifKey(keyName));
        if (iterator != srcExif.end()) {
            dstExif[keyName] = *iterator;
        }
    }
    // Now we set the SubImage1 file type to Primary Image
    // Exiv2 wouldn't modify SubImage1 tags if it was set before
    dstExif["Exif.SubImage1.NewSubfileType"] = 0u;

    for (const auto & datum : srcExif) {
        if (!excludeExifDatum(datum) && dstExif.findKey(Exiv2::ExifKey(datum.key())) == dstExif.end()) {
            dstExif.add(datum);
        }
    }
}
