/*
 * 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/SkColorFilter.h"
#include "include/effects/SkImageFilters.h"
#include "include/private/SkColorData.h"
#include "modules/svg/include/SkSVGAttributeParser.h"
#include "modules/svg/include/SkSVGFeColorMatrix.h"
#include "modules/svg/include/SkSVGFilterContext.h"
#include "modules/svg/include/SkSVGRenderContext.h"
#include "modules/svg/include/SkSVGValue.h"

bool SkSVGFeColorMatrix::parseAndSetAttribute(const char* name, const char* value) {
    return INHERITED::parseAndSetAttribute(name, value) ||
           this->setType(
                   SkSVGAttributeParser::parse<SkSVGFeColorMatrixType>("type", name, value)) ||
           this->setValues(
                   SkSVGAttributeParser::parse<SkSVGFeColorMatrixValues>("values", name, value));
}

SkColorMatrix SkSVGFeColorMatrix::makeMatrixForType() const {
    if (fValues.empty() && fType != SkSVGFeColorMatrixType::kLuminanceToAlpha) {
        return SkColorMatrix();
    }

    switch (fType) {
        case SkSVGFeColorMatrixType::kMatrix: {
            if (fValues.size() < 20) {
                return SkColorMatrix();
            }
            SkColorMatrix m;
            m.setRowMajor(this->fValues.begin());
            return m;
        }
        case SkSVGFeColorMatrixType::kSaturate:
            return MakeSaturate(!fValues.empty() ? fValues[0] : 1);
        case SkSVGFeColorMatrixType::kHueRotate:
            return MakeHueRotate(!fValues.empty() ? fValues[0] : 0);
        case SkSVGFeColorMatrixType::kLuminanceToAlpha:
            return MakeLuminanceToAlpha();
    }

    SkUNREACHABLE;
}

SkColorMatrix SkSVGFeColorMatrix::MakeSaturate(SkSVGNumberType s) {
    SkColorMatrix m;
    m.setSaturation(s);
    return m;
}

SkColorMatrix SkSVGFeColorMatrix::MakeHueRotate(SkSVGNumberType degrees) {
    const SkScalar theta = SkDegreesToRadians(degrees);
    const SkSVGNumberType c = SkScalarCos(theta);
    const SkSVGNumberType s = SkScalarSin(theta);
    return SkColorMatrix(
        0.213f + c* 0.787f + s*-0.213f,
        0.715f + c*-0.715f + s*-0.715f,
        0.072f + c*-0.072f + s* 0.928f,
        0,
        0,

        0.213f + c*-0.213f + s* 0.143f,
        0.715f + c* 0.285f + s* 0.140f,
        0.072f + c*-0.072f + s*-0.283f,
        0,
        0,

        0.213f + c*-0.213f + s*-0.787f,
        0.715f + c*-0.715f + s* 0.715f,
        0.072f + c* 0.928f + s* 0.072f,
        0,
        0,

        0,0,0,1,0
    );
}

SkColorMatrix SkSVGFeColorMatrix::MakeLuminanceToAlpha() {
    return SkColorMatrix(
        0, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B, 0, 0
    );
}

sk_sp<SkImageFilter> SkSVGFeColorMatrix::onMakeImageFilter(const SkSVGRenderContext& ctx,
                                                           const SkSVGFilterContext& fctx) const {
    return SkImageFilters::ColorFilter(
            SkColorFilters::Matrix(makeMatrixForType()),
            fctx.resolveInput(ctx, this->getIn(), this->resolveColorspace(ctx, fctx)),
            this->resolveFilterSubregion(ctx, fctx));
}

template <> bool SkSVGAttributeParser::parse(SkSVGFeColorMatrixValues* values) {
    SkSVGNumberType value;
    if (!this->parse(&value)) {
        return false;
    }

    values->push_back(value);
    while (true) {
        if (!this->parse(&value) || values->size() >= 20) {
            break;
        }
        values->push_back(value);
    }

    return this->parseEOSToken();
}

template <> bool SkSVGAttributeParser::parse(SkSVGFeColorMatrixType* type) {
    static constexpr std::tuple<const char*, SkSVGFeColorMatrixType> gTypeMap[] = {
            {"matrix", SkSVGFeColorMatrixType::kMatrix},
            {"saturate", SkSVGFeColorMatrixType::kSaturate},
            {"hueRotate", SkSVGFeColorMatrixType::kHueRotate},
            {"luminanceToAlpha", SkSVGFeColorMatrixType::kLuminanceToAlpha},
    };

    return this->parseEnumMap(gTypeMap, type) && this->parseEOSToken();
}
