/*
    This file is part of the KFileMetaData project
    SPDX-FileCopyrightText: 2014 Vishesh Handa <me@vhanda.in>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/

#include "propertyinfo.h"

#include <KLazyLocalizedString>
#include <KLocalizedString>

#include "formatstrings_p.h"

#include <array>

#include <QLocale>

using namespace KFileMetaData;

class KFileMetaData::PropertyInfoData
{
    using FormatFunction = QString (&)(const QVariant&);

public:
    const Property::Property prop;
    const bool shouldBeIndexed = false;
    const QString name;
    const KLazyLocalizedString displayName;
    const QVariant::Type valueType = QVariant::Invalid;
    const FormatFunction formatAsString = FormatStrings::toStringFunction;
};

const static PropertyInfoData staticEmptyPropertyInfo{ Property::Empty, false, QStringLiteral("empty"), kli18n("Empty"), QVariant::Invalid };

const static std::array<PropertyInfoData, 78> staticPropertyInfo
{
    PropertyInfoData{ Property::Album,                      true,  QStringLiteral("album"),                      kli18nc("@label music album", "Album"),                QVariant::String },
    PropertyInfoData{ Property::AlbumArtist,                true,  QStringLiteral("albumArtist"),                kli18nc("@label", "Album Artist"),                     QVariant::String },
    PropertyInfoData{ Property::Artist,                     true,  QStringLiteral("artist"),                     kli18nc("@label", "Artist"),                           QVariant::String },
    PropertyInfoData{ Property::AspectRatio,                false, QStringLiteral("aspectRatio"),                kli18nc("@label", "Aspect Ratio"),                     QVariant::Double,   FormatStrings::formatAspectRatio },
    PropertyInfoData{ Property::Author,                     true,  QStringLiteral("author"),                     kli18nc("@label", "Author"),                           QVariant::String },
    PropertyInfoData{ Property::BitRate,                    false, QStringLiteral("bitRate"),                    kli18nc("@label", "Bitrate"),                          QVariant::Int,      FormatStrings::formatBitRate },
    PropertyInfoData{ Property::Channels,                   false, QStringLiteral("channels"),                   kli18nc("@label", "Channels"),                         QVariant::Int },
    PropertyInfoData{ Property::Comment,                    false, QStringLiteral("comment"),                    kli18nc("@label", "Comment"),                          QVariant::String },
    PropertyInfoData{ Property::Description,                false, QStringLiteral("description"),                kli18nc("@label", "Description"),                      QVariant::String },
    PropertyInfoData{ Property::Composer,                   true,  QStringLiteral("composer"),                   kli18nc("@label", "Composer"),                         QVariant::String },
    PropertyInfoData{ Property::Copyright,                  false, QStringLiteral("copyright"),                  kli18nc("@label", "Copyright"),                        QVariant::String },
    PropertyInfoData{ Property::CreationDate,               false, QStringLiteral("creationDate"),               kli18nc("@label", "Creation Date"),                    QVariant::DateTime, FormatStrings::formatDate },
    PropertyInfoData{ Property::Duration,                   false, QStringLiteral("duration"),                   kli18nc("@label", "Duration"),                         QVariant::Int,      FormatStrings::formatDuration },
    PropertyInfoData{ Property::FrameRate,                  false, QStringLiteral("frameRate"),                  kli18nc("@label", "Frame Rate"),                       QVariant::Double,   FormatStrings::formatAsFrameRate },
    PropertyInfoData{ Property::Generator,                  false, QStringLiteral("generator"),                  kli18nc("@label", "Document Generated By"),            QVariant::String },
    PropertyInfoData{ Property::Genre,                      true,  QStringLiteral("genre"),                      kli18nc("@label music genre", "Genre"),                QVariant::String },
    PropertyInfoData{ Property::Height,                     false, QStringLiteral("height"),                     kli18nc("@label", "Height"),                           QVariant::Int },
    PropertyInfoData{ Property::ImageDateTime,              false, QStringLiteral("imageDateTime"),              kli18nc("@label EXIF", "Image Date Time"),             QVariant::DateTime, FormatStrings::formatDate },
    PropertyInfoData{ Property::Manufacturer,               false, QStringLiteral("manufacturer"),               kli18nc("@label EXIF", "Manufacturer"),                QVariant::String },
    PropertyInfoData{ Property::Model,                      false, QStringLiteral("model"),                      kli18nc("@label EXIF", "Model"),                       QVariant::String },
    PropertyInfoData{ Property::ImageOrientation,           false, QStringLiteral("imageOrientation"),           kli18nc("@label EXIF", "Image Orientation"),           QVariant::Int,      FormatStrings::formatOrientationValue },
    PropertyInfoData{ Property::Keywords,                   false, QStringLiteral("keywords"),                   kli18nc("@label", "Keywords"),                         QVariant::String },
    PropertyInfoData{ Property::Language,                   false, QStringLiteral("language"),                   kli18nc("@label", "Language"),                         QVariant::String },
    PropertyInfoData{ Property::LineCount,                  false, QStringLiteral("lineCount"),                  kli18nc("@label number of lines", "Line Count"),       QVariant::Int },
    PropertyInfoData{ Property::Lyricist,                   true,  QStringLiteral("lyricist"),                   kli18nc("@label", "Lyricist"),                         QVariant::String },
    PropertyInfoData{ Property::PageCount,                  false, QStringLiteral("pageCount"),                  kli18nc("@label", "Page Count"),                       QVariant::Int },
    PropertyInfoData{ Property::PhotoApertureValue,         false, QStringLiteral("photoApertureValue"),         kli18nc("@label EXIF", "Aperture Value"),              QVariant::Double,   FormatStrings::formatAsFNumber },
    PropertyInfoData{ Property::PhotoDateTimeOriginal,      false, QStringLiteral("photoDateTimeOriginal"),      kli18nc("@label EXIF", "Original Date Time"),          QVariant::DateTime, FormatStrings::formatDate },
    PropertyInfoData{ Property::PhotoExposureBiasValue,     false, QStringLiteral("photoExposureBiasValue"),     kli18nc("@label EXIF", "Exposure Bias"),               QVariant::Double,   FormatStrings::formatPhotoExposureBias },
    PropertyInfoData{ Property::PhotoExposureTime,          false, QStringLiteral("photoExposureTime"),          kli18nc("@label EXIF", "Exposure Time"),               QVariant::Double,   FormatStrings::formatPhotoTime },
    PropertyInfoData{ Property::PhotoFlash,                 false, QStringLiteral("photoFlash"),                 kli18nc("@label EXIF", "Flash"),                       QVariant::Int,      FormatStrings::formatPhotoFlashValue },
    PropertyInfoData{ Property::PhotoFNumber,               false, QStringLiteral("photoFNumber"),               kli18nc("@label EXIF", "F Number"),                    QVariant::Double,   FormatStrings::formatAsFNumber },
    PropertyInfoData{ Property::PhotoFocalLength,           false, QStringLiteral("photoFocalLength"),           kli18nc("@label EXIF", "Focal Length"),                QVariant::Double,   FormatStrings::formatAsMilliMeter },
    PropertyInfoData{ Property::PhotoFocalLengthIn35mmFilm, false, QStringLiteral("photoFocalLengthIn35mmFilm"), kli18nc("@label EXIF", "Focal Length 35mm"),           QVariant::Double,   FormatStrings::formatAsMilliMeter },
    PropertyInfoData{ Property::PhotoGpsLatitude,           false, QStringLiteral("photoGpsLatitude"),           kli18nc("@label EXIF", "GPS Latitude"),                QVariant::Double,   FormatStrings::formatAsDegree },
    PropertyInfoData{ Property::PhotoGpsLongitude,          false, QStringLiteral("photoGpsLongitude"),          kli18nc("@label EXIF", "GPS Longitude"),               QVariant::Double,   FormatStrings::formatAsDegree },
    PropertyInfoData{ Property::PhotoGpsAltitude,           false, QStringLiteral("photoGpsAltitude"),           kli18nc("@label EXIF", "GPS Altitude"),                QVariant::Double,   FormatStrings::formatAsMeter },
    PropertyInfoData{ Property::PhotoISOSpeedRatings,       false, QStringLiteral("photoISOSpeedRatings"),       kli18nc("@label EXIF", "ISO Speed Rating"),            QVariant::Int },
    PropertyInfoData{ Property::PhotoMeteringMode,          false, QStringLiteral("photoMeteringMode"),          kli18nc("@label EXIF", "Metering Mode"),               QVariant::Int },
    PropertyInfoData{ Property::PhotoPixelXDimension,       false, QStringLiteral("photoPixelXDimension"),       kli18nc("@label EXIF", "X Dimension"),                 QVariant::Int },
    PropertyInfoData{ Property::PhotoPixelYDimension,       false, QStringLiteral("photoPixelYDimension"),       kli18nc("@label EXIF", "Y Dimension"),                 QVariant::Int },
    PropertyInfoData{ Property::PhotoSaturation,            false, QStringLiteral("photoSaturation"),            kli18nc("@label EXIF", "Saturation"),                  QVariant::Int },
    PropertyInfoData{ Property::PhotoSharpness,             false, QStringLiteral("photoSharpness"),             kli18nc("@label EXIF", "Sharpness"),                   QVariant::Int },
    PropertyInfoData{ Property::PhotoWhiteBalance,          false, QStringLiteral("photoWhiteBalance"),          kli18nc("@label EXIF", "White Balance"),               QVariant::Int },
    PropertyInfoData{ Property::Publisher,                  true,  QStringLiteral("publisher"),                  kli18nc("@label", "Publisher"),                        QVariant::String },
    PropertyInfoData{ Property::Label,                      true,  QStringLiteral("label"),                      kli18nc("@label", "Label"),                            QVariant::String },
    PropertyInfoData{ Property::ReleaseYear,                false, QStringLiteral("releaseYear"),                kli18nc("@label", "Release Year"),                     QVariant::Int },
    PropertyInfoData{ Property::SampleRate,                 false, QStringLiteral("sampleRate"),                 kli18nc("@label", "Sample Rate"),                      QVariant::Int,      FormatStrings::formatSampleRate },
    PropertyInfoData{ Property::Subject,                    false, QStringLiteral("subject"),                    kli18nc("@label", "Subject"),                          QVariant::String },
    PropertyInfoData{ Property::Title,                      true,  QStringLiteral("title"),                      kli18nc("@label", "Title"),                            QVariant::String },
    PropertyInfoData{ Property::TrackNumber,                false, QStringLiteral("trackNumber"),                kli18nc("@label music track number", "Track Number"),  QVariant::Int },
    PropertyInfoData{ Property::DiscNumber,                 false, QStringLiteral("discNumber"),                 kli18nc("@label music disc number", "Disc Number"),    QVariant::Int },
    PropertyInfoData{ Property::Location,                   true,  QStringLiteral("location"),                   kli18nc("@label", "Location"),                         QVariant::String },
    PropertyInfoData{ Property::Performer,                  true,  QStringLiteral("performer"),                  kli18nc("@label", "Performer"),                        QVariant::String },
    PropertyInfoData{ Property::Ensemble,                   true,  QStringLiteral("ensemble"),                   kli18nc("@label", "Ensemble"),                         QVariant::String },
    PropertyInfoData{ Property::Arranger,                   true,  QStringLiteral("arranger"),                   kli18nc("@label", "Arranger"),                         QVariant::String },
    PropertyInfoData{ Property::Conductor,                  true,  QStringLiteral("conductor"),                  kli18nc("@label", "Conductor"),                        QVariant::String },
    PropertyInfoData{ Property::Compilation,                true,  QStringLiteral("compilation"),                kli18nc("@label", "Compilation"),                      QVariant::String },
    PropertyInfoData{ Property::License,                    true,  QStringLiteral("license"),                    kli18nc("@label", "License"),                          QVariant::String },
    PropertyInfoData{ Property::Lyrics,                     true,  QStringLiteral("lyrics"),                     kli18nc("@label", "Lyrics"),                           QVariant::String },
    PropertyInfoData{ Property::Opus,                       false, QStringLiteral("opus"),                       kli18nc("@label", "Opus"),                             QVariant::Int },
    PropertyInfoData{ Property::Rating,                     false, QStringLiteral("embeddedRating"),             kli18nc("@label", "Rating"),                           QVariant::Int },
    PropertyInfoData{ Property::ReplayGainAlbumPeak,        false, QStringLiteral("replayGainAlbumPeak"),        kli18nc("@label ReplayGain is the name of a standard", "ReplayGain Album Peak"),           QVariant::Double, FormatStrings::formatDouble },
    PropertyInfoData{ Property::ReplayGainAlbumGain,        false, QStringLiteral("replayGainAlbumGain"),        kli18nc("@label ReplayGain is the name of a standard", "ReplayGain Album Gain"),           QVariant::Double, FormatStrings::formatDouble },
    PropertyInfoData{ Property::ReplayGainTrackPeak,        false, QStringLiteral("replayGainTrackPeak"),        kli18nc("@label ReplayGain is the name of a standard", "ReplayGain Track Peak"),           QVariant::Double, FormatStrings::formatDouble },
    PropertyInfoData{ Property::ReplayGainTrackGain,        false, QStringLiteral("replayGainTrackGain"),        kli18nc("@label ReplayGain is the name of a standard", "ReplayGain Track Gain"),           QVariant::Double, FormatStrings::formatDouble },
    PropertyInfoData{ Property::Width,                      false, QStringLiteral("width"),                      kli18nc("@label", "Width"),                            QVariant::Int },
    PropertyInfoData{ Property::WordCount,                  false, QStringLiteral("wordCount"),                  kli18nc("@label number of words", "Word Count"),       QVariant::Int },
    PropertyInfoData{ Property::TranslationUnitsTotal,                false, QStringLiteral("translationUnitsTotal"),                kli18nc("@label number of translatable strings", "Translatable Units"),      QVariant::Int },
    PropertyInfoData{ Property::TranslationUnitsWithTranslation,      false, QStringLiteral("translationUnitsWithTranslation"),      kli18nc("@label number of translated strings", "Translations"),              QVariant::Int },
    PropertyInfoData{ Property::TranslationUnitsWithDraftTranslation, false, QStringLiteral("translationUnitsWithDraftTranslation"), kli18nc("@label number of fuzzy translated strings", "Draft Translations"),  QVariant::Int },
    PropertyInfoData{ Property::TranslationLastAuthor,                false, QStringLiteral("translationLastAuthor"),                kli18nc("@label translation author", "Author"),                              QVariant::String },
    PropertyInfoData{ Property::TranslationLastUpDate,                false, QStringLiteral("translationLastUpDate"),                kli18nc("@label translations last update", "Last Update"),                   QVariant::String, FormatStrings::formatDate },
    PropertyInfoData{ Property::TranslationTemplateDate,              false, QStringLiteral("translationTemplateDate"),              kli18nc("@label date of template creation8", "Template Creation"),           QVariant::String, FormatStrings::formatDate },
    PropertyInfoData{ Property::OriginUrl,                  false, QStringLiteral("originUrl"),                  kli18nc("@label the URL a file was originally downloaded from", "Downloaded From"),                     QVariant::Url },
    PropertyInfoData{ Property::OriginEmailSubject,         false, QStringLiteral("originEmailSubject"),         kli18nc("@label the subject of an email this file was attached to", "E-Mail Attachment Subject"),       QVariant::String },
    PropertyInfoData{ Property::OriginEmailSender,          false, QStringLiteral("originEmailSender"),          kli18nc("@label the sender of an email this file was attached to", "E-Mail Attachment Sender"),         QVariant::String },
    PropertyInfoData{ Property::OriginEmailMessageId,       false, QStringLiteral("originEmailMessageId"),       kli18nc("@label the message ID of an email this file was attached to", "E-Mail Attachment Message ID"), QVariant::String }
};

namespace {
     auto constexpr propertyDataById(Property::Property property) {
         for (const auto& p : staticPropertyInfo) {
             if (p.prop == property)
                 return &p;
         }
         return &staticEmptyPropertyInfo;
     }
}

PropertyInfo::PropertyInfo(Property::Property property) : d(propertyDataById(property)) {};

PropertyInfo::PropertyInfo() : d(&staticEmptyPropertyInfo) {};

PropertyInfo::PropertyInfo(const PropertyInfo& pi)
    : d(pi.d)
{
}

PropertyInfo::~PropertyInfo() = default;

PropertyInfo& PropertyInfo::operator=(const PropertyInfo& rhs)
{
    d = rhs.d;
    return *this;
}

bool PropertyInfo::operator==(const PropertyInfo& rhs) const
{
    return d == rhs.d;
}

QString PropertyInfo::displayName() const
{
    return d->displayName.toString();
}

QString PropertyInfo::name() const
{
    return d->name;
}

Property::Property PropertyInfo::property() const
{
    return d->prop;
}

QVariant::Type PropertyInfo::valueType() const
{
    return d->valueType;
}

bool PropertyInfo::shouldBeIndexed() const
{
    return d->shouldBeIndexed;
}

QString PropertyInfo::formatAsDisplayString(const QVariant &value) const
{
    if (value.type() == QVariant::List || value.type() == QVariant::StringList) {
        if (d->valueType == QVariant::String) {
            return QLocale().createSeparatedList(value.toStringList());
        } else {
            QStringList displayList;
            const auto valueList = value.toList();
            for (const auto& entry : valueList) {
                displayList << d->formatAsString(entry);
            }
            return QLocale().createSeparatedList(displayList);
        }
    } else {
        return d->formatAsString(value);
    }
}

namespace {
    class LcPropertyName
    {
    public:
        LcPropertyName(const QString& n) : name(n) {};
        QString name;
    };

    constexpr QChar trivialToLower(const QChar &c) {
        if (c.isUpper()) {
            return QChar::fromLatin1(c.toLatin1() ^ ('a' ^ 'A'));
        }
        return c;
    }

    inline bool operator==(const LcPropertyName &a, const LcPropertyName &b)
    {
        if (a.name.size() != b.name.size()) {
            return false;
        }
        for (int i = 0; i < a.name.size(); i++) {
            if ((a.name[i] != b.name[i]) && (trivialToLower(a.name[i]) != trivialToLower(b.name[i]))) {
                return false;
            }
        }
        return true;
    }

    inline size_t qHash(const LcPropertyName &key, size_t seed = 0)
    {
        size_t val = seed;
        for (const auto& c : key.name) {
            val ^= qHash(trivialToLower(c));
        }
        return val;
    }

    static const QHash<LcPropertyName, PropertyInfo> propertyHash = []() {
        QHash<LcPropertyName, PropertyInfo> infoHash;
        infoHash.reserve(staticPropertyInfo.size());

        for (const auto& info: staticPropertyInfo) {
            infoHash[info.name] = info.prop;
        }
        return infoHash;
    }();
}

PropertyInfo PropertyInfo::fromName(const QString& name)
{
    return propertyHash.value(LcPropertyName(name));
}

QStringList PropertyInfo::allNames()
{
    static QStringList sNames = []() {
        QStringList names;
        names.reserve(staticPropertyInfo.size());
        for (auto info: staticPropertyInfo) {
            names.append(info.name);
        }
        return names;
    }();
    return sNames;
}
