// 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_SHELL_PLATFORM_FUCHSIA_FLUTTER_EXTERNAL_VIEW_EMBEDDER_H_
#define FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_EXTERNAL_VIEW_EMBEDDER_H_

#include <fuchsia/ui/composition/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/fit/function.h>

#include <cstdint>  // For uint32_t & uint64_t
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#include "flutter/flow/embedded_views.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/macros.h"
#include "flutter/shell/platform/fuchsia/flutter/canvas_spy.h"
#include "flutter/shell/platform/fuchsia/flutter/rtree.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "third_party/skia/include/core/SkPoint.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkSize.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"

#include "flatland_connection.h"
#include "surface_producer.h"

namespace flutter_runner {

using ViewCallback = std::function<void()>;
using ViewCreatedCallback = std::function<void(
    fuchsia::ui::composition::ContentId,
    fuchsia::ui::composition::ChildViewWatcherHandle child_view_watcher)>;
using ViewIdCallback = std::function<void(fuchsia::ui::composition::ContentId)>;

// This class orchestrates interaction with the Scenic's compositor on
// Fuchsia. It ensures that flutter content and platform view content are both
// rendered correctly in a unified scene.
class ExternalViewEmbedder final : public flutter::ExternalViewEmbedder {
 public:
  constexpr static uint32_t kDefaultViewportSize = 32;

  ExternalViewEmbedder(
      fuchsia::ui::views::ViewCreationToken view_creation_token,
      fuchsia::ui::views::ViewIdentityOnCreation view_identity,
      fuchsia::ui::composition::ViewBoundProtocols endpoints,
      fidl::InterfaceRequest<fuchsia::ui::composition::ParentViewportWatcher>
          parent_viewport_watcher_request,
      std::shared_ptr<FlatlandConnection> flatland,
      std::shared_ptr<SurfaceProducer> surface_producer,
      bool intercept_all_input = false);
  ~ExternalViewEmbedder();

  // |ExternalViewEmbedder|
  flutter::DlCanvas* GetRootCanvas() override;

  // |ExternalViewEmbedder|
  void PrerollCompositeEmbeddedView(
      int64_t view_id,
      std::unique_ptr<flutter::EmbeddedViewParams> params) override;

  // |ExternalViewEmbedder|
  flutter::DlCanvas* CompositeEmbeddedView(int64_t view_id) override;

  // |ExternalViewEmbedder|
  flutter::PostPrerollResult PostPrerollAction(
      fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) override;

  // |ExternalViewEmbedder|
  void BeginFrame(
      SkISize frame_size,
      GrDirectContext* context,
      double device_pixel_ratio,
      fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) override;

  // |ExternalViewEmbedder|
  void EndFrame(
      bool should_resubmit_frame,
      fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) override;

  // |ExternalViewEmbedder|
  void SubmitFrame(GrDirectContext* context,
                   const std::shared_ptr<impeller::AiksContext>& aiks_context,
                   std::unique_ptr<flutter::SurfaceFrame> frame) override;

  // |ExternalViewEmbedder|
  void CancelFrame() override { Reset(); }

  // |ExternalViewEmbedder|
  bool SupportsDynamicThreadMerging() override { return false; }

  // View manipulation.
  // |SetViewProperties| doesn't manipulate the view directly -- it sets pending
  // properties for the next |UpdateView| call.
  void CreateView(int64_t view_id,
                  ViewCallback on_view_created,
                  ViewCreatedCallback on_view_bound);
  void DestroyView(int64_t view_id, ViewIdCallback on_view_unbound);
  void SetViewProperties(int64_t view_id,
                         const SkRect& occlusion_hint,
                         bool hit_testable,
                         bool focusable);

  // Holds the clip transform that may be applied on a View.
  struct ClipTransform {
    fuchsia::ui::composition::TransformId transform_id;
    std::vector<fuchsia::ui::composition::TransformId> children;
  };

 private:
  void Reset();  // Reset state for a new frame.

  // This struct represents a transformed clip rect.
  struct TransformedClip {
    SkMatrix transform = SkMatrix::I();
    SkRect rect = SkRect::MakeEmpty();

    bool operator==(const TransformedClip& other) const {
      return transform == other.transform && rect == other.rect;
    }
  };

  // This struct represents all the mutators that can be applied to a
  // PlatformView, unpacked from the `MutatorStack`.
  struct ViewMutators {
    std::vector<TransformedClip> clips;
    SkMatrix total_transform = SkMatrix::I();
    SkMatrix transform = SkMatrix::I();
    SkScalar opacity = 1.f;

    bool operator==(const ViewMutators& other) const {
      return clips == other.clips && total_transform == other.total_transform &&
             transform == other.transform && opacity == other.opacity;
    }
  };

  ViewMutators ParseMutatorStack(const flutter::MutatorsStack& mutators_stack);

  struct EmbedderLayer {
    EmbedderLayer(const SkISize& frame_size,
                  std::optional<flutter::EmbeddedViewParams> view_params,
                  flutter::RTreeFactory rtree_factory)
        : rtree(rtree_factory.getInstance()),
          embedded_view_params(std::move(view_params)),
          recorder(std::make_unique<SkPictureRecorder>()),
          canvas_spy(std::make_unique<flutter::CanvasSpy>(
              recorder->beginRecording(SkRect::Make(frame_size),
                                       &rtree_factory))),
          surface_size(frame_size),
          picture(nullptr) {}

    // Records paint operations applied to this layer's `SkCanvas`.
    // These records are used to determine which portions of this layer
    // contain content. The embedder propagates this information to scenic, so
    // that scenic can accurately decide which portions of this layer may
    // interact with input.
    sk_sp<flutter::RTree> rtree;

    std::optional<flutter::EmbeddedViewParams> embedded_view_params;
    std::unique_ptr<SkPictureRecorder> recorder;
    // TODO(cyanglaz: use DlOpSpy instead.
    // https://github.com/flutter/flutter/issues/123805
    std::unique_ptr<flutter::CanvasSpy> canvas_spy;
    SkISize surface_size;
    sk_sp<SkPicture> picture;
  };
  using EmbedderLayerId = std::optional<uint32_t>;
  constexpr static EmbedderLayerId kRootLayerId = EmbedderLayerId{};

  struct View {
    std::vector<ClipTransform> clip_transforms;
    fuchsia::ui::composition::TransformId transform_id;
    fuchsia::ui::composition::ContentId viewport_id;
    ViewMutators mutators;
    SkSize size = SkSize::MakeEmpty();
    SkRect pending_occlusion_hint = SkRect::MakeEmpty();
    SkRect occlusion_hint = SkRect::MakeEmpty();
    fit::callback<void(const SkSize&, const SkRect&)>
        pending_create_viewport_callback;
  };

  struct Layer {
    // Transform on which Images are set.
    fuchsia::ui::composition::TransformId transform_id;
  };

  std::shared_ptr<FlatlandConnection> flatland_;
  std::shared_ptr<SurfaceProducer> surface_producer_;

  fuchsia::ui::composition::ParentViewportWatcherPtr parent_viewport_watcher_;

  fuchsia::ui::composition::TransformId root_transform_id_;

  std::unordered_map<int64_t, View> views_;
  std::vector<Layer> layers_;

  std::unordered_map<EmbedderLayerId, EmbedderLayer> frame_layers_;
  std::vector<EmbedderLayerId> frame_composition_order_;
  std::vector<fuchsia::ui::composition::TransformId> child_transforms_;
  SkISize frame_size_ = SkISize::Make(0, 0);
  float frame_dpr_ = 1.f;

  // TransformId for the input interceptor node when input shield is turned on,
  // std::nullptr otherwise.
  std::optional<fuchsia::ui::composition::TransformId>
      input_interceptor_transform_;

  FML_DISALLOW_COPY_AND_ASSIGN(ExternalViewEmbedder);
};

}  // namespace flutter_runner

#endif  // FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_EXTERNAL_VIEW_EMBEDDER_H_
