/*
    SPDX-FileCopyrightText: 2022 Albert Astals Cid <aacid@kde.org>
    SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#ifndef UTIL_P_H
#define UTIL_P_H

#include <limits>

#include <QImage>
#include <QImageIOHandler>
#include <QIODevice>
#include <QPixelFormat>

// Default maximum width and height for the large image plugins.
#ifndef KIF_LARGE_IMAGE_PIXEL_LIMIT
#define KIF_LARGE_IMAGE_PIXEL_LIMIT 300000
#endif

// Maximum size for legacy image formats.
#define KIF_64K_IMAGE_PIXEL_LIMIT 65536

#if KIF_LARGE_IMAGE_PIXEL_LIMIT < KIF_64K_IMAGE_PIXEL_LIMIT
#undef KIF_LARGE_IMAGE_PIXEL_LIMIT
#define KIF_LARGE_IMAGE_PIXEL_LIMIT KIF_64K_IMAGE_PIXEL_LIMIT
#endif

// Image metadata keys to use in plugins (so they are consistent)
#define META_KEY_ALTITUDE "Altitude"
#define META_KEY_AUTHOR "Author"
#define META_KEY_COMMENT "Comment"
#define META_KEY_COPYRIGHT "Copyright"
#define META_KEY_CREATIONDATE "CreationDate"
#define META_KEY_DESCRIPTION "Description"
#define META_KEY_DIRECTION "Direction"
#define META_KEY_DOCUMENTNAME "DocumentName"
#define META_KEY_HOSTCOMPUTER "HostComputer"
#define META_KEY_LATITUDE "Latitude"
#define META_KEY_LONGITUDE "Longitude"
#define META_KEY_MODIFICATIONDATE "ModificationDate"
#define META_KEY_OWNER "Owner"
#define META_KEY_SOFTWARE "Software"
#define META_KEY_SPEED "Speed"
#define META_KEY_TITLE "Title"
#define META_KEY_XML_GIMP "XML:org.gimp.xml"
#define META_KEY_XMP_ADOBE "XML:com.adobe.xmp"

// Shot info metadata keys
#define META_KEY_DIGITALZOOMRATIO "DigitalZoomRatio"
#define META_KEY_EXPOSUREMODE "ExposureMode"
#define META_KEY_EXPOSUREPROGRAM "ExposureProgram"
#define META_KEY_EXPOSURETIME "ExposureTime"
#define META_KEY_FLASH "Flash"
#define META_KEY_FNUMBER "FNumber"
#define META_KEY_FOCALLENGTH "FocalLength"
#define META_KEY_ISOSPEEDRATINGS "ISOSpeedRatings"
#define META_KEY_WHITEBALANCE "WhiteBalance"

// Camera info metadata keys
#define META_KEY_MANUFACTURER "Manufacturer"
#define META_KEY_MODEL "Model"
#define META_KEY_SERIALNUMBER "SerialNumber"

// Lens info metadata keys
#define META_KEY_LENS_MANUFACTURER "LensManufacturer"
#define META_KEY_LENS_MODEL "LensModel"
#define META_KEY_LENS_SERIALNUMBER "LensSerialNumber"

// QList uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira
static constexpr int kMaxQVectorSize = std::numeric_limits<int>::max() - 32;

/*!
 * \brief The ImageInitToZero enum
 */
enum class ImageInitToZero
{
    None, /**< Do not initialize the image. */
    All, /**< Initialize all images. */
    PremulOnly, /**< Initialize only images with premultiplied alpha. */
    FPOnly, /**< Initialize only images with floating point format. */
    FPAndPremul /**< Initialize floating point and premultiplied formats. */
};

/*!
 * \brief imageAlloc
 * Helper function to initialize framework images.
 * \param size The image size.
 * \param format The image format,
 * \param init Whether and which images should be initialized to zero.
 * \return The allocated image or a null image on error.
 */
inline QImage imageAlloc(const QSize &size, const QImage::Format &format, const ImageInitToZero& init = ImageInitToZero::None)
{
    QImage img;
    if (!QImageIOHandler::allocateImage(size, format, &img)) {
        img = QImage(); // paranoia
    }
    if (init != ImageInitToZero::None && !img.isNull()) {
        auto pixelFormat = img.pixelFormat();
        auto isFloat = pixelFormat.typeInterpretation() == QPixelFormat::FloatingPoint;
        auto isPremul = pixelFormat.premultiplied();
        if (init == ImageInitToZero::All) {
            img.fill(0);
        } else if (isFloat && (init == ImageInitToZero::FPOnly || init == ImageInitToZero::FPAndPremul)) {
            img.fill(0);
        } else if (isPremul && (init == ImageInitToZero::PremulOnly || init == ImageInitToZero::FPAndPremul)) {
            img.fill(0);
        }
    }
    return img;
}

inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &format, const ImageInitToZero& init = ImageInitToZero::None)
{
    return imageAlloc(QSize(width, height), format, init);
}

template<class TI, class SF> // SF = source FP, TI = target INT
TI qRoundOrZero_T(SF d, bool *ok = nullptr)
{
    // checks for undefined behavior
    if (qIsNaN(d) || qIsInf(d) || d < SF() || d > SF(std::numeric_limits<TI>::max())) {
        if (ok) {
            *ok = false;
        }
        return 0;
    }
    if (ok) {
        *ok = true;
    }
    return qRound(d);
}

inline qint32 qRoundOrZero(double d, bool *ok = nullptr)
{
    return qRoundOrZero_T<qint32>(d, ok);
}
inline qint32 qRoundOrZero(float d, bool *ok = nullptr)
{
    return qRoundOrZero_T<qint32>(d, ok);
}

/*!
 * \brief dpi2ppm
 * Converts a value from DPI to PPM.
 * \return \a dpi converted to pixel per meter.
 */
inline qint32 dpi2ppm(double dpi, bool *ok = nullptr)
{
    return qRoundOrZero(dpi / double(25.4) * double(1000), ok);
}
inline qint32 dpi2ppm(float dpi, bool *ok = nullptr)
{
    return qRoundOrZero(dpi / float(25.4) * float(1000), ok);
}
inline qint32 dpi2ppm(quint16 dpi, bool *ok = nullptr)
{
    return qRoundOrZero(dpi / double(25.4) * double(1000), ok);
}

/*!
 * \brief ppm2dpi
 * Converts a value from PPM to DPI.
 * \return \a ppm converted to dot per inch.
 */
template<class TF, class SI> // SI = source INT, TF = target FP
TF ppm2dpi_T(SI ppm, bool *ok = nullptr)
{
    if (ok) {
        *ok = ppm > 0;
    }
    return ppm > 0 ? ppm * TF(25.4) / TF(1000) : TF();
}

inline double dppm2dpi(qint32 ppm, bool *ok = nullptr)
{
    return ppm2dpi_T<double>(ppm, ok);
}
inline float fppm2dpi(qint32 ppm, bool *ok = nullptr)
{
    return ppm2dpi_T<float>(ppm, ok);
}

/*!
 * \brief deviceRead
 * A function for reading from devices.
 *
 * Similar to QIODevice::read(qint64) but limits the initial memory allocation. Useful for reading corrupted streams.
 * \param d The device.
 * \param maxSize The maximum size to read.
 * \return The byte array read.
 */
static QByteArray deviceRead(QIODevice *d, qint64 maxSize)
{
    if (d == nullptr) {
        return{};
    }

    const qint64 blockSize = 32 * 1024 * 1024;
    auto devSize = d->isSequential() ? qint64() : d->size();

    if (devSize > 0) {
        // random access device
        maxSize = std::min(maxSize, devSize - d->pos());
        return d->read(maxSize);
    } else if (maxSize < blockSize) {
        // small read
        return d->read(maxSize);
    }

    // sequential device
    QByteArray ba;
    while (ba.size() < maxSize) {
        auto toRead = std::min(blockSize, maxSize - ba.size());
        if (toRead + ba.size() > QByteArray::maxSize()) {
            break;
        }
        auto tmp = d->read(toRead);
        if (tmp.isEmpty()) {
            break;
        }
        ba.append(tmp);
    }

    return ba;
}

#endif // UTIL_P_H
