// vim: set tabstop=4 shiftwidth=4 expandtab:
/*
Gwenview: an image viewer
Copyright 2007 Aurélien Gâteau <agateau@kde.org>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

*/
// Self
#include "imagemetainfomodel.h"

// Qt

// KDE
#include <KDebug>
#include <KFileItem>
#include <KGlobal>
#include <KLocale>
#include <KExiv2>

// Local

namespace Gwenview
{

enum GroupRow {
    NoGroupSpace = -2,
    NoGroup = -1,
    GeneralGroup,
    ExifGroup,
    IptcGroup,
    XmpGroup
};

class MetaInfoGroup
{
public:
    enum {
        InvalidRow = -1
    };

    class Entry
    {
    public:
        Entry(const QString& key, const QString& label, const QString& value)
            : mKey(key), mLabel(label.trimmed()), mValue(value.trimmed())
        {}

        QString key() const
        {
            return mKey;
        }
        QString label() const
        {
            return mLabel;
        }

        QString value() const
        {
            return mValue;
        }
        void setValue(const QString& value)
    {
            mValue = value.trimmed();
        }

        void appendValue(const QString& value)
    {
            if (mValue.length() > 0) {
                mValue += '\n';
            }
            mValue += value.trimmed();
        }

    private:
        QString mKey;
        QString mLabel;
        QString mValue;
    };

    MetaInfoGroup(const QString& label)
        : mLabel(label)
        {}

    ~MetaInfoGroup()
    {
        qDeleteAll(mList);
    }

    void clear()
    {
        qDeleteAll(mList);
        mList.clear();
        mRowForKey.clear();
    }

    void addEntry(const QString& key, const QString& label, const QString& value)
    {
        addEntry(new Entry(key, label, value));
    }

    void addEntry(Entry* entry)
    {
        mList << entry;
        mRowForKey[entry->key()] = mList.size() - 1;
    }

    void getInfoForKey(const QString& key, QString* label, QString* value) const
    {
        Entry* entry = getEntryForKey(key);
        if (entry) {
            *label = entry->label();
            *value = entry->value();
        }
    }

    QString getKeyAt(int row) const
    {
        Q_ASSERT(row < mList.size());
        return mList[row]->key();
    }

    QString getLabelForKeyAt(int row) const
    {
        Q_ASSERT(row < mList.size());
        return mList[row]->label();
    }

    QString getValueForKeyAt(int row) const
    {
        Q_ASSERT(row < mList.size());
        return mList[row]->value();
    }

    void setValueForKeyAt(int row, const QString& value)
    {
        Q_ASSERT(row < mList.size());
        mList[row]->setValue(value);
    }

    int getRowForKey(const QString& key) const
    {
        return mRowForKey.value(key, InvalidRow);
    }

    int size() const
    {
        return mList.size();
    }

    QString label() const
    {
        return mLabel;
    }

    const QList<Entry*>& entryList() const {
        return mList;
    }

private:
    Entry* getEntryForKey(const QString& key) const
    {
        int row = getRowForKey(key);
        if (row == InvalidRow) {
            return 0;
        }
        return mList[row];
    }

    QList<Entry*> mList;
    QHash<QString, int> mRowForKey;
    QString mLabel;
};

struct ImageMetaInfoModelPrivate
{
    QVector<MetaInfoGroup*> mMetaInfoGroupVector;
    ImageMetaInfoModel* q;

    void clearGroup(MetaInfoGroup* group, const QModelIndex& parent)
    {
        if (group->size() > 0) {
            q->beginRemoveRows(parent, 0, group->size() - 1);
            group->clear();
            q->endRemoveRows();
        }
    }

    void setGroupEntryValue(GroupRow groupRow, const QString& key, const QString& value)
    {
        MetaInfoGroup* group = mMetaInfoGroupVector[groupRow];
        int entryRow = group->getRowForKey(key);
        if (entryRow == MetaInfoGroup::InvalidRow) {
            kWarning() << "No row for key" << key;
            return;
        }
        group->setValueForKeyAt(entryRow, value);
        QModelIndex groupIndex = q->index(groupRow, 0);
        QModelIndex entryIndex = q->index(entryRow, 1, groupIndex);
        emit q->dataChanged(entryIndex, entryIndex);
    }

    QVariant displayData(const QModelIndex& index) const
    {
        if (index.internalId() == NoGroup) {
            if (index.column() != 0) {
                return QVariant();
            }
            QString label = mMetaInfoGroupVector[index.row()]->label();
            return QVariant(label);
        }

        if (index.internalId() == NoGroupSpace) {
            return QVariant(QString());
        }

        MetaInfoGroup* group = mMetaInfoGroupVector[index.internalId()];
        if (index.column() == 0) {
            return group->getLabelForKeyAt(index.row());
        } else {
            return group->getValueForKeyAt(index.row());
        }
    }

    void initGeneralGroup()
    {
        MetaInfoGroup* group = mMetaInfoGroupVector[GeneralGroup];
        group->addEntry("General.Name", i18nc("@item:intable Image file name", "Name"), QString());
        group->addEntry("General.Size", i18nc("@item:intable", "File Size"), QString());
        group->addEntry("General.Time", i18nc("@item:intable", "File Time"), QString());
        group->addEntry("General.ImageSize", i18nc("@item:intable", "Image Size"), QString());
        group->addEntry("General.Comment", i18nc("@item:intable", "Comment"), QString());
    }
};

ImageMetaInfoModel::ImageMetaInfoModel()
: d(new ImageMetaInfoModelPrivate)
{
    d->q = this;
    d->mMetaInfoGroupVector.resize(4);
    d->mMetaInfoGroupVector[GeneralGroup] = new MetaInfoGroup(i18nc("@title:group General info about the image", "General"));
    d->mMetaInfoGroupVector[ExifGroup] = new MetaInfoGroup("EXIF");
    d->mMetaInfoGroupVector[IptcGroup] = new MetaInfoGroup("IPTC");
    d->mMetaInfoGroupVector[XmpGroup]  = new MetaInfoGroup("XMP");
    d->initGeneralGroup();
}

ImageMetaInfoModel::~ImageMetaInfoModel()
{
    qDeleteAll(d->mMetaInfoGroupVector);
    delete d;
}

void ImageMetaInfoModel::setUrl(const KUrl& url)
{
    KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url);
    QString sizeString = KGlobal::locale()->formatByteSize(item.size());

    d->setGroupEntryValue(GeneralGroup, "General.Name", item.name());
    d->setGroupEntryValue(GeneralGroup, "General.Size", sizeString);
    d->setGroupEntryValue(GeneralGroup, "General.Time", item.timeString());

    const KExiv2 kexiv2(url.path());
    KExiv2PropertyList exifprops;
    KExiv2PropertyList iptcprops;
    KExiv2PropertyList xmpprops;
    foreach (const KExiv2Property &kexiv2property, kexiv2.metadata()) {
        // there is vendor specific metadata that is not supposed to be user-visible
        if (kexiv2property.value.isEmpty() || kexiv2property.label.isEmpty()) {
            kDebug() << "Empty value or label, skipping" << kexiv2property.name;
            continue;
        } else if (kexiv2property.label.startsWith("0x")) {
            kDebug() << "Hex label, skipping" << kexiv2property.name;
            continue;
        }

        if (kexiv2property.name.startsWith("Exif.")) {
            exifprops.append(kexiv2property);
        } else if (kexiv2property.name.startsWith("Xmp.")) {
            iptcprops.append(kexiv2property);
        } else if (kexiv2property.name.startsWith("Iptc.")) {
            xmpprops.append(kexiv2property);
        } else {
            kWarning() << "Unknown key" << kexiv2property.name;
        }
    }

    MetaInfoGroup* exifGroup = d->mMetaInfoGroupVector[ExifGroup];
    MetaInfoGroup* iptcGroup = d->mMetaInfoGroupVector[IptcGroup];
    MetaInfoGroup* xmpGroup  = d->mMetaInfoGroupVector[XmpGroup];
    QModelIndex exifIndex = index(ExifGroup, 0);
    QModelIndex iptcIndex = index(IptcGroup, 0);
    QModelIndex xmpIndex  = index(XmpGroup, 0);
    d->clearGroup(exifGroup, exifIndex);
    d->clearGroup(iptcGroup, iptcIndex);
    d->clearGroup(xmpGroup,  xmpIndex);

    if (!exifprops.isEmpty()) {
        beginInsertRows(exifIndex, 0, exifprops.size() - 1);
        foreach (const KExiv2Property &exifprop, exifprops) {
            exifGroup->addEntry(exifprop.name, exifprop.label, exifprop.value);
        }
        endInsertRows();
    }

    if (!iptcprops.isEmpty()) {
        beginInsertRows(iptcIndex, 0, iptcprops.size() - 1);
        foreach (const KExiv2Property &iptcprop, iptcprops) {
            iptcGroup->addEntry(iptcprop.name, iptcprop.label, iptcprop.value);
        }
        endInsertRows();
    }

    if (!xmpprops.isEmpty()) {
        beginInsertRows(xmpIndex, 0, xmpprops.size() - 1);
        foreach (const KExiv2Property &xmpprop, xmpprops) {
            xmpGroup->addEntry(xmpprop.name, xmpprop.label, xmpprop.value);
        }
        endInsertRows();
    }
}

void ImageMetaInfoModel::setImageSize(const QSize& size)
{
    QString imageSize;
    if (size.isValid()) {
        imageSize = i18nc(
                        "@item:intable %1 is image width, %2 is image height",
                        "%1x%2", size.width(), size.height());

        double megaPixels = size.width() * size.height() / 1000000.;
        if (megaPixels > 0.1) {
            QString megaPixelsString = QString::number(megaPixels, 'f', 1);
            imageSize += ' ';
            imageSize += i18nc(
                             "@item:intable %1 is number of millions of pixels in image",
                             "(%1MP)", megaPixelsString);
        }
    } else {
        imageSize = '-';
    }
    d->setGroupEntryValue(GeneralGroup, "General.ImageSize", imageSize);
}

void ImageMetaInfoModel::getInfoForKey(const QString& key, QString* label, QString* value) const
{
    MetaInfoGroup* group;
    if (key.startsWith(QLatin1String("General"))) {
        group = d->mMetaInfoGroupVector[GeneralGroup];
    } else if (key.startsWith(QLatin1String("Exif"))) {
        group = d->mMetaInfoGroupVector[ExifGroup];
    } else if (key.startsWith(QLatin1String("Iptc"))) {
        group = d->mMetaInfoGroupVector[IptcGroup];
    } else if (key.startsWith(QLatin1String("Xmp"))) {
        group = d->mMetaInfoGroupVector[XmpGroup];
    } else {
        kWarning() << "Unknown metainfo key" << key;
        return;
    }
    group->getInfoForKey(key, label, value);
}

QString ImageMetaInfoModel::getValueForKey(const QString& key) const
{
    QString label, value;
    getInfoForKey(key, &label, &value);
    return value;
}

QString ImageMetaInfoModel::keyForIndex(const QModelIndex& index) const
{
    if (index.internalId() == NoGroup) {
        return QString();
    }
    MetaInfoGroup* group = d->mMetaInfoGroupVector[index.internalId()];
    return group->getKeyAt(index.row());
}

QModelIndex ImageMetaInfoModel::index(int row, int col, const QModelIndex& parent) const
{
    if (col < 0 || col > 1) {
        return QModelIndex();
    }
    if (!parent.isValid()) {
        // This is a group
        if (row < 0 || row >= d->mMetaInfoGroupVector.size()) {
            return QModelIndex();
        }
        return createIndex(row, col, col == 0 ? NoGroup : NoGroupSpace);
    } else {
        // This is an entry
        int group = parent.row();
        if (row < 0 || row >= d->mMetaInfoGroupVector[group]->size()) {
            return QModelIndex();
        }
        return createIndex(row, col, group);
    }
}

QModelIndex ImageMetaInfoModel::parent(const QModelIndex& index) const
{
    if (!index.isValid()) {
        return QModelIndex();
    }
    if (index.internalId() == NoGroup || index.internalId() == NoGroupSpace) {
        return QModelIndex();
    } else {
        return createIndex(index.internalId(), 0, NoGroup);
    }
}

int ImageMetaInfoModel::rowCount(const QModelIndex& parent) const
{
    if (!parent.isValid()) {
        return d->mMetaInfoGroupVector.size();
    } else if (parent.internalId() == NoGroup) {
        return d->mMetaInfoGroupVector[parent.row()]->size();
    } else {
        return 0;
    }
}

int ImageMetaInfoModel::columnCount(const QModelIndex& /*parent*/) const
{
    return 2;
}

QVariant ImageMetaInfoModel::data(const QModelIndex& index, int role) const
{
    if (!index.isValid()) {
        return QVariant();
    }

    switch (role) {
    case Qt::DisplayRole:
        return d->displayData(index);
    default:
        return QVariant();
    }
}

QVariant ImageMetaInfoModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Vertical || role != Qt::DisplayRole) {
        return QVariant();
    }

    QString caption;
    if (section == 0) {
        caption = i18nc("@title:column", "Property");
    } else if (section == 1) {
        caption = i18nc("@title:column", "Value");
    } else {
        kWarning() << "Unknown section" << section;
    }

    return QVariant(caption);
}

} // namespace
