// Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2016 Alex Char.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#ifndef QICNSHANDLER_P_H
#define QICNSHANDLER_P_H

#include <QtGui/qimageiohandler.h>
#include <QtCore/qlist.h>

#ifndef QT_NO_DATASTREAM

#define MAKEOSTYPE(c0,c1,c2,c3) (((quint8)c0 << 24) | ((quint8)c1 << 16) | ((quint8)c2 << 8) | (quint8)c3)

QT_BEGIN_NAMESPACE

struct ICNSBlockHeader
{
    enum OS {
        TypeIcns = MAKEOSTYPE('i', 'c', 'n', 's'), // Icns container magic
        TypeToc  = MAKEOSTYPE('T', 'O', 'C', ' '), // Table of contents
        TypeIcnv = MAKEOSTYPE('i', 'c', 'n', 'V'), // Icon Composer version
        // Legacy:
        TypeClut = MAKEOSTYPE('c', 'l', 'u', 't'), // Color look-up table (pre-OS X resources)
        TypeTile = MAKEOSTYPE('t', 'i', 'l', 'e'), // Container (icon variants)
        TypeOver = MAKEOSTYPE('o', 'v', 'e', 'r'), // Container (icon variants)
        TypeOpen = MAKEOSTYPE('o', 'p', 'e', 'n'), // Container (icon variants)
        TypeDrop = MAKEOSTYPE('d', 'r', 'o', 'p'), // Container (icon variants)
        TypeOdrp = MAKEOSTYPE('o', 'd', 'r', 'p'), // Container (icon variants)
    };

    quint32 ostype;
    quint32 length;
};

struct ICNSEntry
{
    enum Group {
        GroupUnknown    = 0,   // Default for invalid ones
        GroupMini       = 'm', // "mini" (16x12)
        GroupSmall      = 's', // "small" (16x16)
        GroupLarge      = 'l', // "large" (32x32)
        GroupHuge       = 'h', // "huge" (48x48)
        GroupThumbnail  = 't', // "thumbnail" (128x128)
        GroupPortable   = 'p', // "portable"? (Speculation, used for png/jp2)
        GroupCompressed = 'c', // "compressed"? (Speculation, used for png/jp2)
        // Legacy icons:
        GroupICON       = 'N', // "ICON" (32x32)
    };
    enum Depth {
        DepthUnknown    = 0,    // Default for invalid or compressed ones
        DepthMono       = 1,
        Depth4bit       = 4,
        Depth8bit       = 8,
        Depth32bit      = 32
    };
    enum Flags {
        Unknown         = 0x0,              // Default for invalid ones
        IsIcon          = 0x1,              // Contains a raw icon without alpha or compressed icon
        IsMask          = 0x2,              // Contains alpha mask
        IconPlusMask    = IsIcon | IsMask   // Contains raw icon and mask combined in one entry (double size)
    };
    enum Format {
        FormatUnknown   = 0,    // Default for invalid or undetermined ones
        RawIcon,                // Raw legacy icon, uncompressed
        RLE24,                  // Raw 32bit icon, data is compressed
        PNG,                    // Compressed icon in PNG format
        JP2                     // Compressed icon in JPEG2000 format
    };

    quint32 ostype;     // Real OSType
    quint32 variant;    // Virtual OSType: a parent container, zero if parent is icns root
    Group group;        // ASCII character number
    quint32 width;      // For uncompressed icons only, zero for compressed ones for now
    quint32 height;     // For uncompressed icons only, zero for compressed ones fow now
    Depth depth;        // Color depth
    Flags flags;        // Flags to determine the type of entry
    Format dataFormat;  // Format of the image data
    quint32 dataLength; // Length of the image data in bytes
    qint64 dataOffset;  // Offset from the initial position of the file/device

    ICNSEntry() :
        ostype(0), variant(0), group(GroupUnknown), width(0), height(0), depth(DepthUnknown),
        flags(Unknown), dataFormat(FormatUnknown), dataLength(0), dataOffset(0)
    {
    }
};
Q_DECLARE_TYPEINFO(ICNSEntry, Q_RELOCATABLE_TYPE);

class QICNSHandler : public QImageIOHandler
{
public:
    QICNSHandler();

    bool canRead() const override;
    bool read(QImage *image) override;
    bool write(const QImage &image) override;

    bool supportsOption(ImageOption option) const override;
    QVariant option(ImageOption option) const override;

    int imageCount() const override;
    bool jumpToImage(int imageNumber) override;
    bool jumpToNextImage() override;

    static bool canRead(QIODevice *device);

private:
    bool ensureScanned() const;
    bool scanDevice();
    bool addEntry(const ICNSBlockHeader &header, qint64 imgDataOffset, quint32 variant = 0);
    const ICNSEntry &getIconMask(const ICNSEntry &icon) const;

private:
    enum ScanState {
        ScanError       = -1,
        ScanNotScanned  = 0,
        ScanSuccess     = 1,
    };

    int m_currentIconIndex;
    QList<ICNSEntry> m_icons;
    QList<ICNSEntry> m_masks;
    ScanState m_state;
};

QT_END_NAMESPACE

#endif // QT_NO_DATASTREAM

#endif /* QICNSHANDLER_P_H */
