/*
    This file is part of the KDE project
    SPDX-FileCopyrightText: 2025 Mirco Miranda <mircomir@outlook.com>

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

#ifndef MICROEXIF_P_H
#define MICROEXIF_P_H

#include <QByteArray>
#include <QColorSpace>
#include <QDataStream>
#include <QDateTime>
#include <QImage>
#include <QImageIOHandler>
#include <QMap>
#include <QUuid>
#include <QVariant>

#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
#define EXIF_DEFAULT_BYTEORDER QDataStream::LittleEndian
#else
#define EXIF_DEFAULT_BYTEORDER QDataStream::BigEndian
#endif

/*!
     * \brief The Flash enum
     */
enum class Flash : quint16 {
    NotSet = 0,

    // Values for bit 0 indicating whether the flash fired.
    // 0b = Flash did not fire.
    // 1b = Flash fired.
    Fired = 1,

    // Values for bits 1 and 2 indicating the status of returned light.
    // 00b = No strobe return detection function
    // 01b = reserved
    // 10b = Strobe return light not detected.
    // 11b = Strobe return light detected.
    ReturnLightNotDetected = 2 << 1,
    ReturnLightDetected = 3 << 1,

    // Values for bits 3 and 4 indicating the camera's flash mode.
    // 00b = unknown
    // 01b = Compulsory flash firing
    // 10b = Compulsory flash suppression
    // 11b = Auto mode
    CompulsoryFiring = 1 << 3,
    CompulsorySuppression = 2 << 3,
    AutoMode = 3 << 3,

    // Values for bit 5 indicating the presence of a flash function.
    // 0b = Flash function present
    // 1b = No flash function
    FlashNotAvailable = 1 << 5,

    // Values for bit 6 indicating the camera's red-eye mode.
    // 0b = No red-eye reduction mode or unknown
    // 1b = Red-eye reduction supported
    RedEyeReductionSupported = 1 << 6,
};
Q_DECLARE_FLAGS(FlashFlags, Flash)
Q_DECLARE_OPERATORS_FOR_FLAGS(FlashFlags)

/*!
     * \brief The ExposureMode enum
     */
enum class ExposureMode : quint16 {
    Auto,
    Manual,
    AutoBracket,

    NotSet = 65535
};

/*!
     * \brief The ExposureProgram enum
     */
enum class ExposureProgram : quint16 {
    NotDefined,
    Manual,
    Normal,
    AperturePriority,
    ShutterPriority,
    Creative,
    Action,
    PortraitMode,
    LandscapeMode,

    NotSet = 65535
};

/*!
     * \brief The WhiteBalance enum
     */
enum class WhiteBalance : quint16 {
    Auto,
    Manual,

    NotSet = 65535
};

/*!
 * \brief The MicroExif class
 * Class to extract / write minimal EXIF data (e.g. resolution, rotation,
 * some strings).
 *
 * This class is a partial (or rather minimal) implementation and is only used
 * to avoid including external libraries when only a few tags are needed.
 */
class MicroExif
{
public:
    using Tags = QMap<quint16, QVariant>;

    /*!
     * \brief The Version enum
     * Exif specs version used when writing.
     */
    enum Version {
        V2, // V2.xx
        V3 // V3.xx, use of UTF-8 data type (default)
    };

    /*!
     * \brief MicroExif
     * Constructs an empty class.
     * \sa isEmpty
     */
    MicroExif();

    MicroExif(const MicroExif &other) = default;
    MicroExif &operator=(const MicroExif &other) = default;

    /*!
     * \brief clear
     * Removes all items.
     */
    void clear();

    /*!
     * \brief isEmpty
     * \return True if it contains no items, otherwise false.
     */
    bool isEmpty() const;

    /*!
     * \brief horizontalResolution
     * \return The horizontal resolution in DPI.
     */
    double horizontalResolution() const;
    void setHorizontalResolution(double hres);

    /*!
     * \brief verticalResolution
     * \return The vertical resolution in DPI.
     */
    double verticalResolution() const;
    void setVerticalResolution(double vres);

    /*!
     * \brief colosSpace
     * \return sRGB color space or an invalid one.
     */
    QColorSpace colosSpace() const;
    void setColorSpace(const QColorSpace& cs);
    void setColorSpace(const QColorSpace::NamedColorSpace& csName);

    /*!
     * \brief width
     * \return The image width.
     */
    qint32 width() const;
    void setWidth(qint32 w);

    /*!
     * \brief height
     * \return The image height.
     */
    qint32 height() const;
    void setHeight(qint32 h);

    /*!
     * \brief orientation
     * The orientation of the image with respect to the rows and columns.
     *
     * Valid orientation values:
     * - 1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
     * - 2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
     * - 3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
     * - 4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
     * - 5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
     * - 6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
     * - 7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
     * - 8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
     * \return The orientation value or 0 if none.
     * \sa transformation
     */
    quint16 orientation() const;
    void setOrientation(quint16 orient);

    /*!
     * \brief transformation
     * \return The orientation converted in the equivalent Qt transformation.
     * \sa orientation
     */
    QImageIOHandler::Transformation transformation() const;
    void setTransformation(const QImageIOHandler::Transformation& t);

    /*!
     * \brief transformationToOrientation
     * \param t The Qt transformation.
     * \return The EXIF orientation value or 0 if none.
     */
    static quint16 transformationToOrientation(const QImageIOHandler::Transformation& t);
    /*!
     * \brief orientationToTransformation
     * \param o The EXIF orientation.
     * \return The orientation converted in the equivalent Qt transformation.
     */
    static QImageIOHandler::Transformation orientationToTransformation(quint16 o);

    /*!
     * \brief software
     * \return Name and version number of the software package(s) used to create the image.
     */
    QString software() const;
    void setSoftware(const QString& s);

    /*!
     * \brief description
     * \return A string that describes the subject of the image.
     */
    QString description() const;
    void setDescription(const QString& s);

    /*!
     * \brief artist
     * \return Person who created the image.
     */
    QString artist() const;
    void setArtist(const QString& s);

    /*!
     * \brief copyright
     * \return Copyright notice of the person or organization that claims the copyright to the image.
     */
    QString copyright() const;
    void setCopyright(const QString& s);

    /*!
     * \brief make
     * \return The manufacturer of the recording equipment.
     */
    QString make() const;
    void setMake(const QString& s);

    /*!
     * \brief model
     * \return The model name or model number of the equipment.
     */
    QString model() const;
    void setModel(const QString& s);

    /*!
     * \brief serialNumber
     * \return The serial number of the recording equipment.
     */
    QString serialNumber() const;
    void setSerialNumber(const QString &s);

    /*!
     * \brief lensMake
     * \return The manufacturer of the interchangeable lens that was used.
     */
    QString lensMake() const;
    void setLensMake(const QString &s);

    /*!
     * \brief lensModel
     * \return The model name or model number of the lens that was used.
     */
    QString lensModel() const;
    void setLensModel(const QString &s);

    /*!
     * \brief lensSerialNumber
     * \return The serial number of the interchangeable lens that was used.
     */
    QString lensSerialNumber() const;
    void setLensSerialNumber(const QString &s);

    /*!
     * \brief dateTime
     * \return Creation date and time.
     */
    QDateTime dateTime() const;
    void setDateTime(const QDateTime& dt);

    /*!
     * \brief dateTimeOriginal
     * \return The date and time when the original image data was generated.
     */
    QDateTime dateTimeOriginal() const;
    void setDateTimeOriginal(const QDateTime& dt);

    /*!
     * \brief dateTimeDigitized
     * \return The date and time when the image was stored as digital data.
     */
    QDateTime dateTimeDigitized() const;
    void setDateTimeDigitized(const QDateTime& dt);

    /*!
     * \brief title
     * \return The title of the image.
     */
    QString title() const;
    void setImageTitle(const QString &s);

    /*!
     * \brief uniqueId
     * \return An identifier assigned uniquely to each image or null one if none.
     */
    QUuid uniqueId() const;
    void setUniqueId(const QUuid &uuid);

    /*!
     * \brief digitalZoomRatio
     * \return The digital zoom ratio when the image was shot or NaN if not set.
     */
    double digitalZoomRatio() const;
    void setDigitalZoomRatio(double zoom);

    /*!
     * \brief exposureMode
     * \return The exposure mode set when the image was shot. In auto-bracketing mode, the camera shoots a series of frames of the same scene at different exposure settings.
     */
    ExposureMode exposureMode() const;
    void setExposureMode(const ExposureMode& em);

    /*!
     * \brief exposureProgram
     * \return The class of the program used by the camera to set exposure when the picture is taken.
     */
    ExposureProgram exposureProgram() const;
    void setExposureProgram(const ExposureProgram& ep);

    /*!
     * \brief exposureTime
     * \return Exposure time, given in seconds (sec) or NaN if not set.
     */
    double exposureTime() const;
    void setExposureTime(double et);

    /*!
     * \brief fNumber
     * \return The F number or NaN if not set.
     */
    double fNumber() const;
    void setFNumber(double f);

    /*!
     * \brief focalLength
     * \return The actual focal length of the lens, in mm.
     */
    double focalLength() const;
    void setFocalLength(double fl);

    /*!
     * \brief flash
     * \return The status of flash when the image was shot.
     */
    FlashFlags flash() const;
    void setFlash(const FlashFlags& flash);

    /*!
     * \brief isoSpeedRatings
     * \return The sensitivity of the camera or input device when the image was shot.
     */
    quint16 isoSpeedRatings() const;
    void setIsoSpeedRatings(quint16 iso);

    /*!
     * \brief whiteBalance
     * \return The white balance mode set when the image was shot.
     */
    WhiteBalance whiteBalance() const;
    void setWhiteBalance(const WhiteBalance& wb);

    /*!
     * \brief latitude
     * \return Floating-point number indicating the latitude in degrees north of the equator (e.g. 27.717) or NaN if not set.
     */
    double latitude() const;
    void setLatitude(double degree);

    /*!
     * \brief longitude
     * \return Floating-point number indicating the longitude in degrees east of Greenwich (e.g. 85.317) or NaN if not set.
     */
    double longitude() const;
    void setLongitude(double degree);

    /*!
     * \brief altitude
     * \return Floating-point number indicating the GPS altitude in meters above sea level or ellipsoidal surface (e.g. 35.4) or NaN if not set.
     * \note It makes no distinction between an 'ellipsoidal surface' and 'sea level'.
     */
    double altitude() const;
    void setAltitude(double meters);

    /*!
     * \brief imageSpeed
     * \return The speed in Km/h or NaN if not set.
     */
    double imageSpeed() const;
    void setImageSpeed(double kmh);

    /*!
     * \brief imageDirection
     * \param isMagnetic Set to true if the direction is relative to magnetic north, false if it is relative to true north. Leave nullptr if is not of interest.
     * \return Floating-point number indicating the direction of the image when it was captured. The range of values is from 0.00 to 359.99 or NaN if not set.
     */
    double imageDirection(bool *isMagnetic = nullptr) const;
    void setImageDirection(double degree, bool isMagnetic = false);

    /*!
     * \brief toByteArray
     * Converts the class to RAW data. The raw data contains:
     * - TIFF header
     * - MAIN IFD
     * - EXIF IFD
     * - GPS IFD
     * \param byteOrder Sets the serialization byte order for EXIF data.
     * \param version The EXIF specs version to use.
     * \return A byte array containing the serialized data.
     * \sa write
     */
    QByteArray toByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;

    /*!
     * \brief exifIfdByteArray
     * Convert the EXIF IFD only to RAW data. Useful when you want to add EXIF data to an existing TIFF container.
     * \param byteOrder Sets the serialization byte order for the data.
     * \param version The EXIF specs version to use.
     * \return A byte array containing the serialized data.
     */
    QByteArray exifIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;
    /*!
     * \brief setExifIfdByteArray
     * \param ba The RAW data of EXIF IFD.
     * \param byteOrder Sets the serialization byte order of the data.
     * \return True on success, otherwise false.
     */
    bool setExifIfdByteArray(const QByteArray& ba, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER);

    /*!
     * \brief gpsIfdByteArray
     * Convert the GPS IFD only to RAW data. Useful when you want to add GPS data to an existing TIFF container.
     * \param byteOrder Sets the serialization byte order for the data.
     * \param version The EXIF specs version to use.
     * \return A byte array containing the serialized data.
     */
    QByteArray gpsIfdByteArray(const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;
    /*!
     * \brief setGpsIfdByteArray
     * \param ba The RAW data of GPS IFD.
     * \param byteOrder Sets the serialization byte order of the data.
     * \return True on success, otherwise false.
     */
    bool setGpsIfdByteArray(const QByteArray& ba, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER);

    /*!
     * \brief write
     * Serialize the class on a device. The serialized data contains:
     * - TIFF header
     * - MAIN IFD
     * - EXIF IFD
     * - GPS IFD
     * \param device A random access device.
     * \param byteOrder Sets the serialization byte order for EXIF data.
     * \param version The EXIF specs version to use.
     * \return True on success, otherwise false.
     * \sa toByteArray
     */
    bool write(QIODevice *device, const QDataStream::ByteOrder &byteOrder = EXIF_DEFAULT_BYTEORDER, const Version &version = Version::V3) const;

    /*!
     * \brief updateImageMetadata
     * Helper to set EXIF metadata to the image.
     * \param targetImage The image to set metadata on.
     * \param replaceExisting Replaces any existing metadata.
     */
    void updateImageMetadata(QImage &targetImage, bool replaceExisting = false) const;

    /*!
     * \brief updateImageResolution
     * Helper to set the EXIF resolution to the image. Resolution is set only if valid.
     * \param targetImage The image to set resolution on.
     * \return True if either the x-resolution or the y-resolution has been changed, otherwise false.
     */
    bool updateImageResolution(QImage &targetImage);

    /*!
     * \brief fromByteArray
     * Creates the class from RAW EXIF data.
     * \param ba Raw data containing EXIF ​​data.
     * \param searchHeader If true, the EXIF ​​header is searched within the data. If false, the data must begin with the EXIF ​​header.
     * \return The created class (empty on error).
     * \sa isEmpty
     */
    static MicroExif fromByteArray(const QByteArray &ba, bool searchHeader = false);

    /*!
     * \brief fromRawData
     * Creates the class from RAW EXIF data.
     * \param data Raw data containing EXIF ​​data.
     * \param size The size of \a data.
     * \param searchHeader If true, the EXIF ​​header is searched within the data. If false, the data must begin with the EXIF ​​header.
     * \return The created class (empty on error).
     * \sa isEmpty, fromByteArray
     */
    static MicroExif fromRawData(const char *data, size_t size, bool searchHeader = false);

    /*!
     * \brief fromDevice
     * Creates the class from a device.
     * \param device A random access device.
     * \return The created class (empty on error).
     * \sa isEmpty
     */
    static MicroExif fromDevice(QIODevice *device);

    /*!
     * \brief fromImage
     * Creates the class and fill it with image info (e.g. resolution).
     */
    static MicroExif fromImage(const QImage &image);

private:
    void setTiffString(quint16 tagId, const QString &s);
    QString tiffString(quint16 tagId) const;
    void setExifString(quint16 tagId, const QString& s);
    QString exifString(quint16 tagId) const;
    void setGpsString(quint16 tagId, const QString& s);
    QString gpsString(quint16 tagId) const;
    bool writeHeader(QDataStream &ds) const;
    bool writeIfds(QDataStream &ds, const Version &version) const;
    void updateTags(Tags &tiffTags, Tags &exifTags, Tags &gpsTags, const Version &version) const;

    static void setString(Tags &tags, quint16 tagId, const QString &s);
    static QString string(const Tags &tags, quint16 tagId);

private:
    Tags m_tiffTags;
    Tags m_exifTags;
    Tags m_gpsTags;
};

#endif // MICROEXIF_P_H
