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

#include <algorithm>

#include "DOMSVGAnimatedLength.h"
#include "DOMSVGAnimatedNumberList.h"
#include "SVGAnimatedEnumeration.h"
#include "SVGAnimatedNumberPair.h"
#include "SVGAnimatedString.h"
#include "SVGNumberList.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/SVGContentUtils.h"
#include "mozilla/SVGFilterInstance.h"
#include "mozilla/dom/SVGComponentTransferFunctionElement.h"
#include "mozilla/dom/SVGElement.h"
#include "mozilla/dom/SVGFEDistantLightElement.h"
#include "mozilla/dom/SVGFEFuncAElementBinding.h"
#include "mozilla/dom/SVGFEFuncBElementBinding.h"
#include "mozilla/dom/SVGFEFuncGElementBinding.h"
#include "mozilla/dom/SVGFEFuncRElementBinding.h"
#include "mozilla/dom/SVGFEPointLightElement.h"
#include "mozilla/dom/SVGFESpotLightElement.h"
#include "mozilla/dom/SVGFilterElement.h"
#include "mozilla/dom/SVGLengthBinding.h"
#include "nsCOMPtr.h"
#include "nsGkAtoms.h"
#include "nsIFrame.h"
#include "nsLayoutUtils.h"

#if defined(XP_WIN)
// Prevent Windows redefining LoadImage
#  undef LoadImage
#endif

using namespace mozilla::gfx;

namespace mozilla::dom {

//--------------------Filter Element Base Class-----------------------

SVGElement::LengthInfo SVGFilterPrimitiveElement::sLengthInfo[4] = {
    {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE,
     SVGLength::Axis::X},
    {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE,
     SVGLength::Axis::Y},
    {nsGkAtoms::width, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE,
     SVGLength::Axis::X},
    {nsGkAtoms::height, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE,
     SVGLength::Axis::Y}};

//----------------------------------------------------------------------
// Implementation

void SVGFilterPrimitiveElement::GetSourceImageNames(
    nsTArray<SVGStringInfo>& aSources) {}

bool SVGFilterPrimitiveElement::OutputIsTainted(
    const nsTArray<bool>& aInputsAreTainted,
    nsIPrincipal* aReferencePrincipal) {
  // This is the default implementation for OutputIsTainted.
  // Our output is tainted if we have at least one tainted input.
  return aInputsAreTainted.Contains(true);
}

bool SVGFilterPrimitiveElement::AttributeAffectsRendering(
    int32_t aNameSpaceID, nsAtom* aAttribute) const {
  return aNameSpaceID == kNameSpaceID_None &&
         (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y ||
          aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
          aAttribute == nsGkAtoms::result);
}

already_AddRefed<DOMSVGAnimatedLength> SVGFilterPrimitiveElement::X() {
  return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this);
}

already_AddRefed<DOMSVGAnimatedLength> SVGFilterPrimitiveElement::Y() {
  return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this);
}

already_AddRefed<DOMSVGAnimatedLength> SVGFilterPrimitiveElement::Width() {
  return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this);
}

already_AddRefed<DOMSVGAnimatedLength> SVGFilterPrimitiveElement::Height() {
  return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this);
}

already_AddRefed<DOMSVGAnimatedString> SVGFilterPrimitiveElement::Result() {
  return GetResultImageName().ToDOMAnimatedString(this);
}

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

bool SVGFilterPrimitiveElement::StyleIsSetToSRGB() {
  nsIFrame* frame = GetPrimaryFrame();
  if (!frame) return false;

  ComputedStyle* style = frame->Style();
  return style->StyleSVG()->mColorInterpolationFilters ==
         StyleColorInterpolation::Srgb;
}

/* virtual */
bool SVGFilterPrimitiveElement::HasValidDimensions() const {
  return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() ||
          mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) &&
         (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() ||
          mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0);
}

Size SVGFilterPrimitiveElement::GetKernelUnitLength(
    SVGFilterInstance* aInstance, SVGAnimatedNumberPair* aKernelUnitLength) {
  if (!aKernelUnitLength->IsExplicitlySet()) {
    return Size(aInstance->GetPrimitiveUserSpaceUnitValue(SVGLength::Axis::X),
                aInstance->GetPrimitiveUserSpaceUnitValue(SVGLength::Axis::Y));
  }

  float kernelX =
      aInstance->GetPrimitiveNumber(SVGLength::Axis::X, aKernelUnitLength,
                                    SVGAnimatedNumberPairWhichOne::First);
  if (kernelX <= 0.0f) {
    kernelX = aInstance->GetPrimitiveUserSpaceUnitValue(SVGLength::Axis::X);
  } else {
    kernelX = std::min(kernelX, float(kReasonableSurfaceSize));
  }
  float kernelY =
      aInstance->GetPrimitiveNumber(SVGLength::Axis::Y, aKernelUnitLength,
                                    SVGAnimatedNumberPairWhichOne::Second);
  if (kernelY <= 0.0f) {
    kernelY = aInstance->GetPrimitiveUserSpaceUnitValue(SVGLength::Axis::Y);
  } else {
    kernelY = std::min(kernelY, float(kReasonableSurfaceSize));
  }
  return Size(kernelX, kernelY);
}

SVGElement::LengthAttributesInfo SVGFilterPrimitiveElement::GetLengthInfo() {
  return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
                              std::size(sLengthInfo));
}

SVGElement::NumberListInfo
    SVGComponentTransferFunctionElement::sNumberListInfo[1] = {
        {nsGkAtoms::tableValues}};

SVGElement::NumberInfo SVGComponentTransferFunctionElement::sNumberInfo[5] = {
    {nsGkAtoms::slope, 1},
    {nsGkAtoms::intercept, 0},
    {nsGkAtoms::amplitude, 1},
    {nsGkAtoms::exponent, 1},
    {nsGkAtoms::offset, 0}};

SVGEnumMapping SVGComponentTransferFunctionElement::sTypeMap[] = {
    {nsGkAtoms::identity, uint8_t(SVGFEComponentTransferType::Identity)},
    {nsGkAtoms::table, uint8_t(SVGFEComponentTransferType::Table)},
    {nsGkAtoms::discrete, uint8_t(SVGFEComponentTransferType::Discrete)},
    {nsGkAtoms::linear, uint8_t(SVGFEComponentTransferType::Linear)},
    {nsGkAtoms::gamma, uint8_t(SVGFEComponentTransferType::Gamma)},
    {nullptr, 0}};

SVGElement::EnumInfo SVGComponentTransferFunctionElement::sEnumInfo[1] = {
    {nsGkAtoms::type, sTypeMap, uint8_t(SVGFEComponentTransferType::Identity)}};

//----------------------------------------------------------------------
// nsSVGFilterPrimitiveChildElement methods

bool SVGComponentTransferFunctionElement::AttributeAffectsRendering(
    int32_t aNameSpaceID, nsAtom* aAttribute) const {
  return aNameSpaceID == kNameSpaceID_None &&
         (aAttribute == nsGkAtoms::tableValues ||
          aAttribute == nsGkAtoms::slope ||
          aAttribute == nsGkAtoms::intercept ||
          aAttribute == nsGkAtoms::amplitude ||
          aAttribute == nsGkAtoms::exponent ||
          aAttribute == nsGkAtoms::offset || aAttribute == nsGkAtoms::type);
}

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

already_AddRefed<DOMSVGAnimatedEnumeration>
SVGComponentTransferFunctionElement::Type() {
  return mEnumAttributes[TYPE].ToDOMAnimatedEnum(this);
}

already_AddRefed<DOMSVGAnimatedNumberList>
SVGComponentTransferFunctionElement::TableValues() {
  return DOMSVGAnimatedNumberList::GetDOMWrapper(
      &mNumberListAttributes[TABLEVALUES], this, TABLEVALUES);
}

already_AddRefed<DOMSVGAnimatedNumber>
SVGComponentTransferFunctionElement::Slope() {
  return mNumberAttributes[SLOPE].ToDOMAnimatedNumber(this);
}

already_AddRefed<DOMSVGAnimatedNumber>
SVGComponentTransferFunctionElement::Intercept() {
  return mNumberAttributes[INTERCEPT].ToDOMAnimatedNumber(this);
}

already_AddRefed<DOMSVGAnimatedNumber>
SVGComponentTransferFunctionElement::Amplitude() {
  return mNumberAttributes[AMPLITUDE].ToDOMAnimatedNumber(this);
}

already_AddRefed<DOMSVGAnimatedNumber>
SVGComponentTransferFunctionElement::Exponent() {
  return mNumberAttributes[EXPONENT].ToDOMAnimatedNumber(this);
}

already_AddRefed<DOMSVGAnimatedNumber>
SVGComponentTransferFunctionElement::Offset() {
  return mNumberAttributes[OFFSET].ToDOMAnimatedNumber(this);
}

void SVGComponentTransferFunctionElement::ComputeAttributes(
    int32_t aChannel, ComponentTransferAttributes& aAttributes) {
  SVGFEComponentTransferType type =
      SVGFEComponentTransferType(mEnumAttributes[TYPE].GetAnimValue());

  float slope, intercept, amplitude, exponent, offset;
  GetAnimatedNumberValues(&slope, &intercept, &amplitude, &exponent, &offset,
                          nullptr);

  const SVGNumberList& tableValues =
      mNumberListAttributes[TABLEVALUES].GetAnimValue();

  aAttributes.mTypes[aChannel] = type;
  switch (type) {
    case SVGFEComponentTransferType::Linear: {
      aAttributes.mValues[aChannel].SetLength(2);
      aAttributes.mValues[aChannel][kComponentTransferSlopeIndex] = slope;
      aAttributes.mValues[aChannel][kComponentTransferInterceptIndex] =
          intercept;
      break;
    }
    case SVGFEComponentTransferType::Gamma: {
      aAttributes.mValues[aChannel].SetLength(3);
      aAttributes.mValues[aChannel][kComponentTransferAmplitudeIndex] =
          amplitude;
      aAttributes.mValues[aChannel][kComponentTransferExponentIndex] = exponent;
      aAttributes.mValues[aChannel][kComponentTransferOffsetIndex] = offset;
      break;
    }
    case SVGFEComponentTransferType::Discrete:
    case SVGFEComponentTransferType::Table: {
      if (!tableValues.IsEmpty()) {
        aAttributes.mValues[aChannel].AppendElements(&tableValues[0],
                                                     tableValues.Length());
      }
      break;
    }
    default:
      break;
  }
}

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

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

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

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

/* virtual */
JSObject* SVGFEFuncRElement::WrapNode(JSContext* aCx,
                                      JS::Handle<JSObject*> aGivenProto) {
  return SVGFEFuncRElement_Binding::Wrap(aCx, this, aGivenProto);
}

}  // namespace mozilla::dom

NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncR)

namespace mozilla::dom {

NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncRElement)

/* virtual */
JSObject* SVGFEFuncGElement::WrapNode(JSContext* aCx,
                                      JS::Handle<JSObject*> aGivenProto) {
  return SVGFEFuncGElement_Binding::Wrap(aCx, this, aGivenProto);
}

}  // namespace mozilla::dom

NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncG)

namespace mozilla::dom {

NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncGElement)

/* virtual */
JSObject* SVGFEFuncBElement::WrapNode(JSContext* aCx,
                                      JS::Handle<JSObject*> aGivenProto) {
  return SVGFEFuncBElement_Binding::Wrap(aCx, this, aGivenProto);
}

}  // namespace mozilla::dom

NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncB)

namespace mozilla::dom {

NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncBElement)

/* virtual */
JSObject* SVGFEFuncAElement::WrapNode(JSContext* aCx,
                                      JS::Handle<JSObject*> aGivenProto) {
  return SVGFEFuncAElement_Binding::Wrap(aCx, this, aGivenProto);
}

}  // namespace mozilla::dom

NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncA)

namespace mozilla::dom {

NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncAElement)

//--------------------------------------------------------------------
//
SVGElement::NumberInfo SVGFELightingElement::sNumberInfo[4] = {
    {nsGkAtoms::surfaceScale, 1},
    {nsGkAtoms::diffuseConstant, 1},
    {nsGkAtoms::specularConstant, 1},
    {nsGkAtoms::specularExponent, 1}};

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

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

//----------------------------------------------------------------------
// Implementation

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

LightType SVGFELightingElement::ComputeLightAttributes(
    SVGFilterInstance* aInstance, nsTArray<float>& aFloatAttributes) {
  // find specified light
  for (nsCOMPtr<nsIContent> child = nsINode::GetFirstChild(); child;
       child = child->GetNextSibling()) {
    if (child->IsAnyOfSVGElements(nsGkAtoms::feDistantLight,
                                  nsGkAtoms::fePointLight,
                                  nsGkAtoms::feSpotLight)) {
      return static_cast<SVGFELightElement*>(child.get())
          ->ComputeLightAttributes(aInstance, aFloatAttributes);
    }
  }

  return LightType::None;
}

bool SVGFELightingElement::AddLightingAttributes(
    mozilla::gfx::LightingAttributes* aAttributes,
    SVGFilterInstance* aInstance) {
  const auto* frame = GetPrimaryFrame();
  if (!frame) {
    return false;
  }

  const nsStyleSVGReset* styleSVGReset = frame->Style()->StyleSVGReset();
  sRGBColor color(
      sRGBColor::FromABGR(styleSVGReset->mLightingColor.CalcColor(frame)));
  color.a = 1.f;
  float surfaceScale = mNumberAttributes[SURFACE_SCALE].GetAnimValue();
  Size kernelUnitLength = GetKernelUnitLength(
      aInstance, &mNumberPairAttributes[KERNEL_UNIT_LENGTH]);

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

  aAttributes->mLightType =
      ComputeLightAttributes(aInstance, aAttributes->mLightValues);
  aAttributes->mSurfaceScale = surfaceScale;
  aAttributes->mKernelUnitLength = kernelUnitLength;
  aAttributes->mColor = color;

  return true;
}

bool SVGFELightingElement::OutputIsTainted(
    const nsTArray<bool>& aInputsAreTainted,
    nsIPrincipal* aReferencePrincipal) {
  if (const auto* frame = GetPrimaryFrame()) {
    if (frame->Style()
            ->StyleSVGReset()
            ->mLightingColor.DependsOnCurrentColor()) {
      return true;
    }
  }

  return SVGFELightingElementBase::OutputIsTainted(aInputsAreTainted,
                                                   aReferencePrincipal);
}

bool SVGFELightingElement::AttributeAffectsRendering(int32_t aNameSpaceID,
                                                     nsAtom* aAttribute) const {
  return SVGFELightingElementBase::AttributeAffectsRendering(aNameSpaceID,
                                                             aAttribute) ||
         (aNameSpaceID == kNameSpaceID_None &&
          (aAttribute == nsGkAtoms::in ||
           aAttribute == nsGkAtoms::surfaceScale ||
           aAttribute == nsGkAtoms::kernelUnitLength));
}

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

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

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

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

}  // namespace mozilla::dom
