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

#include "modules/skottie/src/effects/Effects.h"

#include "modules/skottie/src/SkottieAdapter.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGGradient.h"
#include "modules/sksg/include/SkSGRenderEffect.h"
#include "src/utils/SkJSON.h"

namespace skottie {
namespace internal {

namespace  {

class GradientRampEffectAdapter final : public SkNVRefCnt<GradientRampEffectAdapter> {
public:
    explicit GradientRampEffectAdapter(sk_sp<sksg::RenderNode> child)
        : fRoot(sksg::ShaderEffect::Make(std::move(child))) {}

    ADAPTER_PROPERTY(StartPoint, SkPoint  , SkPoint::Make(0, 0))
    ADAPTER_PROPERTY(EndPoint  , SkPoint  , SkPoint::Make(0, 0))
    ADAPTER_PROPERTY(StartColor, SkColor4f,    SkColors::kBlack)
    ADAPTER_PROPERTY(EndColor  , SkColor4f,    SkColors::kBlack)
    ADAPTER_PROPERTY(Blend     , SkScalar ,                   0)
    ADAPTER_PROPERTY(Scatter   , SkScalar ,                   0)

    // Really an enum: 1 -> linear, 7 -> radial (?!)
    ADAPTER_PROPERTY(Shape     , SkScalar,                   0)

    const sk_sp<sksg::ShaderEffect>& root() const { return fRoot; }

private:
    enum class InstanceType {
        kNone,
        kLinear,
        kRadial,
    };

    void apply() {
        // This adapter manages a SG fragment with the following structure:
        //
        // - ShaderEffect [fRoot]
        //     \  GradientShader [fGradient]
        //     \  child/wrapped fragment
        //
        // The gradient shader is updated based on the (animatable) instance type (linear/radial).

        auto update_gradient = [this] (InstanceType new_type) {
            if (new_type != fInstanceType) {
                fGradient = new_type == InstanceType::kLinear
                        ? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make())
                        : sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());

                fRoot->setShader(fGradient);
                fInstanceType = new_type;
            }

            fGradient->setColorStops({ {0, fStartColor}, {1, fEndColor} });
        };

        static constexpr int kLinearShapeValue = 1;
        const auto instance_type = (SkScalarRoundToInt(fShape) == kLinearShapeValue)
                ? InstanceType::kLinear
                : InstanceType::kRadial;

        // Sync the gradient shader instance if needed.
        update_gradient(instance_type);

        // Sync instance-dependent gradient params.
        if (instance_type == InstanceType::kLinear) {
            auto* lg = static_cast<sksg::LinearGradient*>(fGradient.get());
            lg->setStartPoint(fStartPoint);
            lg->setEndPoint(fEndPoint);
        } else {
            SkASSERT(instance_type == InstanceType::kRadial);

            auto* rg = static_cast<sksg::RadialGradient*>(fGradient.get());
            rg->setStartCenter(fStartPoint);
            rg->setEndCenter(fStartPoint);
            rg->setEndRadius(SkPoint::Distance(fStartPoint, fEndPoint));
        }

        // TODO: blend, scatter
    }

    sk_sp<sksg::ShaderEffect> fRoot;
    sk_sp<sksg::Gradient>     fGradient;
    InstanceType              fInstanceType = InstanceType::kNone;
};

} // anonymous ns

sk_sp<sksg::RenderNode> EffectBuilder::attachGradientEffect(const skjson::ArrayValue& jprops,
                                                            sk_sp<sksg::RenderNode> layer) const {
    enum : size_t {
        kStartPoint_Index  = 0,
        kStartColor_Index  = 1,
        kEndPoint_Index    = 2,
        kEndColor_Index    = 3,
        kRampShape_Index   = 4,
        kRampScatter_Index = 5,
        kBlendRatio_Index  = 6,
    };

    auto adapter = sk_make_sp<GradientRampEffectAdapter>(std::move(layer));

    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kStartPoint_Index),
        [adapter](const VectorValue& p0) {
            adapter->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(p0));
        });
    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kEndPoint_Index),
        [adapter](const VectorValue& p1) {
            adapter->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(p1));
        });
    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kStartColor_Index),
        [adapter](const VectorValue& c0) {
            adapter->setStartColor(ValueTraits<VectorValue>::As<SkColor4f>(c0));
        });
    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kEndColor_Index),
        [adapter](const VectorValue& c1) {
            adapter->setEndColor(ValueTraits<VectorValue>::As<SkColor4f>(c1));
        });
    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRampShape_Index),
        [adapter](const ScalarValue& shape) {
            adapter->setShape(shape);
        });
    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kBlendRatio_Index),
        [adapter](const ScalarValue& blend) {
            adapter->setBlend(blend);
        });
    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRampScatter_Index),
        [adapter](const ScalarValue& scatter) {
            adapter->setScatter(scatter);
        });

    return adapter->root();
}

} // namespace internal
} // namespace skottie
