// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_IMPELLER_GEOMETRY_PATH_H_
#define FLUTTER_IMPELLER_GEOMETRY_PATH_H_

#include <functional>
#include <memory>
#include <optional>
#include <ostream>
#include <tuple>
#include <vector>

#include "impeller/geometry/path_component.h"
#include "impeller/geometry/rect.h"

namespace impeller {

enum class Cap {
  kButt,
  kRound,
  kSquare,
};

enum class Join {
  kMiter,
  kRound,
  kBevel,
};

enum class FillType {
  kNonZero,  // The default winding order.
  kOdd,
};

enum class Convexity {
  kUnknown,
  kConvex,
};

//------------------------------------------------------------------------------
/// @brief      Paths are lightweight objects that describe a collection of
///             linear, quadratic, or cubic segments. These segments may be
///             broken up by move commands, which are effectively linear
///             commands that pick up the pen rather than continuing to draw.
///
///             All shapes supported by Impeller are paths either directly or
///             via approximation (in the case of circles).
///
///             Paths are externally immutable once created, Creating paths must
///             be done using a path builder.
///
class Path {
 public:
  enum class ComponentType {
    kLinear,
    kQuadratic,
    kConic,
    kCubic,
    kContour,
  };

  class ComponentIterator {
   public:
    ComponentType type() const;

    // Return pointer to path component or null if the type is wrong or
    // the iterator is past the end of the path.
    const LinearPathComponent* linear() const;
    const QuadraticPathComponent* quadratic() const;
    const ConicPathComponent* conic() const;
    const CubicPathComponent* cubic() const;
    const ContourComponent* contour() const;

    ComponentIterator& operator++();
    bool operator==(const ComponentIterator& other) const {
      return component_index_ == other.component_index_;
    }
    bool operator!=(const ComponentIterator& other) const {
      return component_index_ != other.component_index_;
    }

   private:
    ComponentIterator(const Path& path, size_t index, size_t offset)
        : path_(path), component_index_(index), storage_offset_(offset) {}

    const Path& path_;
    size_t component_index_ = 0u;
    size_t storage_offset_ = 0u;

    friend class Path;
  };

  static constexpr size_t VerbToOffset(Path::ComponentType verb) {
    switch (verb) {
      case Path::ComponentType::kLinear:
        return 2u;
      case Path::ComponentType::kQuadratic:
        return 3u;
      case Path::ComponentType::kConic:
        return 4u;
      case Path::ComponentType::kCubic:
        return 4u;
      case Path::ComponentType::kContour:
        return 2u;
        break;
    }
    FML_UNREACHABLE();
  }

  struct PolylineContour {
    struct Component {
      size_t component_start_index;
      /// Denotes whether this component is a curve.
      ///
      /// This is set to true when this component is generated from
      /// QuadraticComponent or CubicPathComponent.
      bool is_curve;
    };
    /// Index that denotes the first point of this contour.
    size_t start_index;

    /// Denotes whether the last point of this contour is connected to the first
    /// point of this contour or not.
    bool is_closed;

    /// The direction of the contour's start cap.
    Vector2 start_direction;
    /// The direction of the contour's end cap.
    Vector2 end_direction;

    /// Distinct components in this contour.
    ///
    /// If this contour is generated from multiple path components, each
    /// path component forms a component in this vector.
    std::vector<Component> components;
  };

  /// One or more contours represented as a series of points and indices in
  /// the point vector representing the start of a new contour.
  ///
  /// Polylines are ephemeral and meant to be used by the tessellator. They do
  /// not allocate their own point vectors to allow for optimizations around
  /// allocation and reuse of arenas.
  struct Polyline {
    /// The signature of a method called when it is safe to reclaim the point
    /// buffer provided to the constructor of this object.
    using PointBufferPtr = std::unique_ptr<std::vector<Point>>;
    using ReclaimPointBufferCallback = std::function<void(PointBufferPtr)>;

    /// The buffer will be cleared and returned at the destruction of this
    /// polyline.
    Polyline(PointBufferPtr point_buffer, ReclaimPointBufferCallback reclaim);

    Polyline(Polyline&& other);
    ~Polyline();

    /// Points in the polyline, which may represent multiple contours specified
    /// by indices in |contours|.
    PointBufferPtr points;

    Point& GetPoint(size_t index) const { return (*points)[index]; }

    /// Contours are disconnected pieces of a polyline, such as when a MoveTo
    /// was issued on a PathBuilder.
    std::vector<PolylineContour> contours;

    /// Convenience method to compute the start (inclusive) and end (exclusive)
    /// point of the given contour index.
    ///
    /// The contour_index parameter is clamped to contours.size().
    std::tuple<size_t, size_t> GetContourPointBounds(
        size_t contour_index) const;

   private:
    ReclaimPointBufferCallback reclaim_points_;
  };

  Path();

  ~Path();

  size_t GetComponentCount(std::optional<ComponentType> type = {}) const;

  size_t GetPointCount() const;

  FillType GetFillType() const;

  bool IsConvex() const;

  bool IsEmpty() const;

  /// @brief Whether the line contains a single contour.
  bool IsSingleContour() const;

  ComponentIterator begin() const;
  ComponentIterator end() const;

  /// Callers must provide the scale factor for how this path will be
  /// transformed.
  ///
  /// It is suitable to use the max basis length of the matrix used to transform
  /// the path. If the provided scale is 0, curves will revert to straight
  /// lines.
  Polyline CreatePolyline(
      Scalar scale,
      Polyline::PointBufferPtr point_buffer =
          std::make_unique<std::vector<Point>>(),
      Polyline::ReclaimPointBufferCallback reclaim = nullptr) const;

  void EndContour(
      size_t storage_offset,
      Polyline& polyline,
      size_t component_index,
      std::vector<PolylineContour::Component>& poly_components) const;

  std::optional<Rect> GetBoundingBox() const;

  std::optional<Rect> GetTransformedBoundingBox(const Matrix& transform) const;

  /// Generate a polyline into the temporary storage held by the [writer].
  ///
  /// It is suitable to use the max basis length of the matrix used to transform
  /// the path. If the provided scale is 0, curves will revert to straight
  /// lines.
  void WritePolyline(Scalar scale, VertexWriter& writer) const;

  /// Determine required storage for points and number of contours.
  std::pair<size_t, size_t> CountStorage(Scalar scale) const;

 private:
  friend class PathBuilder;

  // All of the data for the path is stored in this structure which is
  // held by a shared_ptr. Since they all share the structure, the
  // copy constructor for Path is very cheap and we don't need to deal
  // with shared pointers for Path fields and method arguments.
  //
  // PathBuilder also uses this structure to accumulate the path data
  // but the Path constructor used in |TakePath()| will clone the
  // structure to prevent sharing and future modifications within the
  // builder from affecting the existing taken paths.
  struct Data {
    Data() = default;

    Data(Data&& other) = default;

    Data(const Data& other) = default;

    ~Data() = default;

    FillType fill = FillType::kNonZero;
    Convexity convexity = Convexity::kUnknown;
    bool single_contour = true;
    std::optional<Rect> bounds;
    std::vector<Point> points;
    std::vector<ComponentType> components;
  };

  explicit Path(Data data);

  std::shared_ptr<const Data> data_;
};

static_assert(sizeof(Path) == sizeof(std::shared_ptr<struct Anonymous>));

}  // namespace impeller

#endif  // FLUTTER_IMPELLER_GEOMETRY_PATH_H_
