/*
 * 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 "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkPoint_impl.h"
#include "modules/skottie/src/SkottiePriv.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/skottie/src/animator/Animator.h"
#include "modules/skottie/src/effects/Effects.h"
#include "modules/sksg/include/SkSGGradient.h"
#include "modules/sksg/include/SkSGRenderEffect.h"
#include "modules/sksg/include/SkSGRenderNode.h"

#include <cstddef>
#include <utility>

namespace skjson {
class ArrayValue;
}

namespace skottie {
namespace internal {

namespace  {

class GradientRampEffectAdapter final : public AnimatablePropertyContainer {
public:
    static sk_sp<GradientRampEffectAdapter> Make(const skjson::ArrayValue& jprops,
                                                 sk_sp<sksg::RenderNode> layer,
                                                 const AnimationBuilder* abuilder) {
        return sk_sp<GradientRampEffectAdapter>(new GradientRampEffectAdapter(jprops,
                                                                              std::move(layer),
                                                                              abuilder));
    }

    sk_sp<sksg::RenderNode> node() const { return fShaderEffect; }

private:
    GradientRampEffectAdapter(const skjson::ArrayValue& jprops,
                              sk_sp<sksg::RenderNode> layer,
                              const AnimationBuilder* abuilder)
        : fShaderEffect(sksg::ShaderEffect::Make(std::move(layer))) {
        enum : size_t {
             kStartPoint_Index = 0,
             kStartColor_Index = 1,
               kEndPoint_Index = 2,
               kEndColor_Index = 3,
              kRampShape_Index = 4,
            kRampScatter_Index = 5,
             kBlendRatio_Index = 6,
        };

        EffectBinder(jprops, *abuilder, this)
                .bind( kStartPoint_Index, fStartPoint)
                .bind( kStartColor_Index, fStartColor)
                .bind(   kEndPoint_Index, fEndPoint  )
                .bind(   kEndColor_Index, fEndColor  )
                .bind(  kRampShape_Index, fShape     )
                .bind(kRampScatter_Index, fScatter   )
                .bind( kBlendRatio_Index, fBlend     );
    }

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

    void onSync() override {
        // 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());

                fShaderEffect->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.
        const auto start_point = SkPoint{fStartPoint.x, fStartPoint.y},
                     end_point = SkPoint{  fEndPoint.x,   fEndPoint.y};
        if (instance_type == InstanceType::kLinear) {
            auto* lg = static_cast<sksg::LinearGradient*>(fGradient.get());
            lg->setStartPoint(start_point);
            lg->setEndPoint(end_point);
        } else {
            SkASSERT(instance_type == InstanceType::kRadial);

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

        // TODO: blend, scatter
    }

    const sk_sp<sksg::ShaderEffect> fShaderEffect;
    sk_sp<sksg::Gradient>           fGradient;

    InstanceType              fInstanceType = InstanceType::kNone;

    ColorValue  fStartColor,
                fEndColor;
    Vec2Value   fStartPoint = {0,0},
                fEndPoint   = {0,0};
    ScalarValue fBlend   = 0,
                fScatter = 0,
                fShape   = 0; // 1 -> linear, 7 -> radial (?!)
};

}  // namespace

sk_sp<sksg::RenderNode> EffectBuilder::attachGradientEffect(const skjson::ArrayValue& jprops,
                                                            sk_sp<sksg::RenderNode> layer) const {
    return fBuilder->attachDiscardableAdapter<GradientRampEffectAdapter>(jprops,
                                                                         std::move(layer),
                                                                         fBuilder);
}

} // namespace internal
} // namespace skottie
