/*
 *  SPDX-FileCopyrightText: 1999 Matthias Elter <me@kde.org>
 *  SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
 *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 */
#ifndef KIS_BRUSH_
#define KIS_BRUSH_

#include <QImage>

#include <KoResource.h>

#include <kis_types.h>
#include <kis_shared.h>
#include <kis_dab_shape.h>
#include <kritabrush_export.h>

class QString;
class KoColor;
class KoColorSpace;

class KisPaintInformation;
class KisPaintopLodLimitations;
class KoAbstractGradient;
class KisOptimizedBrushOutline;
typedef QSharedPointer<KoAbstractGradient> KoAbstractGradientSP;

enum enumBrushType {
    INVALID,
    MASK,
    IMAGE,
    PIPE_MASK,
    PIPE_IMAGE
};

enum enumBrushApplication {
    ALPHAMASK,
    IMAGESTAMP,
    LIGHTNESSMAP,
    GRADIENTMAP
};

static const qreal DEFAULT_SOFTNESS_FACTOR = 1.0;
static const qreal DEFAULT_LIGHTNESS_STRENGTH = 1.0;

class KisBrush;
typedef QSharedPointer<KisBrush> KisBrushSP;

/**
 * KisBrush is the base class for brush resources. A brush resource
 * defines one or more images that are used to potato-stamp along
 * the drawn path. The brush type defines how this brush is used --
 * the important difference is between masks (which take the current
 * painting color) and images (which do not). It is up to the paintop
 * to make use of this feature.
 *
 * Brushes must be serializable to an xml representation and provide
 * a factory class that can recreate or retrieve the brush based on
 * this representation.
 *
 * XXX: This api is still a big mess -- it needs a good refactoring.
 * And the whole KoResource architecture is way over-designed.
 */
class BRUSH_EXPORT KisBrush : public KoResource
{
public:
    class ColoringInformation
    {
    public:
        virtual ~ColoringInformation();
        virtual const quint8* color() const = 0;
        virtual void nextColumn() = 0;
        virtual void nextRow() = 0;
    };

protected:

    class PlainColoringInformation : public ColoringInformation
    {
    public:
        PlainColoringInformation(const quint8* color);
        ~PlainColoringInformation() override;
        const quint8* color() const override ;
        void nextColumn() override;
        void nextRow() override;
    private:
        const quint8* m_color;
    };

    class PaintDeviceColoringInformation : public ColoringInformation
    {

    public:

        PaintDeviceColoringInformation(const KisPaintDeviceSP source, int width);
        ~PaintDeviceColoringInformation() override;
        const quint8* color() const override ;
        void nextColumn() override;
        void nextRow() override;

    private:

        const KisPaintDeviceSP m_source;
        KisHLineConstIteratorSP m_iterator;
    };

public:

    KisBrush();
    KisBrush(const QString& filename);
    ~KisBrush() override;

    KisBrush(const KisBrush &rhs);
    KisBrush &operator=(const KisBrush &rhs) = delete;

    virtual qreal userEffectiveSize() const = 0;
    virtual void setUserEffectiveSize(qreal value) = 0;

    QPair<QString, QString> resourceType() const override {
        return QPair<QString, QString>(ResourceType::Brushes, "");
    }

    /**
     * @brief brushImage the image the brush tip can paint with. Not all brush types have a single
     * image.
     * @return a valid QImage.
     */
    virtual QImage brushTipImage() const;

    /**
     * Is a paint device of the brush that should be used for generation
     * of the brush outline. Usually, it is the same device returned
     * by brushTipImage(), but might be different in some types
     * of brushes, like in KisAutoBrush.
     */
    virtual KisFixedPaintDeviceSP outlineSourceImage() const;


    /**
     * Change the spacing of the brush.
     * @param spacing a spacing of 1.0 means that strokes will be separated from one time the size
     *                of the brush.
     */
    virtual void setSpacing(double spacing);

    /**
     * @return the spacing between two strokes for this brush
     */
    double spacing() const;

    void setAutoSpacing(bool active, qreal coeff);

    bool autoSpacingActive() const;
    qreal autoSpacingCoeff() const;


    /**
     * @return the width (for scale == 1.0)
     */
    qint32 width() const;

    /**
     * @return the height (for scale == 1.0)
     */
    qint32 height() const;

    /**
     * @return the width of the mask for the given scale and angle
     */
    virtual qint32 maskWidth(KisDabShape const&, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const;

    /**
     * @return the height of the mask for the given scale and angle
     */
    virtual qint32 maskHeight(KisDabShape const&, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const;

    /**
     * @return the logical size of the brush, that is the size measured
     *         in floating point value.
     *
     *         This value should not be used for calculating future dab sizes
     *         because it doesn't take any rounding into account. The only use
     *         of this metric is calculation of brush-size derivatives like
     *         hotspots and spacing.
     */
     virtual QSizeF characteristicSize(KisDabShape const&) const;

    /**
     * @return the angle of the mask adding the given angle
     */
    double maskAngle(double angle = 0) const;

    /**
     * @return the currently selected index of the brush
     *         if the brush consists of multiple images
     *
     * @see prepareForSeqNo()
     */
    virtual quint32 brushIndex() const;

    /**
     * The brush type defines how the brush is used.
     */
    virtual enumBrushType brushType() const;

    QPointF hotSpot(KisDabShape const&, const KisPaintInformation& info) const;

    /**
     * Returns true if this brush can return something useful for the info. This is used
     * by Pipe Brushes that can't paint sometimes
     **/
    virtual bool canPaintFor(const KisPaintInformation& /*info*/);


    /**
     * Is called by the paint op when a paintop starts a stroke.  The
     * point is that we store brushes a server while the paint ops are
     * are recreated all the time. Is means that upon a stroke start
     * the brushes may need to clear its state.
     */
    virtual void notifyStrokeStarted();

    /**
     * Is called by the paint op before it is going to clone the brush into
     * multiple instances to pass to different threads. During this call the
     * brush is free to prepare some structures that may be shared by all the
     * clones without excessive recalculation.
     */
    virtual void notifyBrushIsGoingToBeClonedForStroke();

    /**
     * Is called by the multithreaded queue to prepare a specific brush
     * tip for the particular seqNo.
     *
     * NOTE: one should use always call prepareForSeqNo() before using the brush
     *
     * Currently, this is used by pipe'd brushes to implement
     * incremental and random parasites
     */
    virtual void prepareForSeqNo(const KisPaintInformation& info, int seqNo);

    /**
     * Return a fixed paint device that contains a correctly scaled image dab.
     */
    virtual KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace,
            KisDabShape const&,
            const KisPaintInformation& info,
            double subPixelX = 0, double subPixelY = 0) const;

    /**
     * clear dst fill it with a mask colored with KoColor
     */
    void mask(KisFixedPaintDeviceSP dst,
              const KoColor& color,
              KisDabShape const& shape,
              const KisPaintInformation& info,
              double subPixelX = 0, double subPixelY = 0, 
              qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR, qreal lightnessStrength = DEFAULT_LIGHTNESS_STRENGTH) const;

    /**
     * clear dst and fill it with a mask colored with the corresponding colors of src
     */
    void mask(KisFixedPaintDeviceSP dst,
              const KisPaintDeviceSP src,
              KisDabShape const& shape,
              const KisPaintInformation& info,
              double subPixelX = 0, double subPixelY = 0, 
              qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR, qreal lightnessStrength = DEFAULT_LIGHTNESS_STRENGTH) const;


    virtual enumBrushApplication brushApplication() const;

    virtual void setBrushApplication(enumBrushApplication brushApplication);

    virtual bool preserveLightness() const;

    virtual bool applyingGradient() const;

    virtual void setGradient(KoAbstractGradientSP gradient);


    /**
     * Create a mask and either mask dst (that is, change all alpha values of the
     * existing pixels to those of the mask) or, if coloringInfo is present, clear
     * dst and fill dst with pixels according to coloringInfo, masked according to the
     * generated mask.
     *
     * @param dst the destination that will be draw on the image, and this function
     *            will edit its alpha channel
     * @param coloringInfo coloring information that will be copied on the dab, it can be null
     * @param shape a shape applied on the alpha mask
     * @param info the painting information (this is only and should only be used by
     *             KisImagePipeBrush and only to be backward compatible with the Gimp,
     *             KisImagePipeBrush is ignoring scale and angle information)
     * @param subPixelX sub position of the brush (contained between 0.0 and 1.0)
     * @param subPixelY sub position of the brush (contained between 0.0 and 1.0)
     * @param softnessFactor softness factor of the brush
     *
     * @return a mask computed from the grey-level values of the
     * pixels in the brush.
     */
    virtual void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
            ColoringInformation* coloringInfo,
            KisDabShape const&,
            const KisPaintInformation& info,
            double subPixelX, double subPixelY,
            qreal softnessFactor, qreal lightnessStrength) const;

    void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
        ColoringInformation* coloringInfo,
        KisDabShape const&,
        const KisPaintInformation& info,
        double subPixelX = 0, double subPixelY = 0,
        qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const;


    /**
     * Serialize this brush to XML.
     */
    virtual void toXML(QDomDocument& , QDomElement&) const;

    static KisBrushSP fromXML(const QDomElement& element, KisResourcesInterfaceSP resourcesInterface);

    static KoResourceLoadResult fromXMLLoadResult(const QDomElement& element, KisResourcesInterfaceSP resourcesInterface);

    virtual KisOptimizedBrushOutline outline(bool forcePreciseOutline = false) const;

    virtual void setScale(qreal _scale);
    qreal scale() const;
    virtual void setAngle(qreal _angle);
    qreal angle() const;

    void clearBrushPyramid();

    virtual void lodLimitations(KisPaintopLodLimitations *l) const;

    virtual bool supportsCaching() const;

    virtual void coldInitBrush();

    static const QString brushTypeMetaDataKey;

protected:

    void setWidth(qint32 width);

    void setHeight(qint32 height);

    void setHotSpot(QPointF);

    /**
     * XXX
     */
    virtual void setBrushType(enumBrushType type);

public:

    /**
     * The image is used to represent the brush in the gui, and may also, depending on the brush type
     * be used to define the actual brush instance.
     */
    virtual void setBrushTipImage(const QImage& image);

    /**
     * Returns true if the brush has a bunch of pixels almost
     * fully transparent in the very center. If the brush is pierced,
     * then dulling mode may not work correctly due to empty samples.
     *
     * WARNING: this method is relatively expensive since it iterates
     *          up to 100 pixels of the brush.
     */
    virtual bool isPiercedApprox() const;

protected:

    void resetOutlineCache();
    void generateOutlineCache();
    bool outlineCacheIsValid() const;

    void predefinedBrushToXML(const QString &type, QDomElement& e) const;

private:

    struct Private;
    Private* const d;

};

Q_DECLARE_METATYPE(KisBrushSP)

#endif // KIS_BRUSH_

