// SPDX-FileCopyrightText: 2022 Francesco Pretto <ceztko@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later OR MPL-2.0

#ifndef PDF_COLOR_SPACE_FILTER_H
#define PDF_COLOR_SPACE_FILTER_H

#include "PdfColor.h"

namespace PoDoFo
{
    class PdfIndirectObjectList;
    class PdfColorSpace;

    /// Output pixel format for a PdfColorSpaceFilter
    enum class PdfColorSpacePixelFormat : uint8_t
    {
        Unknown = 0,
        Grayscale,
        RGB,
        CMYK,
        // TODO:
        // Custom    ///< Used for /DeviceN colorspaces
    };

    /// A class that implements methods to sample colors from a scanline buffer
    class PODOFO_API PdfColorSpaceFilter
    {
        friend class PdfColorSpace;
        friend class PdfPainter;
        friend class PdfColorSpaceInitializer;
        friend class PdfColorSpaceFilterUnkown;
        friend class PdfColorSpaceDeviceGray;
        friend class PdfColorSpaceFilterDeviceRGB;
        friend class PdfColorSpaceFilterDeviceCMYK;
        friend class PdfColorSpaceFilterIndexed;
        friend class PdfColorSpaceFilterSeparation;
        friend class PdfColorSpaceFilterLab;
        friend class PdfColorSpaceFilterICCBased;
        friend class PdfColorSpaceFilterPattern;

    private:
        PdfColorSpaceFilter();

    public:
        virtual ~PdfColorSpaceFilter();
        virtual PdfColorSpaceType GetType() const = 0;
        /// True if the code space doesn't perform any non-trivial
        /// encoding/filtering. In other words pixels can be sampled
        /// by just copying scan lines 
        virtual bool IsRawEncoded() const;
        /// True if the color space is fully identified by its name
        virtual bool IsTrivial() const;
        /// Get the output pixel format of this color space
        virtual PdfColorSpacePixelFormat GetPixelFormat() const = 0;
        /// Get the size of the scan line to sample from
        virtual unsigned GetSourceScanLineSize(unsigned width, unsigned bitsPerComponent) const = 0;
        /// Get the size of the scan line to sample to
        virtual unsigned GetScanLineSize(unsigned width, unsigned bitsPerComponent) const = 0;
        /// Fetch the actual scanline of the exported format from/to the given buffers
        virtual void FetchScanLine(unsigned char* dstScanLine, const unsigned char* srcScanLine,
            unsigned width, unsigned bitsPerComponent) const = 0;
        /// Get the number of the color components for this color space
        virtual unsigned char GetColorComponentCount() const = 0;

    protected:
        /// Get an export object
        virtual PdfVariant GetExportObject(PdfIndirectObjectList& objectsj) const = 0;
    };

    /// Convenience alias for a constant PdfColorSpaceFilter shared ptr
    using PdfColorSpaceFilterPtr = std::shared_ptr<const PdfColorSpaceFilter>;

    /// A proxy class that can used to identify a color space choosing
    /// from several input types
    class PODOFO_API PdfColorSpaceInitializer final
    {
        friend class PdfImage;
        friend class PdfGraphicsStateWrapper;
        friend class PdfShadingDefinition;
        friend class PdfColorSpaceFilterICCBased;
        friend class PdfColorSpaceFilterIndexed;
        friend class PdfColorSpaceFilterPattern;
        friend class PdfColorSpaceFilterFactory;

    public:
        /// A null color space
        PdfColorSpaceInitializer();
        /// Identify a color space from color space document element
        PdfColorSpaceInitializer(const PdfColorSpace& colorSpace);
        /// Identify a trivial colorspace from its enum type (DeviceGray, DeviceRGB or DeviceCYMC)
        PdfColorSpaceInitializer(PdfColorSpaceType colorSpace);

        PdfColorSpaceInitializer(const PdfColorSpaceInitializer&) = default;

    private:
        /// Identify a color space from a filter
        /// To re-use the object, create a PdfColorSpace element from a
        /// document with PdfDocument::CreateColorSpace(filter)
        PdfColorSpaceInitializer(PdfColorSpaceFilterPtr&& filter);

    public:
        bool IsNull() const;

        /// Get the filter for this initializer
        /// @remarks If null, it returns the Unknown filter instance
        const PdfColorSpaceFilter& GetFilter() const;

        PdfColorSpaceInitializer& operator=(const PdfColorSpaceInitializer&) = default;

    private:
        const PdfColorSpaceFilterPtr& GetFilterPtr() const { return m_Filter; }
        PdfColorSpaceFilterPtr Take(PdfVariant& expObj);
        PdfVariant GetExportObject(PdfIndirectObjectList& objects) const;

    private:
        PdfColorSpaceFilterPtr m_Filter;
        PdfVariant m_ExpVar;
    };

    /// Unknown color space filter that default throws on implementations
    class PODOFO_API PdfColorSpaceFilterUnkown final : public PdfColorSpaceFilter
    {
        friend class PdfColorSpaceFilterFactory;
    private:
        PdfColorSpaceFilterUnkown();
    public:
        PdfColorSpaceType GetType() const override;
        PdfColorSpacePixelFormat GetPixelFormat() const override;
        unsigned GetSourceScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        unsigned GetScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        void FetchScanLine(unsigned char* dstScanLine, const unsigned char* srcScanLine,
            unsigned width, unsigned bitsPerComponent) const override;
        unsigned char GetColorComponentCount() const override;
    protected:
        PdfVariant GetExportObject(PdfIndirectObjectList& objects) const override;
    };

    class PODOFO_API PdfColorSpaceDeviceGray final : public PdfColorSpaceFilter
    {
        friend class PdfColorSpaceFilterFactory;
    private:
        PdfColorSpaceDeviceGray();
    public:
        PdfColorSpaceType GetType() const override;
        bool IsRawEncoded() const override;
        bool IsTrivial() const override;
        PdfColorSpacePixelFormat GetPixelFormat() const override;
        unsigned GetSourceScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        unsigned GetScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        void FetchScanLine(unsigned char* dstScanLine, const unsigned char* srcScanLine,
            unsigned width, unsigned bitsPerComponent) const override;
        unsigned char GetColorComponentCount() const override;
    protected:
        PdfVariant GetExportObject(PdfIndirectObjectList& objects) const override;
    };

    class PODOFO_API PdfColorSpaceFilterDeviceRGB final : public PdfColorSpaceFilter
    {
        friend class PdfColorSpaceFilterFactory;
    private:
        PdfColorSpaceFilterDeviceRGB();
    public:
        PdfColorSpaceType GetType() const override;
        bool IsRawEncoded() const override;
        bool IsTrivial() const override;
        PdfColorSpacePixelFormat GetPixelFormat() const override;
        unsigned GetSourceScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        unsigned GetScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        void FetchScanLine(unsigned char* dstScanLine, const unsigned char* srcScanLine,
            unsigned width, unsigned bitsPerComponent) const override;
        unsigned char GetColorComponentCount() const override;
    protected:
        PdfVariant GetExportObject(PdfIndirectObjectList& objects) const override;
    };

    class PODOFO_API PdfColorSpaceFilterDeviceCMYK final : public PdfColorSpaceFilter
    {
        friend class PdfColorSpaceFilterFactory;
    private:
        PdfColorSpaceFilterDeviceCMYK();
    public:
        PdfColorSpaceType GetType() const override;
        bool IsRawEncoded() const override;
        bool IsTrivial() const override;
        PdfColorSpacePixelFormat GetPixelFormat() const override;
        unsigned GetSourceScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        unsigned GetScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        void FetchScanLine(unsigned char* dstScanLine, const unsigned char* srcScanLine,
            unsigned width, unsigned bitsPerComponent) const override;
        unsigned char GetColorComponentCount() const override;
    protected:
        PdfVariant GetExportObject(PdfIndirectObjectList& objects) const override;
    };

    /// Color space as described by ISO 32000-2:2020 "8.6.6.3 Indexed colour spaces"
    class PODOFO_API PdfColorSpaceFilterIndexed final : public PdfColorSpaceFilter
    {
    public:
        PdfColorSpaceFilterIndexed(PdfColorSpaceInitializer&& baseColorSpace, unsigned mapSize, charbuff lookup);

        /// @remarks Deserialization constructor
        PdfColorSpaceFilterIndexed(PdfColorSpaceFilterPtr&& baseColorSpace, unsigned mapSize, charbuff&& lookup);
    public:
        PdfColorSpaceType GetType() const override;
        PdfColorSpacePixelFormat GetPixelFormat() const override;
        unsigned GetSourceScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        unsigned GetScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        void FetchScanLine(unsigned char* dstScanLine, const unsigned char* srcScanLine,
            unsigned width, unsigned bitsPerComponent) const override;
        unsigned char GetColorComponentCount() const override;
    protected:
        PdfVariant GetExportObject(PdfIndirectObjectList& objects) const override;
    private:
        PdfColorSpaceFilterPtr m_BaseColorSpace;
        PdfVariant m_colorSpaceExpVar;
        unsigned m_MapSize;
        charbuff m_lookup;
    };

    class PODOFO_API PdfColorSpaceFilterLab final : public PdfColorSpaceFilter
    {
    public:
        PdfColorSpaceFilterLab(const std::array<double, 3>& whitePoint,
            nullable<const std::array<double, 3>&> blackPoint = nullptr,
            nullable<const std::array<double, 4>&> range = nullptr);
    public:
        PdfColorSpaceType GetType() const override;
        bool IsRawEncoded() const override;
        PdfColorSpacePixelFormat GetPixelFormat() const override;
        unsigned GetSourceScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        unsigned GetScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        void FetchScanLine(unsigned char* dstScanLine, const unsigned char* srcScanLine,
            unsigned width, unsigned bitsPerComponent) const override;
        unsigned char GetColorComponentCount() const override;
    protected:
        PdfVariant GetExportObject(PdfIndirectObjectList& objects) const override;
    private:
        std::array<double, 3> m_WhitePoint;
        std::array<double, 3> m_BlackPoint;
        std::array<double, 4> m_Range;
    };

    class PODOFO_API PdfColorSpaceFilterSeparation final : public PdfColorSpaceFilter
    {
    public:
        /// Create a new PdfColor object with
        /// a separation-name and an equivalent color
        ///
        /// @param name Name of the separation color
        /// @param alternateColor the alternate color, must be of type gray, rgb, cmyk or cie
        PdfColorSpaceFilterSeparation(const std::string_view& name, const PdfColor& alternateColor);

        /// Create a new PdfColor object with
        /// Separation color None.
        ///
        static std::unique_ptr<PdfColorSpaceFilterSeparation> CreateSeparationNone();

        /// Create a new PdfColor object with
        /// Separation color All.
        ///
        static std::unique_ptr<PdfColorSpaceFilterSeparation> CreateSeparationAll();
    public:
        PdfColorSpaceType GetType() const override;
        bool IsRawEncoded() const override;
        PdfColorSpacePixelFormat GetPixelFormat() const override;
        unsigned GetSourceScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        unsigned GetScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        void FetchScanLine(unsigned char* dstScanLine, const unsigned char* srcScanLine,
            unsigned width, unsigned bitsPerComponent) const override;
        unsigned char GetColorComponentCount() const override;
    protected:
        PdfVariant GetExportObject(PdfIndirectObjectList& objects) const override;
    public:
        const std::string& GetName() const { return m_Name; }
        const PdfColorRaw& GetAlternateColor() const;
        const PdfColorSpaceFilter& GetColorSpace() const;
    private:
        std::string m_Name;
        PdfColor m_AlternateColor;
    };

    /// Color space as described by ISO 32000-2:2020 "8.6.5.5 ICCBased colour spaces"
    class PODOFO_API PdfColorSpaceFilterICCBased final : public PdfColorSpaceFilter
    {
    public:
        PdfColorSpaceFilterICCBased(PdfColorSpaceInitializer&& alternateColorSpace, charbuff iccprofile);

        /// @remarks Deserialization constructor
        PdfColorSpaceFilterICCBased(PdfColorSpaceFilterPtr&& alternateColorSpace, charbuff&& iccprofile);
    public:
        PdfColorSpaceType GetType() const override;
        PdfColorSpacePixelFormat GetPixelFormat() const override;
        unsigned GetSourceScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        unsigned GetScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        void FetchScanLine(unsigned char* dstScanLine, const unsigned char* srcScanLine,
            unsigned width, unsigned bitsPerComponent) const override;
        unsigned char GetColorComponentCount() const override;
    protected:
        PdfVariant GetExportObject(PdfIndirectObjectList& objects) const override;
    private:
        PdfColorSpaceFilterPtr m_AlternateColorSpace;
        PdfVariant m_colorSpaceExpVar;
        charbuff m_iccprofile;
    };

    /// A color space for a tiling or a shading pattern, as described by ISO 32000-2:2020 8.6.6.2 "Pattern colour spaces"
    class PODOFO_API PdfColorSpaceFilterPattern final : public PdfColorSpaceFilter
    {
    public:
        PdfColorSpaceFilterPattern(PdfColorSpaceInitializer&& underlyingColorSpace);

        /// @remarks Deserialization constructor
        PdfColorSpaceFilterPattern(PdfColorSpaceFilterPtr&& alternateColorSpace);
    public:
        PdfColorSpaceType GetType() const override;
        PdfColorSpacePixelFormat GetPixelFormat() const override;
        unsigned GetSourceScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        unsigned GetScanLineSize(unsigned width, unsigned bitsPerComponent) const override;
        void FetchScanLine(unsigned char* dstScanLine, const unsigned char* srcScanLine,
            unsigned width, unsigned bitsPerComponent) const override;
        unsigned char GetColorComponentCount() const override;
    protected:
        PdfVariant GetExportObject(PdfIndirectObjectList& objects) const override;
    public:
        const PdfColorSpaceFilter& GetUnderlyingColorSpace() const { return *m_UnderlyingColorSpace; }
    private:
        PdfColorSpaceFilterPtr m_UnderlyingColorSpace;
        PdfVariant m_colorSpaceExpVar;
    };


    class PODOFO_API PdfColorSpaceFilterFactory final
    {
        friend class PdfImage;
        friend class PdfColorSpaceFilterPattern;
        friend class PdfGraphicsStateWrapper;
        friend class PdfColorSpaceInitializer;

    public:
        static bool TryCreateFromObject(const PdfObject& obj, PdfColorSpaceFilterPtr& colorSpace);

        /// True if the filter is trivial like /DeviceRGB, /DeviceGray or /DeviceCMYK
        static PdfColorSpaceFilterPtr GetTrivialFilterPtr(PdfColorSpaceType type);
        static PdfColorSpaceFilterPtr GetTrivialFilterPtr(PdfColorSpaceType type, PdfName& exportName);

        /// Singleton method which returns a global instance
        /// of /DeviceGray color space
        static PdfColorSpaceFilterPtr GetDeviceGrayInstancePtr();
        static const PdfColorSpaceFilter& GetDeviceGrayInstance();

        /// Singleton method which returns a global instance
        /// of /DeviceRGB color space
        static PdfColorSpaceFilterPtr GetDeviceRGBInstancePtr();
        static const PdfColorSpaceFilter& GetDeviceRGBInstance();

        /// Singleton method which returns a global instance
        /// of /DeviceCMYK color space
        static PdfColorSpaceFilterPtr GetDeviceCMYKInstancePtr();
        static const PdfColorSpaceFilter& GetDeviceCMYKInstance();

    private:
        /// Singleton method which returns a global instance
        /// of Unknown color space
        static const PdfColorSpaceFilterPtr& GetUnkownInstancePtr();

        /// Singleton method which returns a global instance
        /// of parameter less Pattern color space
        static const PdfColorSpaceFilterPtr& GetParameterLessPatternInstancePtr();

    private:
        static const PdfColorSpaceFilterPtr& getDeviceGrayInstancePtr();
        static const PdfColorSpaceFilterPtr& getDeviceRGBInstancePtr();
        static const PdfColorSpaceFilterPtr& getDeviceCMYKInstancePtr();
    };
}

#endif // PDF_COLOR_SPACE_FILTER_H
