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

#include "modules/sksg/include/SkSGMaskEffect.h"

#include "include/core/SkCanvas.h"
#include "include/effects/SkLumaColorFilter.h"

namespace sksg {

static bool is_inverted(sksg::MaskEffect::Mode mode) {
    return static_cast<uint32_t>(mode) & 1;
};

static bool is_luma(sksg::MaskEffect::Mode mode) {
    return static_cast<uint32_t>(mode) & 2;
}

MaskEffect::MaskEffect(sk_sp<RenderNode> child, sk_sp<RenderNode> mask, Mode mode)
    : INHERITED(std::move(child))
    , fMaskNode(std::move(mask))
    , fMaskMode(mode) {
    this->observeInval(fMaskNode);
}

MaskEffect::~MaskEffect() {
    this->unobserveInval(fMaskNode);
}

void MaskEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
    SkAutoCanvasRestore acr(canvas, false);

    // The mask mode covers two independent bits.
    //
    //   - mask source controls how the mask coverage is generated:
    //     * alpha => coverage = mask_alpha
    //     * luma  => coverage = luma(mask_rgb)
    //
    //   - mask type controls how the mask coverage is interpreted:
    //     * normal   => coverage' = coverage
    //     * inverted => coverage' = 1 - coverage

    {
        // Outer layer: mask coverage stored in the alpha channel.
        SkPaint mask_layer_paint;
        if (ctx) {
            // Apply all optional context overrides upfront.
            ctx->modulatePaint(canvas->getTotalMatrix(), &mask_layer_paint);
        }

        RenderContext mask_render_context;
        if (is_luma(fMaskMode)) {
            mask_render_context.fColorFilter = SkLumaColorFilter::Make();
        }

        // TODO: could be an A8 layer?
        canvas->saveLayer(this->bounds(), &mask_layer_paint);
        fMaskNode->render(canvas, &mask_render_context);

        {
            // Inner layer: masked content.
            SkPaint content_layer_paint;
            content_layer_paint.setBlendMode(is_inverted(fMaskMode) ? SkBlendMode::kSrcOut
                                                                    : SkBlendMode::kSrcIn);
            canvas->saveLayer(this->bounds(), &content_layer_paint);

            this->INHERITED::onRender(canvas, nullptr);
        }
    }
}

const RenderNode* MaskEffect::onNodeAt(const SkPoint& p) const {
    const auto mask_hit = (SkToBool(fMaskNode->nodeAt(p)) == !is_inverted(fMaskMode));

    if (!mask_hit) {
        return nullptr;
    }

    return this->INHERITED::onNodeAt(p);
}

SkRect MaskEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
    SkASSERT(this->hasInval());

    const auto maskBounds = fMaskNode->revalidate(ic, ctm);
    auto childBounds = this->INHERITED::onRevalidate(ic, ctm);

    return (is_inverted(fMaskMode) || childBounds.intersect(maskBounds))
        ? childBounds
        : SkRect::MakeEmpty();
}

} // namespace sksg
