/*
 * Copyright 2022 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/SkBlendMode.h"
#include "include/core/SkBlender.h"
#include "include/effects/SkRuntimeEffect.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/skottie/src/SkottiePriv.h"
#include "modules/sksg/include/SkSGRenderEffect.h"

namespace skottie::internal {

namespace {

enum CustomBlenders {
    HARDMIX = 17,
};

static sk_sp<SkBlender> hardMix() {
    static SkRuntimeEffect* hardMixEffect = []{
        const char hardMix[] =
            "half4 main(half4 src, half4 dst) {"
                "src.rgb = unpremul(src).rgb + unpremul(dst).rgb;"
                "src.rgb = min(floor(src.rgb), 1) * src.a;"

                "return src + (1 - src.a)*dst;"
            "}"
        ;
        auto result = SkRuntimeEffect::MakeForBlender(SkString(hardMix));
        return result.effect.release();
    }();
    return hardMixEffect->makeBlender(nullptr);
}

static sk_sp<SkBlender> get_blender(const skjson::ObjectValue& jobject,
                                    const AnimationBuilder* abuilder) {
    static constexpr SkBlendMode kBlendModeMap[] = {
        SkBlendMode::kSrcOver,    // 0:'normal'
        SkBlendMode::kMultiply,   // 1:'multiply'
        SkBlendMode::kScreen,     // 2:'screen'
        SkBlendMode::kOverlay,    // 3:'overlay
        SkBlendMode::kDarken,     // 4:'darken'
        SkBlendMode::kLighten,    // 5:'lighten'
        SkBlendMode::kColorDodge, // 6:'color-dodge'
        SkBlendMode::kColorBurn,  // 7:'color-burn'
        SkBlendMode::kHardLight,  // 8:'hard-light'
        SkBlendMode::kSoftLight,  // 9:'soft-light'
        SkBlendMode::kDifference, // 10:'difference'
        SkBlendMode::kExclusion,  // 11:'exclusion'
        SkBlendMode::kHue,        // 12:'hue'
        SkBlendMode::kSaturation, // 13:'saturation'
        SkBlendMode::kColor,      // 14:'color'
        SkBlendMode::kLuminosity, // 15:'luminosity'
        SkBlendMode::kPlus,       // 16:'add'
    };

    const size_t mode = ParseDefault<size_t>(jobject["bm"], 0);

    // Special handling of src-over, so we can detect the trivial/no-fancy-blending case
    // (a null blender is equivalent to src-over).
    if (!mode) {
        return nullptr;
    }

    // Modes that are expressible as SkBlendMode.
    if (mode < std::size(kBlendModeMap)) {
        return SkBlender::Mode(kBlendModeMap[mode]);
    }

    // Modes that require custom blenders.
    switch (mode)
    {
    case HARDMIX:
        return hardMix();
    default:
        break;
    }

    abuilder->log(Logger::Level::kWarning, &jobject, "Unsupported blend mode %zu\n", mode);
    return nullptr;
}

}  // namespace

sk_sp<sksg::RenderNode> AnimationBuilder::attachBlendMode(const skjson::ObjectValue& jobject,
                                                          sk_sp<sksg::RenderNode> child) const {
    if (auto blender = get_blender(jobject, this)) {
        fHasNontrivialBlending = true;
        child = sksg::BlenderEffect::Make(std::move(child), std::move(blender));
    }

    return child;
}

}  // namespace skottie::internal
