/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/dom/SVGFEConvolveMatrixElement.h"

#include <numeric>

#include "DOMSVGAnimatedNumberList.h"
#include "mozilla/SVGFilterInstance.h"
#include "mozilla/SVGUtils.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/SVGFEConvolveMatrixElementBinding.h"

NS_IMPL_NS_NEW_SVG_ELEMENT(FEConvolveMatrix)

using namespace mozilla::gfx;

namespace mozilla::dom {

JSObject* SVGFEConvolveMatrixElement::WrapNode(
    JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
  return SVGFEConvolveMatrixElement_Binding::Wrap(aCx, this, aGivenProto);
}

SVGElement::NumberInfo SVGFEConvolveMatrixElement::sNumberInfo[2] = {
    {nsGkAtoms::divisor, 1}, {nsGkAtoms::bias, 0}};

SVGElement::NumberPairInfo SVGFEConvolveMatrixElement::sNumberPairInfo[1] = {
    {nsGkAtoms::kernelUnitLength, 0}};

SVGElement::IntegerInfo SVGFEConvolveMatrixElement::sIntegerInfo[2] = {
    {nsGkAtoms::targetX, 0}, {nsGkAtoms::targetY, 0}};

SVGElement::IntegerPairInfo SVGFEConvolveMatrixElement::sIntegerPairInfo[1] = {
    {nsGkAtoms::order, 3}};

SVGElement::BooleanInfo SVGFEConvolveMatrixElement::sBooleanInfo[1] = {
    {nsGkAtoms::preserveAlpha, false}};

SVGEnumMapping SVGFEConvolveMatrixElement::sEdgeModeMap[] = {
    {nsGkAtoms::duplicate, uint8_t(SVGEdgeMode::Duplicate)},
    {nsGkAtoms::wrap, uint8_t(SVGEdgeMode::Wrap)},
    {nsGkAtoms::none, uint8_t(SVGEdgeMode::None)},
    {nullptr, 0}};

SVGElement::EnumInfo SVGFEConvolveMatrixElement::sEnumInfo[1] = {
    {nsGkAtoms::edgeMode, sEdgeModeMap, uint8_t(SVGEdgeMode::Duplicate)}};

SVGElement::StringInfo SVGFEConvolveMatrixElement::sStringInfo[2] = {
    {nsGkAtoms::result, kNameSpaceID_None, true},
    {nsGkAtoms::in, kNameSpaceID_None, true}};

SVGElement::NumberListInfo SVGFEConvolveMatrixElement::sNumberListInfo[1] = {
    {nsGkAtoms::kernelMatrix}};

//----------------------------------------------------------------------
// nsINode methods

NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEConvolveMatrixElement)

//----------------------------------------------------------------------

already_AddRefed<DOMSVGAnimatedString> SVGFEConvolveMatrixElement::In1() {
  return mStringAttributes[IN1].ToDOMAnimatedString(this);
}

already_AddRefed<DOMSVGAnimatedInteger> SVGFEConvolveMatrixElement::OrderX() {
  return mIntegerPairAttributes[ORDER].ToDOMAnimatedInteger(
      SVGAnimatedIntegerPairWhichOne::First, this);
}

already_AddRefed<DOMSVGAnimatedInteger> SVGFEConvolveMatrixElement::OrderY() {
  return mIntegerPairAttributes[ORDER].ToDOMAnimatedInteger(
      SVGAnimatedIntegerPairWhichOne::Second, this);
}

already_AddRefed<DOMSVGAnimatedNumberList>
SVGFEConvolveMatrixElement::KernelMatrix() {
  return DOMSVGAnimatedNumberList::GetDOMWrapper(
      &mNumberListAttributes[KERNELMATRIX], this, KERNELMATRIX);
}

already_AddRefed<DOMSVGAnimatedInteger> SVGFEConvolveMatrixElement::TargetX() {
  return mIntegerAttributes[TARGET_X].ToDOMAnimatedInteger(this);
}

already_AddRefed<DOMSVGAnimatedInteger> SVGFEConvolveMatrixElement::TargetY() {
  return mIntegerAttributes[TARGET_Y].ToDOMAnimatedInteger(this);
}

already_AddRefed<DOMSVGAnimatedEnumeration>
SVGFEConvolveMatrixElement::EdgeMode() {
  return mEnumAttributes[EDGEMODE].ToDOMAnimatedEnum(this);
}

already_AddRefed<DOMSVGAnimatedBoolean>
SVGFEConvolveMatrixElement::PreserveAlpha() {
  return mBooleanAttributes[PRESERVEALPHA].ToDOMAnimatedBoolean(this);
}

already_AddRefed<DOMSVGAnimatedNumber> SVGFEConvolveMatrixElement::Divisor() {
  return mNumberAttributes[DIVISOR].ToDOMAnimatedNumber(this);
}

already_AddRefed<DOMSVGAnimatedNumber> SVGFEConvolveMatrixElement::Bias() {
  return mNumberAttributes[BIAS].ToDOMAnimatedNumber(this);
}

already_AddRefed<DOMSVGAnimatedNumber>
SVGFEConvolveMatrixElement::KernelUnitLengthX() {
  return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber(
      SVGAnimatedNumberPairWhichOne::First, this);
}

already_AddRefed<DOMSVGAnimatedNumber>
SVGFEConvolveMatrixElement::KernelUnitLengthY() {
  return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber(
      SVGAnimatedNumberPairWhichOne::Second, this);
}

void SVGFEConvolveMatrixElement::GetSourceImageNames(
    nsTArray<SVGStringInfo>& aSources) {
  aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this));
}

FilterPrimitiveDescription SVGFEConvolveMatrixElement::GetPrimitiveDescription(
    SVGFilterInstance* aInstance, const IntRect& aFilterSubregion,
    const nsTArray<bool>& aInputsAreTainted,
    nsTArray<RefPtr<SourceSurface>>& aInputImages) {
  FilterPrimitiveDescription failureDescription;

  const SVGNumberList& kernelMatrix =
      mNumberListAttributes[KERNELMATRIX].GetAnimValue();
  uint32_t kmLength = kernelMatrix.Length();

  int32_t orderX = mIntegerPairAttributes[ORDER].GetAnimValue(
      SVGAnimatedIntegerPairWhichOne::First);
  int32_t orderY = mIntegerPairAttributes[ORDER].GetAnimValue(
      SVGAnimatedIntegerPairWhichOne::Second);

  if (orderX <= 0 || orderY <= 0 ||
      static_cast<uint32_t>(orderX * orderY) != kmLength) {
    return failureDescription;
  }

  int32_t targetX, targetY;
  GetAnimatedIntegerValues(&targetX, &targetY, nullptr);

  if (mIntegerAttributes[TARGET_X].IsExplicitlySet()) {
    if (targetX < 0 || targetX >= orderX) return failureDescription;
  } else {
    targetX = orderX / 2;
  }
  if (mIntegerAttributes[TARGET_Y].IsExplicitlySet()) {
    if (targetY < 0 || targetY >= orderY) return failureDescription;
  } else {
    targetY = orderY / 2;
  }

  if (orderX > kReasonableSurfaceSize || orderY > kReasonableSurfaceSize)
    return failureDescription;

  float divisor;
  if (mNumberAttributes[DIVISOR].IsExplicitlySet()) {
    divisor = mNumberAttributes[DIVISOR].GetAnimValue();
  }
  if (!mNumberAttributes[DIVISOR].IsExplicitlySet() || divisor == 0) {
    divisor = std::accumulate(kernelMatrix.begin(), kernelMatrix.end(), 0.0f);
    if (divisor == 0) {
      divisor = 1;
    }
  }

  SVGEdgeMode edgeMode = SVGEdgeMode(mEnumAttributes[EDGEMODE].GetAnimValue());
  bool preserveAlpha = mBooleanAttributes[PRESERVEALPHA].GetAnimValue();
  float bias = mNumberAttributes[BIAS].GetAnimValue();

  Size kernelUnitLength = GetKernelUnitLength(
      aInstance, &mNumberPairAttributes[KERNEL_UNIT_LENGTH]);

  MOZ_ASSERT(kernelUnitLength.width > 0.0f && kernelUnitLength.height > 0.0f,
             "Expecting positive kernelUnitLength values");

  ConvolveMatrixAttributes atts;
  atts.mKernelSize = IntSize(orderX, orderY);
  atts.mKernelMatrix.AppendElements(&kernelMatrix[0], kmLength);
  atts.mDivisor = divisor;
  atts.mBias = bias;
  atts.mTarget = IntPoint(targetX, targetY);
  atts.mEdgeMode = edgeMode;
  atts.mKernelUnitLength = kernelUnitLength;
  atts.mPreserveAlpha = preserveAlpha;

  return FilterPrimitiveDescription(AsVariant(std::move(atts)));
}

bool SVGFEConvolveMatrixElement::AttributeAffectsRendering(
    int32_t aNameSpaceID, nsAtom* aAttribute) const {
  return SVGFEConvolveMatrixElementBase::AttributeAffectsRendering(
             aNameSpaceID, aAttribute) ||
         (aNameSpaceID == kNameSpaceID_None &&
          (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::divisor ||
           aAttribute == nsGkAtoms::bias ||
           aAttribute == nsGkAtoms::kernelUnitLength ||
           aAttribute == nsGkAtoms::targetX ||
           aAttribute == nsGkAtoms::targetY || aAttribute == nsGkAtoms::order ||
           aAttribute == nsGkAtoms::preserveAlpha ||
           aAttribute == nsGkAtoms::edgeMode ||
           aAttribute == nsGkAtoms::kernelMatrix));
}

nsresult SVGFEConvolveMatrixElement::BindToTree(BindContext& aCtx,
                                                nsINode& aParent) {
  if (aCtx.InComposedDoc()) {
    aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feConvolveMatrix);
  }

  return SVGFEConvolveMatrixElementBase::BindToTree(aCtx, aParent);
}

//----------------------------------------------------------------------
// SVGElement methods

SVGElement::NumberAttributesInfo SVGFEConvolveMatrixElement::GetNumberInfo() {
  return NumberAttributesInfo(mNumberAttributes, sNumberInfo,
                              std::size(sNumberInfo));
}

SVGElement::NumberPairAttributesInfo
SVGFEConvolveMatrixElement::GetNumberPairInfo() {
  return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo,
                                  std::size(sNumberPairInfo));
}

SVGElement::IntegerAttributesInfo SVGFEConvolveMatrixElement::GetIntegerInfo() {
  return IntegerAttributesInfo(mIntegerAttributes, sIntegerInfo,
                               std::size(sIntegerInfo));
}

SVGElement::IntegerPairAttributesInfo
SVGFEConvolveMatrixElement::GetIntegerPairInfo() {
  return IntegerPairAttributesInfo(mIntegerPairAttributes, sIntegerPairInfo,
                                   std::size(sIntegerPairInfo));
}

SVGElement::BooleanAttributesInfo SVGFEConvolveMatrixElement::GetBooleanInfo() {
  return BooleanAttributesInfo(mBooleanAttributes, sBooleanInfo,
                               std::size(sBooleanInfo));
}

SVGElement::EnumAttributesInfo SVGFEConvolveMatrixElement::GetEnumInfo() {
  return EnumAttributesInfo(mEnumAttributes, sEnumInfo, std::size(sEnumInfo));
}

SVGElement::StringAttributesInfo SVGFEConvolveMatrixElement::GetStringInfo() {
  return StringAttributesInfo(mStringAttributes, sStringInfo,
                              std::size(sStringInfo));
}

SVGElement::NumberListAttributesInfo
SVGFEConvolveMatrixElement::GetNumberListInfo() {
  return NumberListAttributesInfo(mNumberListAttributes, sNumberListInfo,
                                  std::size(sNumberListInfo));
}

}  // namespace mozilla::dom
