/* 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/. */

#ifndef DOM_SVG_SVGPATHDATA_H_
#define DOM_SVG_SVGPATHDATA_H_

#include "mozilla/MemoryReporting.h"
#include "mozilla/ServoStyleConsts.h"
#include "mozilla/dom/SVGElement.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Types.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsIContent.h"
#include "nsINode.h"
#include "nsIWeakReferenceUtils.h"
#include "nsTArray.h"

namespace mozilla {

struct SVGMark;
enum class StyleStrokeLinecap : uint8_t;
namespace dom {
class SVGPathElement;
class SVGPathSegment;
}  // namespace dom

class SVGPathData {
  friend class SVGAnimatedPathSegList;
  friend class SVGPathDataAndInfo;
  friend class SVGPathSegListSMILType;

  using DrawTarget = gfx::DrawTarget;
  using Path = gfx::Path;
  using PathBuilder = gfx::PathBuilder;
  using FillRule = gfx::FillRule;
  using Float = gfx::Float;
  using CapStyle = gfx::CapStyle;

 public:
  SVGPathData() = default;
  ~SVGPathData() = default;

  explicit SVGPathData(const nsACString& aString) {
    SetValueFromString(aString);
  }

  SVGPathData& operator=(const SVGPathData&) = default;
  SVGPathData(const SVGPathData&) = default;
  SVGPathData& operator=(SVGPathData&&) = default;
  SVGPathData(SVGPathData&&) = default;

  // Used by SMILCompositor to check if the cached base val is out of date
  bool operator==(const SVGPathData& rhs) const { return mData == rhs.mData; }

  // Only methods that don't make/permit modification to this list are public.
  // Only our friend classes can access methods that may change us.

  /// This may return an incomplete string on OOM, but that's acceptable.
  void GetValueAsString(nsACString& aValue) const;

  Span<const StylePathCommand> AsSpan() const { return mData._0.AsSpan(); }
  bool IsEmpty() const { return AsSpan().IsEmpty(); }

  const StyleSVGPathData& RawData() const { return mData; }

  static already_AddRefed<dom::SVGPathSegment> GetPathSegmentAtLength(
      dom::SVGPathElement* aPathElement, Span<const StylePathCommand> aPath,
      float aDistance);

  void GetMarkerPositioningData(float aZoom, nsTArray<SVGMark>* aMarks) const;

  static void GetMarkerPositioningData(Span<const StylePathCommand> aPath,
                                       float aZoom, nsTArray<SVGMark>* aMarks);
  /**
   * Returns true, except on OOM, in which case returns false.
   */
  bool GetDistancesFromOriginToEndsOfVisibleSegments(
      FallibleTArray<double>* aOutput) const;

  /**
   * This is identical to the above one but accepts StylePathCommand.
   */
  static bool GetDistancesFromOriginToEndsOfVisibleSegments(
      Span<const StylePathCommand> aPath, FallibleTArray<double>* aOutput);

  /**
   * This returns a path without the extra little line segments that
   * ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps.
   * See the comment for that function for more info on that.
   */
  already_AddRefed<Path> BuildPathForMeasuring(float aZoom) const;

  already_AddRefed<Path> BuildPath(PathBuilder* aBuilder,
                                   StyleStrokeLinecap aStrokeLineCap,
                                   Float aStrokeWidth, float aZoom) const;

  static already_AddRefed<Path> BuildPathForMeasuring(
      Span<const StylePathCommand> aPath, float aZoom);

  /**
   * This function tries to build the path from an array of GenericShapeCommand,
   * which is generated by cbindgen from Rust (see ServoStyleConsts.h).
   * Basically, this is a variant of the above BuildPath() functions.
   * Note: |StylePathCommand| doesn't accept percentage values, so its |aBasis|
   * is empty by default.
   */
  static already_AddRefed<Path> BuildPath(Span<const StylePathCommand> aPath,
                                          PathBuilder* aBuilder,
                                          StyleStrokeLinecap aStrokeLineCap,
                                          Float aStrokeWidth,
                                          const CSSSize& aBasis = {},
                                          const gfx::Point& aOffset = {},
                                          float aZoomFactor = 1.0);
  static already_AddRefed<Path> BuildPath(
      Span<const StyleShapeCommand> aShape, PathBuilder* aBuilder,
      StyleStrokeLinecap aStrokeLineCap, Float aStrokeWidth,
      const CSSSize& aBasis, const gfx::Point& aOffset = gfx::Point(),
      float aZoomFactor = 1.0);

  // memory reporting methods
  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;

 protected:
  nsresult SetValueFromString(const nsACString& aValue);

  void Clear() { mData = {}; }

  StyleSVGPathData& RawData() { return mData; }

  mozilla::StyleSVGPathData mData;
};

/**
 * This SVGPathData subclass is for SVGPathSegListSMILType which needs to
 * have write access to the lists it works with.
 *
 * Instances of this class do not have DOM wrappers that need to be kept in
 * sync, so we can safely expose any protected base class methods required by
 * the SMIL code.
 */
class SVGPathDataAndInfo final : public SVGPathData {
 public:
  explicit SVGPathDataAndInfo(dom::SVGElement* aElement = nullptr)
      : mElement(do_GetWeakReference(static_cast<nsINode*>(aElement))) {}

  void SetElement(dom::SVGElement* aElement) {
    mElement = do_GetWeakReference(static_cast<nsINode*>(aElement));
  }

  dom::SVGElement* Element() const {
    nsCOMPtr<nsIContent> e = do_QueryReferent(mElement);
    return static_cast<dom::SVGElement*>(e.get());
  }

  // If you use this, you need to call SetElement manually.
  void CopyFrom(const SVGPathData& aOther) { mData = aOther.mData; }
  void CopyFrom(const SVGPathDataAndInfo& aOther) {
    CopyFrom(static_cast<const SVGPathData&>(aOther));
    mElement = aOther.mElement;
  }

  /**
   * Returns true if this object is an "identity" value, from the perspective
   * of SMIL. In other words, returns true until the initial value set up in
   * SVGPathSegListSMILType::InitValue() has been changed with a SetElement()
   * call.
   */
  bool IsIdentity() const {
    if (!mElement) {
      MOZ_ASSERT(IsEmpty(), "target element propagation failure");
      return true;
    }
    return false;
  }

 private:
  // We must keep a weak reference to our element because we may belong to a
  // cached baseVal SMILValue. See the comments starting at:
  // https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15
  // See also https://bugzilla.mozilla.org/show_bug.cgi?id=653497
  nsWeakPtr mElement;
};

}  // namespace mozilla

#endif  // DOM_SVG_SVGPATHDATA_H_
