// 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_DISPLAY_LIST_CANVAS_H_
#define FLUTTER_IMPELLER_DISPLAY_LIST_CANVAS_H_

#include <deque>
#include <functional>
#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "display_list/effects/dl_image_filter.h"
#include "impeller/core/sampler_descriptor.h"
#include "impeller/display_list/paint.h"
#include "impeller/entity/contents/atlas_contents.h"
#include "impeller/entity/contents/clip_contents.h"
#include "impeller/entity/contents/text_contents.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/entity_pass_clip_stack.h"
#include "impeller/entity/geometry/geometry.h"
#include "impeller/entity/geometry/vertices_geometry.h"
#include "impeller/entity/inline_pass_context.h"
#include "impeller/geometry/matrix.h"
#include "impeller/geometry/path.h"
#include "impeller/geometry/point.h"
#include "impeller/geometry/round_rect.h"
#include "impeller/geometry/vector.h"
#include "impeller/renderer/snapshot.h"
#include "impeller/typographer/text_frame.h"

namespace impeller {

struct BackdropData {
  size_t backdrop_count = 0;
  bool all_filters_equal = true;
  std::shared_ptr<Texture> texture_slot;
  // A single snapshot of the backdrop filter that is used when there are
  // multiple backdrops that share an identical filter.
  std::optional<Snapshot> shared_filter_snapshot;
  std::shared_ptr<flutter::DlImageFilter> last_backdrop;
};

struct CanvasStackEntry {
  Matrix transform;
  uint32_t clip_depth = 0u;
  size_t clip_height = 0u;
  // The number of clips tracked for this canvas stack entry.
  size_t num_clips = 0u;
  Scalar distributed_opacity = 1.0f;
  Entity::RenderingMode rendering_mode = Entity::RenderingMode::kDirect;
  // Whether all entities in the current save should be skipped.
  bool skipping = false;
  // Whether subpass coverage was rounded out to pixel coverage, or if false
  // truncated.
  bool did_round_out = false;
};

enum class PointStyle {
  /// @brief Points are drawn as squares.
  kRound,

  /// @brief Points are drawn as circles.
  kSquare,
};

/// Controls the behavior of the source rectangle given to DrawImageRect.
enum class SourceRectConstraint {
  /// @brief Faster, but may sample outside the bounds of the source rectangle.
  kFast,

  /// @brief Sample only within the source rectangle. May be slower.
  kStrict,
};

/// Specifies how much to trust the bounds rectangle provided for a list
/// of contents. Used by both |EntityPass| and |Canvas::SaveLayer|.
enum class ContentBoundsPromise {
  /// @brief The caller makes no claims related to the size of the bounds.
  kUnknown,

  /// @brief The caller claims the bounds are a reasonably tight estimate
  ///        of the coverage of the contents and should contain all of the
  ///        contents.
  kContainsContents,

  /// @brief The caller claims the bounds are a subset of an estimate of
  ///        the reasonably tight bounds but likely clips off some of the
  ///        contents.
  kMayClipContents,
};

struct LazyRenderingConfig {
  std::unique_ptr<EntityPassTarget> entity_pass_target;
  std::unique_ptr<InlinePassContext> inline_pass_context;

  /// Whether or not the clear color texture can still be updated.
  bool IsApplyingClearColor() const { return !inline_pass_context->IsActive(); }

  LazyRenderingConfig(ContentContext& renderer,
                      std::unique_ptr<EntityPassTarget> p_entity_pass_target)
      : entity_pass_target(std::move(p_entity_pass_target)) {
    inline_pass_context =
        std::make_unique<InlinePassContext>(renderer, *entity_pass_target);
  }

  LazyRenderingConfig(ContentContext& renderer,
                      std::unique_ptr<EntityPassTarget> entity_pass_target,
                      std::unique_ptr<InlinePassContext> inline_pass_context)
      : entity_pass_target(std::move(entity_pass_target)),
        inline_pass_context(std::move(inline_pass_context)) {}
};

class Canvas {
 public:
  static constexpr uint32_t kMaxDepth = 1 << 24;

  using BackdropFilterProc = std::function<std::shared_ptr<FilterContents>(
      FilterInput::Ref,
      const Matrix& effect_transform,
      Entity::RenderingMode rendering_mode)>;

  Canvas(ContentContext& renderer,
         const RenderTarget& render_target,
         bool is_onscreen,
         bool requires_readback);

  explicit Canvas(ContentContext& renderer,
                  const RenderTarget& render_target,
                  bool is_onscreen,
                  bool requires_readback,
                  Rect cull_rect);

  explicit Canvas(ContentContext& renderer,
                  const RenderTarget& render_target,
                  bool is_onscreen,
                  bool requires_readback,
                  IRect cull_rect);

  ~Canvas() = default;

  /// @brief Update the backdrop data used to group together backdrop filters
  ///        within the same layer
  void SetBackdropData(std::unordered_map<int64_t, BackdropData> backdrop_data,
                       size_t backdrop_count);

  /// @brief Return the culling bounds of the current render target, or nullopt
  ///        if there is no coverage.
  std::optional<Rect> GetLocalCoverageLimit() const;

  void Save(uint32_t total_content_depth = kMaxDepth);

  void SaveLayer(
      const Paint& paint,
      std::optional<Rect> bounds = std::nullopt,
      const flutter::DlImageFilter* backdrop_filter = nullptr,
      ContentBoundsPromise bounds_promise = ContentBoundsPromise::kUnknown,
      uint32_t total_content_depth = kMaxDepth,
      bool can_distribute_opacity = false,
      std::optional<int64_t> backdrop_id = std::nullopt);

  bool Restore();

  size_t GetSaveCount() const;

  void RestoreToCount(size_t count);

  const Matrix& GetCurrentTransform() const;

  void ResetTransform();

  void Transform(const Matrix& transform);

  void Concat(const Matrix& transform);

  void PreConcat(const Matrix& transform);

  void Translate(const Vector3& offset);

  void Scale(const Vector2& scale);

  void Scale(const Vector3& scale);

  void Skew(Scalar sx, Scalar sy);

  void Rotate(Radians radians);

  void DrawPath(const Path& path, const Paint& paint);

  void DrawPaint(const Paint& paint);

  void DrawLine(const Point& p0,
                const Point& p1,
                const Paint& paint,
                bool reuse_depth = false);

  void DrawRect(const Rect& rect, const Paint& paint);

  void DrawOval(const Rect& rect, const Paint& paint);

  void DrawRoundRect(const RoundRect& rect, const Paint& paint);

  void DrawRoundSuperellipse(const RoundSuperellipse& rse, const Paint& paint);

  void DrawCircle(const Point& center, Scalar radius, const Paint& paint);

  void DrawPoints(const Point points[],
                  uint32_t count,
                  Scalar radius,
                  const Paint& paint,
                  PointStyle point_style);

  void DrawImage(const std::shared_ptr<Texture>& image,
                 Point offset,
                 const Paint& paint,
                 const SamplerDescriptor& sampler = {});

  void DrawImageRect(
      const std::shared_ptr<Texture>& image,
      Rect source,
      Rect dest,
      const Paint& paint,
      const SamplerDescriptor& sampler = {},
      SourceRectConstraint src_rect_constraint = SourceRectConstraint::kFast);

  void DrawTextFrame(const std::shared_ptr<TextFrame>& text_frame,
                     Point position,
                     const Paint& paint);

  void DrawVertices(const std::shared_ptr<VerticesGeometry>& vertices,
                    BlendMode blend_mode,
                    const Paint& paint);

  void DrawAtlas(const std::shared_ptr<AtlasContents>& atlas_contents,
                 const Paint& paint);

  void ClipGeometry(const Geometry& geometry,
                    Entity::ClipOperation clip_op,
                    bool is_aa = true);

  void EndReplay();

  uint64_t GetOpDepth() const { return current_depth_; }

  uint64_t GetMaxOpDepth() const { return transform_stack_.back().clip_depth; }

  struct SaveLayerState {
    Paint paint;
    Rect coverage;
  };

  // Visible for testing.
  bool RequiresReadback() const { return requires_readback_; }

  // Whether the current device has the capabilities to blit an offscreen
  // texture into the onscreen.
  //
  // This requires the availibility of the blit framebuffer command, but is
  // disabled for GLES. A simple glBlitFramebuffer does not support resolving
  // different sample counts which may be present in GLES when using MSAA.
  //
  // Visible for testing.
  bool SupportsBlitToOnscreen() const;

  /// For picture snapshots we need addition steps to verify that final mipmaps
  /// are generated.
  bool EnsureFinalMipmapGeneration() const;

 private:
  ContentContext& renderer_;
  RenderTarget render_target_;
  const bool is_onscreen_;
  bool requires_readback_;
  EntityPassClipStack clip_coverage_stack_;

  std::deque<CanvasStackEntry> transform_stack_;
  std::optional<Rect> initial_cull_rect_;
  std::vector<LazyRenderingConfig> render_passes_;
  std::vector<SaveLayerState> save_layer_state_;

  /// Backdrop layers identified by an optional backdrop id.
  ///
  /// This is not the same as the [backdrop_count_] below as not
  /// all backdrop filters will have an identified backdrop id. The
  /// backdrop_count_ is also mutated during rendering.
  std::unordered_map<int64_t, BackdropData> backdrop_data_;

  /// The remaining number of backdrop filters.
  ///
  /// This value is decremented while rendering. When it reaches 0, then
  /// the FlipBackdrop can use the onscreen render target instead of
  /// another offscreen.
  ///
  /// This optimization is disabled on devices that do not support framebuffer
  /// fetch (iOS Simulator and certain OpenGLES devices).
  size_t backdrop_count_ = 0u;

  // All geometry objects created for regular draws can be stack allocated,
  // but clip geometries must be cached for record/replay for backdrop filters
  // and so must be kept alive longer.
  std::vector<std::unique_ptr<Geometry>> clip_geometry_;

  uint64_t current_depth_ = 0u;

  Point GetGlobalPassPosition() const;

  // clip depth of the previous save or 0.
  size_t GetClipHeightFloor() const;

  /// @brief Whether all entites should be skipped until a corresponding
  ///        restore.
  bool IsSkipping() const;

  /// @brief Skip all rendering/clipping entities until next restore.
  void SkipUntilMatchingRestore(size_t total_content_depth);

  void SetupRenderPass();

  /// @brief  Ends the current render pass, saving the result as a texture, and
  ///         thenrestart it with the backdrop cleared to the previous contents.
  ///
  /// The returned texture is used as the input for backdrop filters and
  /// emulated advanced blends. Returns nullptr if there was a validation
  /// failure.
  ///
  /// [should_remove_texture] defaults to false. If true, the render target
  /// texture is removed from the entity pass target. This allows the texture to
  /// be cached by the canvas dispatcher for usage in the backdrop filter reuse
  /// mechanism.
  ///
  /// [should_use_onscreen] defaults to false. If true, the results are flipped
  /// to the onscreen render target. This will set requires_readback_ to false.
  /// This action is only safe to perform when there are no more backdrop
  /// filters or advanced blends, or no more backdrop filters and the device
  /// supports framebuffer fetch.
  std::shared_ptr<Texture> FlipBackdrop(Point global_pass_position,
                                        bool should_remove_texture = false,
                                        bool should_use_onscreen = false);

  bool BlitToOnscreen(bool is_onscreen = false);

  size_t GetClipHeight() const;

  void Initialize(std::optional<Rect> cull_rect);

  void Reset();

  void AddRenderEntityWithFiltersToCurrentPass(Entity& entity,
                                               const Geometry* geometry,
                                               const Paint& paint,
                                               bool reuse_depth = false);

  void AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth = false);

  bool AttemptDrawBlurredRRect(const Rect& rect,
                               Size corner_radii,
                               const Paint& paint);

  /// For simple DrawImageRect calls, optimize any draws with a color filter
  /// into the corresponding atlas draw.
  ///
  /// Returns whether not the optimization was applied.
  bool AttemptColorFilterOptimization(const std::shared_ptr<Texture>& image,
                                      Rect source,
                                      Rect dest,
                                      const Paint& paint,
                                      const SamplerDescriptor& sampler,
                                      SourceRectConstraint src_rect_constraint);

  bool AttemptBlurredTextOptimization(
      const std::shared_ptr<TextFrame>& text_frame,
      const std::shared_ptr<TextContents>& text_contents,
      Entity& entity,
      const Paint& paint);

  RenderPass& GetCurrentRenderPass() const;

  Canvas(const Canvas&) = delete;

  Canvas& operator=(const Canvas&) = delete;
};

}  // namespace impeller

#endif  // FLUTTER_IMPELLER_DISPLAY_LIST_CANVAS_H_
