/*
 * 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/SkSGColorFilter.h"

#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkMatrix.h"
#include "include/private/base/SkAssert.h"
#include "modules/sksg/include/SkSGPaint.h"
#include "modules/sksg/include/SkSGRenderNode.h"
#include "src/core/SkColorData.h"

#include <cmath>
#include <cstddef>
#include <cstdint>
#include <utility>

enum class SkBlendMode;
struct SkPoint;

namespace sksg {

ColorFilter::ColorFilter(sk_sp<RenderNode> child)
    : INHERITED(std::move(child)) {}

void ColorFilter::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
    const auto local_ctx = ScopedRenderContext(canvas, ctx).modulateColorFilter(fColorFilter);

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

const RenderNode* ColorFilter::onNodeAt(const SkPoint& p) const {
    // TODO: we likely need to do something more sophisticated than delegate to descendants here.
    return this->INHERITED::onNodeAt(p);
}

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

    fColorFilter = this->onRevalidateFilter();

    return this->INHERITED::onRevalidate(ic, ctm);
}

sk_sp<ExternalColorFilter> ExternalColorFilter::Make(sk_sp<RenderNode> child) {
    return child ? sk_sp<ExternalColorFilter>(new ExternalColorFilter(std::move(child)))
                 : nullptr;
}

ExternalColorFilter::ExternalColorFilter(sk_sp<RenderNode> child) : INHERITED(std::move(child)) {}

ExternalColorFilter::~ExternalColorFilter() = default;

void ExternalColorFilter::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
    auto local_ctx = ScopedRenderContext(canvas, ctx).modulateColorFilter(fColorFilter);

    if (fCoverage == Coverage::kBoundingBox) {
        // For bounding box coverage, use a layer clipped to the content bounding box.
        canvas->save();
        canvas->clipRect(this->bounds(), /*doAntiAlias=*/ true);
        local_ctx.setIsolation(this->bounds(), canvas->getTotalMatrix(), /*do_isolate=*/ true);
    }

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

sk_sp<ModeColorFilter> ModeColorFilter::Make(sk_sp<RenderNode> child, sk_sp<Color> color,
                                             SkBlendMode mode) {
    return (child && color) ? sk_sp<ModeColorFilter>(new ModeColorFilter(std::move(child),
                                                                         std::move(color), mode))
                            : nullptr;
}

ModeColorFilter::ModeColorFilter(sk_sp<RenderNode> child, sk_sp<Color> color, SkBlendMode mode)
    : INHERITED(std::move(child))
    , fColor(std::move(color))
    , fMode(mode) {
    this->observeInval(fColor);
}

ModeColorFilter::~ModeColorFilter() {
    this->unobserveInval(fColor);
}

sk_sp<SkColorFilter> ModeColorFilter::onRevalidateFilter() {
    fColor->revalidate(nullptr, SkMatrix::I());
    return SkColorFilters::Blend(fColor->getColor(), fMode);
}

sk_sp<GradientColorFilter> GradientColorFilter::Make(sk_sp<RenderNode> child,
                                                     sk_sp<Color> c0, sk_sp<Color> c1) {
    return Make(std::move(child), { std::move(c0), std::move(c1) });
}

sk_sp<GradientColorFilter> GradientColorFilter::Make(sk_sp<RenderNode> child,
                                                     std::vector<sk_sp<Color>> colors) {
    return (child && colors.size() > 1)
        ? sk_sp<GradientColorFilter>(new GradientColorFilter(std::move(child), std::move(colors)))
        : nullptr;
}

GradientColorFilter::GradientColorFilter(sk_sp<RenderNode> child, std::vector<sk_sp<Color>> colors)
    : INHERITED(std::move(child))
    , fColors(std::move(colors)) {
    for (const auto& color : fColors) {
        this->observeInval(color);
    }
}

GradientColorFilter::~GradientColorFilter() {
    for (const auto& color : fColors) {
        this->unobserveInval(color);
    }
}

namespace  {

sk_sp<SkColorFilter> Make2ColorGradient(const sk_sp<Color>& color0, const sk_sp<Color>& color1) {
    const auto c0 = SkColor4f::FromColor(color0->getColor()),
               c1 = SkColor4f::FromColor(color1->getColor());

    const auto dR = c1.fR - c0.fR,
               dG = c1.fG - c0.fG,
               dB = c1.fB - c0.fB;

    // A 2-color gradient can be expressed as a color matrix (and combined with the luminance
    // calculation).  First, the luminance:
    //
    //   L = [r,g,b] . [kR,kG,kB]
    //
    // We can compute it using a color matrix (result stored in R):
    //
    //   | kR, kG, kB,  0,  0 |    r' = L
    //   |  0,  0,  0,  0,  0 |    g' = 0
    //   |  0,  0,  0,  0,  0 |    b' = 0
    //   |  0,  0,  0,  1,  0 |    a' = a
    //
    // Then we want to interpolate component-wise, based on L:
    //
    //   r' = c0.r + (c1.r - c0.r) * L = c0.r + dR*L
    //   g' = c0.g + (c1.g - c0.g) * L = c0.g + dG*L
    //   b' = c0.b + (c1.b - c0.b) * L = c0.b + dB*L
    //   a' = a
    //
    // This can be expressed as another color matrix (when L is stored in R):
    //
    //  | dR,  0,  0,  0, c0.r |
    //  | dG,  0,  0,  0, c0.g |
    //  | dB,  0,  0,  0, c0.b |
    //  |  0,  0,  0,  1,    0 |
    //
    // Composing these two, we get the total tint matrix:

    const float tint_matrix[] = {
        dR*SK_LUM_COEFF_R, dR*SK_LUM_COEFF_G, dR*SK_LUM_COEFF_B, 0, c0.fR,
        dG*SK_LUM_COEFF_R, dG*SK_LUM_COEFF_G, dG*SK_LUM_COEFF_B, 0, c0.fG,
        dB*SK_LUM_COEFF_R, dB*SK_LUM_COEFF_G, dB*SK_LUM_COEFF_B, 0, c0.fB,
        0,                 0,                 0,                 1, 0,
    };

    return SkColorFilters::Matrix(tint_matrix);
}

sk_sp<SkColorFilter> MakeNColorGradient(const std::vector<sk_sp<Color>>& colors) {
    // For N colors, we build a gradient color table.
    uint8_t rTable[256], gTable[256], bTable[256];

    SkASSERT(colors.size() > 2);
    const auto span_count = colors.size() - 1;

    size_t span_start = 0;
    for (size_t i = 0; i < span_count; ++i) {
        const auto span_stop = static_cast<size_t>(std::round((i + 1) * 255.0f / span_count)),
                   span_size = span_stop - span_start;
        if (span_start > span_stop) {
            // Degenerate case.
            continue;
        }
        SkASSERT(span_stop <= 255);

        // Fill the gradient in [span_start,span_stop] -> [c0,c1]
        const SkColor c0 = colors[i    ]->getColor(),
                      c1 = colors[i + 1]->getColor();
        float r = SkColorGetR(c0),
              g = SkColorGetG(c0),
              b = SkColorGetB(c0);
        const float dR = (SkColorGetR(c1) - r) / span_size,
                    dG = (SkColorGetG(c1) - g) / span_size,
                    dB = (SkColorGetB(c1) - b) / span_size;

        for (size_t j = span_start; j <= span_stop; ++j) {
            rTable[j] = static_cast<uint8_t>(std::round(r));
            gTable[j] = static_cast<uint8_t>(std::round(g));
            bTable[j] = static_cast<uint8_t>(std::round(b));
            r += dR;
            g += dG;
            b += dB;
        }

        // Ensure we always advance.
        span_start = span_stop + 1;
    }
    SkASSERT(span_start == 256);

    const float luminance_matrix[] = {
        SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B,  0,  0,  // r' = L
        SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B,  0,  0,  // g' = L
        SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B,  0,  0,  // b' = L
                     0,              0,              0,  1,  0,  // a' = a
    };

    return SkColorFilters::TableARGB(nullptr, rTable, gTable, bTable)
            ->makeComposed(SkColorFilters::Matrix(luminance_matrix));
}

} // namespace

sk_sp<SkColorFilter> GradientColorFilter::onRevalidateFilter() {
    for (const auto& color : fColors) {
        color->revalidate(nullptr, SkMatrix::I());
    }

    if (fWeight <= 0) {
        return nullptr;
    }

    SkASSERT(fColors.size() > 1);
    auto gradientCF = (fColors.size() > 2) ? MakeNColorGradient(fColors)
                                           : Make2ColorGradient(fColors[0], fColors[1]);

    return SkColorFilters::Lerp(fWeight, nullptr, std::move(gradientCF));
}

} // namespace sksg
