/*
 * Copyright 2020 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/effects/SkTrimPathEffect.h"
#include "modules/skottie/src/Adapter.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/skottie/src/SkottiePriv.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
#include "modules/sksg/include/SkSGGeometryEffect.h"
#include "modules/sksg/include/SkSGGeometryNode.h"
#include "modules/sksg/include/SkSGMerge.h"
#include "src/utils/SkJSON.h"

#include <algorithm>
#include <cstddef>
#include <utility>
#include <vector>

namespace skottie {
namespace internal {

namespace  {

class TrimEffectAdapter final : public DiscardableAdapterBase<TrimEffectAdapter, sksg::TrimEffect> {
public:
    TrimEffectAdapter(const skjson::ObjectValue& jtrim,
                      const AnimationBuilder& abuilder,
                      sk_sp<sksg::GeometryNode> child)
        : INHERITED(sksg::TrimEffect::Make(std::move(child))) {
        this->bind(abuilder, jtrim["s"], &fStart);
        this->bind(abuilder, jtrim["e"], &fEnd);
        this->bind(abuilder, jtrim["o"], &fOffset);
    }

private:
    void onSync() override {
        // BM semantics: start/end are percentages, offset is "degrees" (?!).
        const auto  start = fStart  / 100,
                      end = fEnd    / 100,
                   offset = fOffset / 360;

        auto startT = std::min(start, end) + offset,
              stopT = std::max(start, end) + offset;
        auto   mode = SkTrimPathEffect::Mode::kNormal;

        if (stopT - startT < 1) {
            startT -= SkScalarFloorToScalar(startT);
            stopT  -= SkScalarFloorToScalar(stopT);

            if (startT > stopT) {
                using std::swap;
                swap(startT, stopT);
                mode = SkTrimPathEffect::Mode::kInverted;
            }
        } else {
            startT = 0;
            stopT  = 1;
        }

        this->node()->setStart(startT);
        this->node()->setStop(stopT);
        this->node()->setMode(mode);
    }

    ScalarValue fStart  =   0,
                fEnd    = 100,
                fOffset =   0;

    using INHERITED = DiscardableAdapterBase<TrimEffectAdapter, sksg::TrimEffect>;
};

} // namespace

std::vector<sk_sp<sksg::GeometryNode>> ShapeBuilder::AttachTrimGeometryEffect(
        const skjson::ObjectValue& jtrim,
        const AnimationBuilder* abuilder,
        std::vector<sk_sp<sksg::GeometryNode>>&& geos) {

    enum class Mode {
        kParallel, // "m": 1 (Trim Multiple Shapes: Simultaneously)
        kSerial,   // "m": 2 (Trim Multiple Shapes: Individually)
    } gModes[] = { Mode::kParallel, Mode::kSerial};

    const auto mode = gModes[std::min<size_t>(ParseDefault<size_t>(jtrim["m"], 1) - 1,
                                            std::size(gModes) - 1)];

    std::vector<sk_sp<sksg::GeometryNode>> inputs;
    if (mode == Mode::kSerial) {
        inputs.push_back(ShapeBuilder::MergeGeometry(std::move(geos), sksg::Merge::Mode::kMerge));
    } else {
        inputs = std::move(geos);
    }

    std::vector<sk_sp<sksg::GeometryNode>> trimmed;
    trimmed.reserve(inputs.size());

    for (const auto& i : inputs) {
        trimmed.push_back(
            abuilder->attachDiscardableAdapter<TrimEffectAdapter>(jtrim, *abuilder, i));
    }

    return trimmed;
}

} // namespace internal
} // namespace skottie
