/*
 * Copyright 2025 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkGradient_DEFINED
#define SkGradient_DEFINED

#include "include/core/SkColor.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkPoint.h"
#include "include/core/SkShader.h"
#include "include/core/SkSpan.h"
#include "include/core/SkTileMode.h"
#include "include/private/base/SkAPI.h"

class SkMatrix;

class SkGradient {
public:
    struct Interpolation {
        enum class InPremul : bool { kNo = false, kYes = true };

        enum class ColorSpace : uint8_t {
            // Default Skia behavior: interpolate in the color space of the destination surface
            kDestination,

            // https://www.w3.org/TR/css-color-4/#interpolation-space
            kSRGBLinear,
            kLab,
            kOKLab,
            // This is the same as kOKLab, except it has a simplified version of the CSS gamut
            // mapping algorithm (https://www.w3.org/TR/css-color-4/#css-gamut-mapping)
            // into Rec2020 space applied to it.
            // Warning: This space is experimental and should not be used in production.
            kOKLabGamutMap,
            kLCH,
            kOKLCH,
            // This is the same as kOKLCH, except it has the same gamut mapping applied to it
            // as kOKLabGamutMap does.
            // Warning: This space is experimental and should not be used in production.
            kOKLCHGamutMap,
            kSRGB,
            kHSL,
            kHWB,

            kDisplayP3,
            kRec2020,
            kProphotoRGB,
            kA98RGB,

            kLastColorSpace = kA98RGB,
        };
        static constexpr int kColorSpaceCount = static_cast<int>(ColorSpace::kLastColorSpace) + 1;

        enum class HueMethod : uint8_t {
            // https://www.w3.org/TR/css-color-4/#hue-interpolation
            kShorter,
            kLonger,
            kIncreasing,
            kDecreasing,

            kLastHueMethod = kDecreasing,
        };
        static constexpr int kHueMethodCount = static_cast<int>(HueMethod::kLastHueMethod) + 1;

        InPremul fInPremul = InPremul::kNo;
        ColorSpace fColorSpace = ColorSpace::kDestination;
        HueMethod fHueMethod = HueMethod::kShorter;  // Only relevant for LCH, OKLCH, HSL, or HWB

        // legacy compatibility -- remove when we can
        static Interpolation FromFlags(uint32_t flags) {
            return {flags & 1 ? InPremul::kYes : InPremul::kNo,
                    ColorSpace::kDestination,
                    HueMethod::kShorter};
        }
    };

    /**
     *  Specification for the colors in a gradient.
     *
     *  @param  colors  The span of colors for the gradient.
     *  @param  pos     Relative positions of each color across the gradient. If empty,
     *                  the the colors are distributed evenly. If this is not null, the values
     *                  must lie between 0.0 and 1.0, and be strictly increasing. If the first
     *                  value is not 0.0, then an additional color stop is added at position 0.0,
     *                  with the same color as colors[0]. If the the last value is less than 1.0,
     *                  then an additional color stop is added at position 1.0, with the same color
     *                  as colors[count - 1].
     *  @param mode     Tiling mode for the gradient.
     *  @param cs       Optional colorspace associated with the span of colors. If this is null,
     *                  the colors are treated as sRGB.
     */
    class Colors {
    public:
        Colors() {}
        Colors(SkSpan<const SkColor4f> colors,
               SkSpan<const float> pos,
               SkTileMode mode,
               sk_sp<SkColorSpace> cs = nullptr)
                : fColors(colors), fPos(pos), fColorSpace(std::move(cs)), fTileMode(mode) {
            SkASSERT(fPos.size() == 0 || fPos.size() == fColors.size());

            // throw away inconsistent inputs
            if (fPos.size() != fColors.size()) {
                fPos = {};
            }
        }

        Colors(SkSpan<const SkColor4f> colors, SkTileMode tm, sk_sp<SkColorSpace> cs = nullptr)
                : Colors(colors, {}, tm, std::move(cs)) {}

        SkSpan<const SkColor4f> colors() const { return fColors; }
        SkSpan<const float> positions() const { return fPos; }
        const sk_sp<SkColorSpace>& colorSpace() const { return fColorSpace; }
        SkTileMode tileMode() const { return fTileMode; }

    private:
        SkSpan<const SkColor4f> fColors;
        SkSpan<const float> fPos;
        sk_sp<SkColorSpace> fColorSpace;
        SkTileMode fTileMode = SkTileMode::kClamp;
    };

    SkGradient() {}
    SkGradient(const Colors& colors, const Interpolation& interp)
            : fColors(colors), fInterpolation(interp) {}

    const Colors& colors() const { return fColors; }
    const Interpolation& interpolation() const { return fInterpolation; }

private:
    Colors fColors;
    Interpolation fInterpolation;
};

namespace SkShaders {
/** Returns a shader that generates a linear gradient between the two specified points.
 *  If the inputs are invalid, this will return nullptr.
 *  @param points  Array of 2 points, the end-points of the line segment
 *  @param grad    Description of the colors and interpolation method
 *  @param lm      Optional local matrix, may be null
 */
SK_API sk_sp<SkShader> LinearGradient(const SkPoint pts[2],
                                      const SkGradient&,
                                      const SkMatrix* lm = nullptr);

/** Returns a shader that generates a radial gradient given the center and radius.
 *  @param center  The center of the circle for this gradient
 *  @param radius  Must be positive. The radius of the circle for this gradient
 *  @param grad    Description of the colors and interpolation method
 *  @param lm      Optional local matrix, may be null
 */
SK_API sk_sp<SkShader> RadialGradient(SkPoint center,
                                      float radius,
                                      const SkGradient& grad,
                                      const SkMatrix* lm = nullptr);

/** Returns a shader that generates a conical gradient given two circles, or
 *  returns null if the inputs are invalid. The gradient interprets the
 *  two circles according to the following HTML spec.
 *  http://dev.w3.org/html5/2dcontext/#dom-context-2d-createradialgradient
 *  @param start        The center of the circle for this gradient
 *  @param startRadius  Must be positive. The radius of the circle for this gradient
 *  @param end          The center of the circle for this gradient
 *  @param startRadius  Must be positive. The radius of the circle for this gradient
 *  @param grad         Description of the colors and interpolation method
 *  @param lm           Optional local matrix, may be null
 */
SK_API sk_sp<SkShader> TwoPointConicalGradient(SkPoint start,
                                               float startRadius,
                                               SkPoint end,
                                               float endRadius,
                                               const SkGradient& grad,
                                               const SkMatrix* lm = nullptr);

/** Returns a shader that generates a sweep gradient given a center.
 *  The shader accepts negative angles and angles larger than 360, draws
 *  between 0 and 360 degrees, similar to the CSS conic-gradient
 *  semantics. 0 degrees means horizontal positive x axis. The start angle
 *  must be less than the end angle, otherwise a null pointer is
 *  returned. If color stops do not contain 0 and 1 but are within this
 *  range, the respective outer color stop is repeated for 0 and 1. Color
 *  stops less than 0 are clamped to 0, and greater than 1 are clamped to 1.
 *  @param center      The center of the sweep
 *  @param startAngle  Start of the angular range, corresponding to pos == 0.
 *  @param endAngle    End of the angular range, corresponding to pos == 1.
 *  @param grad        Description of the colors and interpolation method
 *  @param lm          Optional local matrix, may be null
 */
SK_API sk_sp<SkShader> SweepGradient(SkPoint center,
                                     float startAngle,
                                     float endAngle,
                                     const SkGradient&,
                                     const SkMatrix* lm = nullptr);
static inline sk_sp<SkShader> SweepGradient(SkPoint center,
                                     const SkGradient& grad,
                                     const SkMatrix* lm = nullptr) {
    return SweepGradient(center, 0, 360, grad, lm);
}

}  // namespace SkShaders

#endif
