/*
 * Copyright 2006 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkPaint_DEFINED
#define SkPaint_DEFINED

#include "include/core/SkColor.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkCPUTypes.h"
#include "include/private/base/SkFloatingPoint.h"
#include "include/private/base/SkTo.h"
#include "include/private/base/SkTypeTraits.h"

#include <cstdint>
#include <optional>
#include <type_traits>

class SkBlender;
class SkColorFilter;
class SkColorSpace;
class SkImageFilter;
class SkMaskFilter;
class SkPathEffect;
class SkShader;
enum class SkBlendMode;
struct SkRect;

/** \class SkPaint
    SkPaint controls options applied when drawing. SkPaint collects all
    options outside of the SkCanvas clip and SkCanvas matrix.

    Various options apply to strokes and fills, and images.

    SkPaint collects effects and filters that describe single-pass and multiple-pass
    algorithms that alter the drawing geometry, color, and transparency. For instance,
    SkPaint does not directly implement dashing or blur, but contains the objects that do so.
*/
class SK_API SkPaint {
public:

    /** Constructs SkPaint with default values.

        @return  default initialized SkPaint

        example: https://fiddle.skia.org/c/@Paint_empty_constructor
    */
    SkPaint();

    /** Constructs SkPaint with default values and the given color.

        Sets alpha and RGB used when stroking and filling. The color is four floating
        point values, unpremultiplied. The color values are interpreted as being in
        the colorSpace. If colorSpace is nullptr, then color is assumed to be in the
        sRGB color space.

        @param color       unpremultiplied RGBA
        @param colorSpace  SkColorSpace describing the encoding of color
        @return            SkPaint with the given color
    */
    explicit SkPaint(const SkColor4f& color, SkColorSpace* colorSpace = nullptr);

    /** Makes a shallow copy of SkPaint. SkPathEffect, SkShader,
        SkMaskFilter, SkColorFilter, and SkImageFilter are shared
        between the original paint and the copy. Objects containing SkRefCnt increment
        their references by one.

        The referenced objects SkPathEffect, SkShader, SkMaskFilter, SkColorFilter,
        and SkImageFilter cannot be modified after they are created.
        This prevents objects with SkRefCnt from being modified once SkPaint refers to them.

        @param paint  original to copy
        @return       shallow copy of paint

        example: https://fiddle.skia.org/c/@Paint_copy_const_SkPaint
    */
    SkPaint(const SkPaint& paint);

    /** Implements a move constructor to avoid increasing the reference counts
        of objects referenced by the paint.

        After the call, paint is undefined, and can be safely destructed.

        @param paint  original to move
        @return       content of paint

        example: https://fiddle.skia.org/c/@Paint_move_SkPaint
    */
    SkPaint(SkPaint&& paint);

    /** Decreases SkPaint SkRefCnt of owned objects: SkPathEffect, SkShader,
        SkMaskFilter, SkColorFilter, and SkImageFilter. If the
        objects containing SkRefCnt go to zero, they are deleted.
    */
    ~SkPaint();

    /** Makes a shallow copy of SkPaint. SkPathEffect, SkShader,
        SkMaskFilter, SkColorFilter, and SkImageFilter are shared
        between the original paint and the copy. Objects containing SkRefCnt in the
        prior destination are decreased by one, and the referenced objects are deleted if the
        resulting count is zero. Objects containing SkRefCnt in the parameter paint
        are increased by one. paint is unmodified.

        @param paint  original to copy
        @return       content of paint

        example: https://fiddle.skia.org/c/@Paint_copy_operator
    */
    SkPaint& operator=(const SkPaint& paint);

    /** Moves the paint to avoid increasing the reference counts
        of objects referenced by the paint parameter. Objects containing SkRefCnt in the
        prior destination are decreased by one; those objects are deleted if the resulting count
        is zero.

        After the call, paint is undefined, and can be safely destructed.

        @param paint  original to move
        @return       content of paint

        example: https://fiddle.skia.org/c/@Paint_move_operator
    */
    SkPaint& operator=(SkPaint&& paint);

    /** Compares a and b, and returns true if a and b are equivalent. May return false
        if SkPathEffect, SkShader, SkMaskFilter, SkColorFilter,
        or SkImageFilter have identical contents but different pointers.

        @param a  SkPaint to compare
        @param b  SkPaint to compare
        @return   true if SkPaint pair are equivalent
    */
    SK_API friend bool operator==(const SkPaint& a, const SkPaint& b);

    /** Compares a and b, and returns true if a and b are not equivalent. May return true
        if SkPathEffect, SkShader, SkMaskFilter, SkColorFilter,
        or SkImageFilter have identical contents but different pointers.

        @param a  SkPaint to compare
        @param b  SkPaint to compare
        @return   true if SkPaint pair are not equivalent
    */
    friend bool operator!=(const SkPaint& a, const SkPaint& b) {
        return !(a == b);
    }

    /** Sets all SkPaint contents to their initial values. This is equivalent to replacing
        SkPaint with the result of SkPaint().

        example: https://fiddle.skia.org/c/@Paint_reset
    */
    void reset();

    /** Returns true if pixels on the active edges of SkPath may be drawn with partial transparency.
        @return  antialiasing state
    */
    bool isAntiAlias() const {
        return SkToBool(fBitfields.fAntiAlias);
    }

    /** Requests, but does not require, that edge pixels draw opaque or with
        partial transparency.
        @param aa  setting for antialiasing
    */
    void setAntiAlias(bool aa) { fBitfields.fAntiAlias = static_cast<unsigned>(aa); }

    /** Returns true if color error may be distributed to smooth color transition.
        @return  dithering state
    */
    bool isDither() const {
        return SkToBool(fBitfields.fDither);
    }

    /** Requests, but does not require, to distribute color error.
        @param dither  setting for ditering
    */
    void setDither(bool dither) { fBitfields.fDither = static_cast<unsigned>(dither); }

    /** \enum SkPaint::Style
        Set Style to fill, stroke, or both fill and stroke geometry.
        The stroke and fill
        share all paint attributes; for instance, they are drawn with the same color.

        Use kStrokeAndFill_Style to avoid hitting the same pixels twice with a stroke draw and
        a fill draw.
    */
    enum Style : uint8_t {
        kFill_Style,          //!< set to fill geometry
        kStroke_Style,        //!< set to stroke geometry
        kStrokeAndFill_Style, //!< sets to stroke and fill geometry
    };

    /** May be used to verify that SkPaint::Style is a legal value.
    */
    static constexpr int kStyleCount = kStrokeAndFill_Style + 1;

    /** Returns whether the geometry is filled, stroked, or filled and stroked.
    */
    Style getStyle() const { return (Style)fBitfields.fStyle; }

    /** Sets whether the geometry is filled, stroked, or filled and stroked.
        Has no effect if style is not a legal SkPaint::Style value.

        example: https://fiddle.skia.org/c/@Paint_setStyle
        example: https://fiddle.skia.org/c/@Stroke_Width
    */
    void setStyle(Style style);

    /**
     *  Set paint's style to kStroke if true, or kFill if false.
     */
    void setStroke(bool);

    /** Retrieves alpha and RGB, unpremultiplied, packed into 32 bits.
        Use helpers SkColorGetA(), SkColorGetR(), SkColorGetG(), and SkColorGetB() to extract
        a color component.

        @return  unpremultiplied ARGB
    */
    SkColor getColor() const { return fColor4f.toSkColor(); }

    /** Retrieves alpha and RGB, unpremultiplied, as four floating point values. RGB are
        extended sRGB values (sRGB gamut, and encoded with the sRGB transfer function).

        @return  unpremultiplied RGBA
    */
    SkColor4f getColor4f() const { return fColor4f; }

    /** Sets alpha and RGB used when stroking and filling. The color is a 32-bit value,
        unpremultiplied, packing 8-bit components for alpha, red, blue, and green.

        @param color  unpremultiplied ARGB

        example: https://fiddle.skia.org/c/@Paint_setColor
    */
    void setColor(SkColor color);

    /** Sets alpha and RGB used when stroking and filling. The color is four floating
        point values, unpremultiplied. The color values are interpreted as being in
        the colorSpace. If colorSpace is nullptr, then color is assumed to be in the
        sRGB color space.

        @param color       unpremultiplied RGBA
        @param colorSpace  SkColorSpace describing the encoding of color
    */
    void setColor(const SkColor4f& color, SkColorSpace* colorSpace = nullptr);

    void setColor4f(const SkColor4f& color, SkColorSpace* colorSpace = nullptr) {
        this->setColor(color, colorSpace);
    }

    /** Retrieves alpha from the color used when stroking and filling.

        @return  alpha ranging from zero, fully transparent, to one, fully opaque
    */
    float getAlphaf() const { return fColor4f.fA; }

    // Helper that scales the alpha by 255.
    uint8_t getAlpha() const {
        return static_cast<uint8_t>(sk_float_round2int(this->getAlphaf() * 255));
    }

    /** Replaces alpha, leaving RGB
        unchanged. An out of range value triggers an assert in the debug
        build. a is a value from 0.0 to 1.0.
        a set to zero makes color fully transparent; a set to 1.0 makes color
        fully opaque.

        @param a  alpha component of color
    */
    void setAlphaf(float a);

    // Helper that accepts an int between 0 and 255, and divides it by 255.0
    void setAlpha(U8CPU a) {
        this->setAlphaf(a * (1.0f / 255));
    }

    /** Sets color used when drawing solid fills. The color components range from 0 to 255.
        The color is unpremultiplied; alpha sets the transparency independent of RGB.

        @param a  amount of alpha, from fully transparent (0) to fully opaque (255)
        @param r  amount of red, from no red (0) to full red (255)
        @param g  amount of green, from no green (0) to full green (255)
        @param b  amount of blue, from no blue (0) to full blue (255)

        example: https://fiddle.skia.org/c/@Paint_setARGB
    */
    void setARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b);

    /** Returns the thickness of the pen used by SkPaint to
        outline the shape.

        @return  zero for hairline, greater than zero for pen thickness
    */
    SkScalar getStrokeWidth() const { return fWidth; }

    /** Sets the thickness of the pen used by the paint to outline the shape.
        A stroke-width of zero is treated as "hairline" width. Hairlines are always exactly one
        pixel wide in device space (their thickness does not change as the canvas is scaled).
        Negative stroke-widths are invalid; setting a negative width will have no effect.

        @param width  zero thickness for hairline; greater than zero for pen thickness

        example: https://fiddle.skia.org/c/@Miter_Limit
        example: https://fiddle.skia.org/c/@Paint_setStrokeWidth
    */
    void setStrokeWidth(SkScalar width);

    /** Returns the limit at which a sharp corner is drawn beveled.

        @return  zero and greater miter limit
    */
    SkScalar getStrokeMiter() const { return fMiterLimit; }

    /** Sets the limit at which a sharp corner is drawn beveled.
        Valid values are zero and greater.
        Has no effect if miter is less than zero.

        @param miter  zero and greater miter limit

        example: https://fiddle.skia.org/c/@Paint_setStrokeMiter
    */
    void setStrokeMiter(SkScalar miter);

    /** \enum SkPaint::Cap
        Cap draws at the beginning and end of an open path contour.
    */
    enum Cap {
        kButt_Cap,                  //!< no stroke extension
        kRound_Cap,                 //!< adds circle
        kSquare_Cap,                //!< adds square
        kLast_Cap    = kSquare_Cap, //!< largest Cap value
        kDefault_Cap = kButt_Cap,   //!< equivalent to kButt_Cap
    };

    /** May be used to verify that SkPaint::Cap is a legal value.
    */
    static constexpr int kCapCount = kLast_Cap + 1;

    /** \enum SkPaint::Join
        Join specifies how corners are drawn when a shape is stroked. Join
        affects the four corners of a stroked rectangle, and the connected segments in a
        stroked path.

        Choose miter join to draw sharp corners. Choose round join to draw a circle with a
        radius equal to the stroke width on top of the corner. Choose bevel join to minimally
        connect the thick strokes.

        The fill path constructed to describe the stroked path respects the join setting but may
        not contain the actual join. For instance, a fill path constructed with round joins does
        not necessarily include circles at each connected segment.
    */
    enum Join : uint8_t {
        kMiter_Join,                 //!< extends to miter limit
        kRound_Join,                 //!< adds circle
        kBevel_Join,                 //!< connects outside edges
        kLast_Join    = kBevel_Join, //!< equivalent to the largest value for Join
        kDefault_Join = kMiter_Join, //!< equivalent to kMiter_Join
    };

    /** May be used to verify that SkPaint::Join is a legal value.
    */
    static constexpr int kJoinCount = kLast_Join + 1;

    /** Returns the geometry drawn at the beginning and end of strokes.
    */
    Cap getStrokeCap() const { return (Cap)fBitfields.fCapType; }

    /** Sets the geometry drawn at the beginning and end of strokes.

        example: https://fiddle.skia.org/c/@Paint_setStrokeCap_a
        example: https://fiddle.skia.org/c/@Paint_setStrokeCap_b
    */
    void setStrokeCap(Cap cap);

    /** Returns the geometry drawn at the corners of strokes.
    */
    Join getStrokeJoin() const { return (Join)fBitfields.fJoinType; }

    /** Sets the geometry drawn at the corners of strokes.

        example: https://fiddle.skia.org/c/@Paint_setStrokeJoin
    */
    void setStrokeJoin(Join join);

    /** Returns optional colors used when filling a path, such as a gradient.

        Does not alter SkShader SkRefCnt.

        @return  SkShader if previously set, nullptr otherwise
    */
    SkShader* getShader() const { return fShader.get(); }

    /** Returns optional colors used when filling a path, such as a gradient.

        Increases SkShader SkRefCnt by one.

        @return  SkShader if previously set, nullptr otherwise

        example: https://fiddle.skia.org/c/@Paint_refShader
    */
    sk_sp<SkShader> refShader() const;

    /** Sets optional colors used when filling a path, such as a gradient.

        Sets SkShader to shader, decreasing SkRefCnt of the previous SkShader.
        Increments shader SkRefCnt by one.

        @param shader  how geometry is filled with color; if nullptr, color is used instead

        example: https://fiddle.skia.org/c/@Color_Filter_Methods
        example: https://fiddle.skia.org/c/@Paint_setShader
    */
    void setShader(sk_sp<SkShader> shader);

    /** Returns SkColorFilter if set, or nullptr.
        Does not alter SkColorFilter SkRefCnt.

        @return  SkColorFilter if previously set, nullptr otherwise
    */
    SkColorFilter* getColorFilter() const { return fColorFilter.get(); }

    /** Returns SkColorFilter if set, or nullptr.
        Increases SkColorFilter SkRefCnt by one.

        @return  SkColorFilter if set, or nullptr

        example: https://fiddle.skia.org/c/@Paint_refColorFilter
    */
    sk_sp<SkColorFilter> refColorFilter() const;

    /** Sets SkColorFilter to filter, decreasing SkRefCnt of the previous
        SkColorFilter. Pass nullptr to clear SkColorFilter.

        Increments filter SkRefCnt by one.

        @param colorFilter  SkColorFilter to apply to subsequent draw

        example: https://fiddle.skia.org/c/@Blend_Mode_Methods
        example: https://fiddle.skia.org/c/@Paint_setColorFilter
    */
    void setColorFilter(sk_sp<SkColorFilter> colorFilter);

    /** If the current blender can be represented as a SkBlendMode enum, this returns that
     *  enum in the optional's value(). If it cannot, then the returned optional does not
     *  contain a value.
     */
    std::optional<SkBlendMode> asBlendMode() const;

    /**
     *  Queries the blender, and if it can be represented as a SkBlendMode, return that mode,
     *  else return the defaultMode provided.
     */
    SkBlendMode getBlendMode_or(SkBlendMode defaultMode) const;

    /** Returns true iff the current blender claims to be equivalent to SkBlendMode::kSrcOver.
     *
     *  Also returns true of the current blender is nullptr.
     */
    bool isSrcOver() const;

    /** Helper method for calling setBlender().
     *
     *  This sets a blender that implements the specified blendmode enum.
     */
    void setBlendMode(SkBlendMode mode);

    /** Returns the user-supplied blend function, if one has been set.
     *  Does not alter SkBlender's SkRefCnt.
     *
     *  A nullptr blender signifies the default SrcOver behavior.
     *
     *  @return  the SkBlender assigned to this paint, otherwise nullptr
     */
    SkBlender* getBlender() const { return fBlender.get(); }

    /** Returns the user-supplied blend function, if one has been set.
     *  Increments the SkBlender's SkRefCnt by one.
     *
     *  A nullptr blender signifies the default SrcOver behavior.
     *
     *  @return  the SkBlender assigned to this paint, otherwise nullptr
     */
    sk_sp<SkBlender> refBlender() const;

    /** Sets the current blender, increasing its refcnt, and if a blender is already
     *  present, decreasing that object's refcnt.
     *
     *  A nullptr blender signifies the default SrcOver behavior.
     *
     *  For convenience, you can call setBlendMode() if the blend effect can be expressed
     *  as one of those values.
     */
    void setBlender(sk_sp<SkBlender> blender);

    /** Returns SkPathEffect if set, or nullptr.
        Does not alter SkPathEffect SkRefCnt.

        @return  SkPathEffect if previously set, nullptr otherwise
    */
    SkPathEffect* getPathEffect() const { return fPathEffect.get(); }

    /** Returns SkPathEffect if set, or nullptr.
        Increases SkPathEffect SkRefCnt by one.

        @return  SkPathEffect if previously set, nullptr otherwise

        example: https://fiddle.skia.org/c/@Paint_refPathEffect
    */
    sk_sp<SkPathEffect> refPathEffect() const;

    /** Sets SkPathEffect to pathEffect, decreasing SkRefCnt of the previous
        SkPathEffect. Pass nullptr to leave the path geometry unaltered.

        Increments pathEffect SkRefCnt by one.

        @param pathEffect  replace SkPath with a modification when drawn

        example: https://fiddle.skia.org/c/@Mask_Filter_Methods
        example: https://fiddle.skia.org/c/@Paint_setPathEffect
    */
    void setPathEffect(sk_sp<SkPathEffect> pathEffect);

    /** Returns SkMaskFilter if set, or nullptr.
        Does not alter SkMaskFilter SkRefCnt.

        @return  SkMaskFilter if previously set, nullptr otherwise
    */
    SkMaskFilter* getMaskFilter() const { return fMaskFilter.get(); }

    /** Returns SkMaskFilter if set, or nullptr.

        Increases SkMaskFilter SkRefCnt by one.

        @return  SkMaskFilter if previously set, nullptr otherwise

        example: https://fiddle.skia.org/c/@Paint_refMaskFilter
    */
    sk_sp<SkMaskFilter> refMaskFilter() const;

    /** Sets SkMaskFilter to maskFilter, decreasing SkRefCnt of the previous
        SkMaskFilter. Pass nullptr to clear SkMaskFilter and leave SkMaskFilter effect on
        mask alpha unaltered.

        Increments maskFilter SkRefCnt by one.

        @param maskFilter  modifies clipping mask generated from drawn geometry

        example: https://fiddle.skia.org/c/@Paint_setMaskFilter
        example: https://fiddle.skia.org/c/@Typeface_Methods
    */
    void setMaskFilter(sk_sp<SkMaskFilter> maskFilter);

    /** Returns SkImageFilter if set, or nullptr.
        Does not alter SkImageFilter SkRefCnt.

        @return  SkImageFilter if previously set, nullptr otherwise
    */
    SkImageFilter* getImageFilter() const { return fImageFilter.get(); }

    /** Returns SkImageFilter if set, or nullptr.
        Increases SkImageFilter SkRefCnt by one.

        @return  SkImageFilter if previously set, nullptr otherwise

        example: https://fiddle.skia.org/c/@Paint_refImageFilter
    */
    sk_sp<SkImageFilter> refImageFilter() const;

    /** Sets SkImageFilter to imageFilter, decreasing SkRefCnt of the previous
        SkImageFilter. Pass nullptr to clear SkImageFilter, and remove SkImageFilter effect
        on drawing.

        Increments imageFilter SkRefCnt by one.

        @param imageFilter  how SkImage is sampled when transformed

        example: https://fiddle.skia.org/c/@Paint_setImageFilter
    */
    void setImageFilter(sk_sp<SkImageFilter> imageFilter);

    /** Returns true if SkPaint prevents all drawing;
        otherwise, the SkPaint may or may not allow drawing.

        Returns true if, for example, SkBlendMode combined with alpha computes a
        new alpha of zero.

        @return  true if SkPaint prevents all drawing

        example: https://fiddle.skia.org/c/@Paint_nothingToDraw
    */
    bool nothingToDraw() const;

    /**     (to be made private)
        Returns true if SkPaint does not include elements requiring extensive computation
        to compute device bounds of drawn geometry. For instance, SkPaint with SkPathEffect
        always returns false.

        @return  true if SkPaint allows for fast computation of bounds
    */
    bool canComputeFastBounds() const;

    /**     (to be made private)
        Only call this if canComputeFastBounds() returned true. This takes a
        raw rectangle (the raw bounds of a shape), and adjusts it for stylistic
        effects in the paint (e.g. stroking). If needed, it uses the storage
        parameter. It returns the adjusted bounds that can then be used
        for SkCanvas::quickReject tests.

        The returned SkRect will either be orig or storage, thus the caller
        should not rely on storage being set to the result, but should always
        use the returned value. It is legal for orig and storage to be the same
        SkRect.
            For example:
            if (!path.isInverseFillType() && paint.canComputeFastBounds()) {
                SkRect storage;
                if (canvas->quickReject(paint.computeFastBounds(path.getBounds(), &storage))) {
                    return; // do not draw the path
                }
            }
            // draw the path

        @param orig     geometry modified by SkPaint when drawn
        @param storage  computed bounds of geometry; may not be nullptr
        @return         fast computed bounds
    */
    const SkRect& computeFastBounds(const SkRect& orig, SkRect* storage) const;

    /**     (to be made private)

        @param orig     geometry modified by SkPaint when drawn
        @param storage  computed bounds of geometry
        @return         fast computed bounds
    */
    const SkRect& computeFastStrokeBounds(const SkRect& orig,
                                          SkRect* storage) const {
        return this->doComputeFastBounds(orig, storage, kStroke_Style);
    }

    /**     (to be made private)
        Computes the bounds, overriding the SkPaint SkPaint::Style. This can be used to
        account for additional width required by stroking orig, without
        altering SkPaint::Style set to fill.

        @param orig     geometry modified by SkPaint when drawn
        @param storage  computed bounds of geometry
        @param style    overrides SkPaint::Style
        @return         fast computed bounds
    */
    const SkRect& doComputeFastBounds(const SkRect& orig, SkRect* storage,
                                      Style style) const;

    using sk_is_trivially_relocatable = std::true_type;

private:
    sk_sp<SkPathEffect>   fPathEffect;
    sk_sp<SkShader>       fShader;
    sk_sp<SkMaskFilter>   fMaskFilter;
    sk_sp<SkColorFilter>  fColorFilter;
    sk_sp<SkImageFilter>  fImageFilter;
    sk_sp<SkBlender>      fBlender;

    SkColor4f       fColor4f;
    SkScalar        fWidth;
    SkScalar        fMiterLimit;
    union {
        struct {
            unsigned    fAntiAlias : 1;
            unsigned    fDither : 1;
            unsigned    fCapType : 2;
            unsigned    fJoinType : 2;
            unsigned    fStyle : 2;
            unsigned    fPadding : 24;  // 24 == 32 -1-1-2-2-2
        } fBitfields;
        uint32_t fBitfieldsUInt;
    };

    static_assert(::sk_is_trivially_relocatable<decltype(fPathEffect)>::value);
    static_assert(::sk_is_trivially_relocatable<decltype(fShader)>::value);
    static_assert(::sk_is_trivially_relocatable<decltype(fMaskFilter)>::value);
    static_assert(::sk_is_trivially_relocatable<decltype(fColorFilter)>::value);
    static_assert(::sk_is_trivially_relocatable<decltype(fImageFilter)>::value);
    static_assert(::sk_is_trivially_relocatable<decltype(fBlender)>::value);
    static_assert(::sk_is_trivially_relocatable<decltype(fColor4f)>::value);
    static_assert(::sk_is_trivially_relocatable<decltype(fBitfields)>::value);

    friend class SkPaintPriv;
};

#endif
