// 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_LIB_UI_WINDOW_PLATFORM_CONFIGURATION_H_
#define FLUTTER_LIB_UI_WINDOW_PLATFORM_CONFIGURATION_H_

#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#include "flutter/assets/asset_manager.h"
#include "flutter/fml/time/time_point.h"
#include "flutter/lib/ui/semantics/semantics_update.h"
#include "flutter/lib/ui/window/pointer_data_packet.h"
#include "flutter/lib/ui/window/viewport_metrics.h"
#include "flutter/lib/ui/window/window.h"
#include "flutter/shell/common/display.h"
#include "third_party/tonic/dart_persistent_value.h"
#include "third_party/tonic/typed_data/dart_byte_data.h"

namespace flutter {
class FontCollection;
class PlatformMessage;
class PlatformMessageHandler;
class Scene;

//--------------------------------------------------------------------------
/// @brief An enum for defining the different kinds of accessibility features
///        that can be enabled by the platform.
///
///         Must match the `AccessibilityFeatures` class in framework.
enum class AccessibilityFeatureFlag : int32_t {
  kAccessibleNavigation = 1 << 0,
  kInvertColors = 1 << 1,
  kDisableAnimations = 1 << 2,
  kBoldText = 1 << 3,
  kReduceMotion = 1 << 4,
  kHighContrast = 1 << 5,
  kOnOffSwitchLabels = 1 << 6,
};

//--------------------------------------------------------------------------
/// @brief A client interface that the `RuntimeController` uses to define
///        handlers for `PlatformConfiguration` requests.
///
/// @see   `PlatformConfiguration`
///
class PlatformConfigurationClient {
 public:
  //--------------------------------------------------------------------------
  /// @brief      Whether the platform provides an implicit view. If true,
  ///             the Framework may assume that it can always render into
  ///             the view with ID 0.
  ///
  ///             This value must not change for the lifetime of the
  ///             application.
  ///
  virtual bool ImplicitViewEnabled() = 0;

  //--------------------------------------------------------------------------
  /// @brief      The route or path that the embedder requested when the
  ///             application was launched.
  ///
  ///             This will be the string "`/`" if no particular route was
  ///             requested.
  ///
  virtual std::string DefaultRouteName() = 0;

  //--------------------------------------------------------------------------
  /// @brief      Requests that, at the next appropriate opportunity, a new
  ///             frame be scheduled for rendering.
  ///
  virtual void ScheduleFrame() = 0;

  //--------------------------------------------------------------------------
  /// @brief      Updates the client's rendering on the GPU with the newly
  ///             provided Scene.
  ///
  virtual void Render(Scene* scene) = 0;

  //--------------------------------------------------------------------------
  /// @brief      Receives an updated semantics tree from the Framework.
  ///
  /// @param[in] update The updated semantic tree to apply.
  ///
  virtual void UpdateSemantics(SemanticsUpdate* update) = 0;

  //--------------------------------------------------------------------------
  /// @brief      When the Flutter application has a message to send to the
  ///             underlying platform, the message needs to be forwarded to
  ///             the platform on the appropriate thread (via the platform
  ///             task runner). The PlatformConfiguration delegates this task
  ///             to the engine via this method.
  ///
  /// @see        `PlatformView::HandlePlatformMessage`
  ///
  /// @param[in]  message  The message from the Flutter application to send to
  ///                      the underlying platform.
  ///
  virtual void HandlePlatformMessage(
      std::unique_ptr<PlatformMessage> message) = 0;

  //--------------------------------------------------------------------------
  /// @brief      Returns the current collection of fonts available on the
  ///             platform.
  ///
  ///             This function reads an XML file and makes font families and
  ///             collections of them. MinikinFontForTest is used for FontFamily
  ///             creation.
  virtual FontCollection& GetFontCollection() = 0;

  //--------------------------------------------------------------------------
  /// @brief      Returns the current collection of assets available on the
  ///             platform.
  virtual std::shared_ptr<AssetManager> GetAssetManager() = 0;

  //--------------------------------------------------------------------------
  /// @brief      Notifies this client of the name of the root isolate and its
  ///             port when that isolate is launched, restarted (in the
  ///             cold-restart scenario) or the application itself updates the
  ///             name of the root isolate (via `Window.setIsolateDebugName`
  ///             in `window.dart`). The name of the isolate is meaningless to
  ///             the engine but is used in instrumentation and tooling.
  ///             Currently, this information is to update the service
  ///             protocol list of available root isolates running in the VM
  ///             and their names so that the appropriate isolate can be
  ///             selected in the tools for debugging and instrumentation.
  ///
  /// @param[in]  isolate_name  The isolate name
  /// @param[in]  isolate_port  The isolate port
  ///
  virtual void UpdateIsolateDescription(const std::string isolate_name,
                                        int64_t isolate_port) = 0;

  //--------------------------------------------------------------------------
  /// @brief      Notifies this client that the application has an opinion about
  ///             whether its frame timings need to be reported backed to it.
  ///             Due to the asynchronous nature of rendering in Flutter, it is
  ///             not possible for the application to determine the total time
  ///             it took to render a specific frame. While the layer-tree is
  ///             constructed on the UI thread, it needs to be rendering on the
  ///             raster thread. Dart code cannot execute on this thread. So any
  ///             instrumentation about the frame times gathered on this thread
  ///             needs to be aggregated and sent back to the UI thread for
  ///             processing in Dart.
  ///
  ///             When the application indicates that frame times need to be
  ///             reported, it collects this information till a specified number
  ///             of data points are gathered. Then this information is sent
  ///             back to Dart code via `Engine::ReportTimings`.
  ///
  ///             This option is engine counterpart of the
  ///             `Window._setNeedsReportTimings` in `window.dart`.
  ///
  /// @param[in]  needs_reporting  If reporting information should be collected
  /// and send back to Dart.
  ///
  virtual void SetNeedsReportTimings(bool value) = 0;

  //--------------------------------------------------------------------------
  /// @brief      The embedder can specify data that the isolate can request
  ///             synchronously on launch. This accessor fetches that data.
  ///
  ///             This data is persistent for the duration of the Flutter
  ///             application and is available even after isolate restarts.
  ///             Because of this lifecycle, the size of this data must be kept
  ///             to a minimum.
  ///
  ///             For asynchronous communication between the embedder and
  ///             isolate, a platform channel may be used.
  ///
  /// @return     A map of the isolate data that the framework can request upon
  ///             launch.
  ///
  virtual std::shared_ptr<const fml::Mapping> GetPersistentIsolateData() = 0;

  //--------------------------------------------------------------------------
  /// @brief      Directly invokes platform-specific APIs to compute the
  ///             locale the platform would have natively resolved to.
  ///
  /// @param[in]  supported_locale_data  The vector of strings that represents
  ///                                    the locales supported by the app.
  ///                                    Each locale consists of three
  ///                                    strings: languageCode, countryCode,
  ///                                    and scriptCode in that order.
  ///
  /// @return     A vector of 3 strings languageCode, countryCode, and
  ///             scriptCode that represents the locale selected by the
  ///             platform. Empty strings mean the value was unassigned. Empty
  ///             vector represents a null locale.
  ///
  virtual std::unique_ptr<std::vector<std::string>>
  ComputePlatformResolvedLocale(
      const std::vector<std::string>& supported_locale_data) = 0;

  //--------------------------------------------------------------------------
  /// @brief      Invoked when the Dart VM requests that a deferred library
  ///             be loaded. Notifies the engine that the deferred library
  ///             identified by the specified loading unit id should be
  ///             downloaded and loaded into the Dart VM via
  ///             `LoadDartDeferredLibrary`
  ///
  ///             Upon encountering errors or otherwise failing to load a
  ///             loading unit with the specified id, the failure should be
  ///             directly reported to dart by calling
  ///             `LoadDartDeferredLibraryFailure` to ensure the waiting dart
  ///             future completes with an error.
  ///
  /// @param[in]  loading_unit_id  The unique id of the deferred library's
  ///                              loading unit. This id is to be passed
  ///                              back into LoadDartDeferredLibrary
  ///                              in order to identify which deferred
  ///                              library to load.
  ///
  virtual void RequestDartDeferredLibrary(intptr_t loading_unit_id) = 0;

 protected:
  virtual ~PlatformConfigurationClient();
};

//----------------------------------------------------------------------------
/// @brief      A class for holding and distributing platform-level information
///             to and from the Dart code in Flutter's framework.
///
///             It handles communication between the engine and the framework,
///             and owns the main window.
///
///             It communicates with the RuntimeController through the use of a
///             PlatformConfigurationClient interface, which the
///             RuntimeController defines.
///
class PlatformConfiguration final {
 public:
  //----------------------------------------------------------------------------
  /// @brief      Creates a new PlatformConfiguration, typically created by the
  ///             RuntimeController.
  ///
  /// @param[in] client The `PlatformConfigurationClient` to be injected into
  ///                   the PlatformConfiguration. This client is used to
  ///                   forward requests to the RuntimeController.
  ///
  explicit PlatformConfiguration(PlatformConfigurationClient* client);

  // PlatformConfiguration is not copyable.
  PlatformConfiguration(const PlatformConfiguration&) = delete;
  PlatformConfiguration& operator=(const PlatformConfiguration&) = delete;

  ~PlatformConfiguration();

  //----------------------------------------------------------------------------
  /// @brief      Access to the platform configuration client (which typically
  ///             is implemented by the RuntimeController).
  ///
  /// @return     Returns the client used to construct this
  /// PlatformConfiguration.
  ///
  PlatformConfigurationClient* client() const { return client_; }

  //----------------------------------------------------------------------------
  /// @brief      Called by the RuntimeController once it has created the root
  ///             isolate, so that the PlatformController can get a handle to
  ///             the 'dart:ui' library.
  ///
  ///             It uses the handle to call the hooks in hooks.dart.
  ///
  void DidCreateIsolate();

  //----------------------------------------------------------------------------
  /// @brief      Update the specified display data in the framework.
  ///
  /// @param[in]  displays  The display data to send to Dart.
  ///
  void UpdateDisplays(const std::vector<DisplayData>& displays);

  //----------------------------------------------------------------------------
  /// @brief      Update the specified locale data in the framework.
  ///
  /// @param[in]  locale_data  The locale data. This should consist of groups of
  ///             4 strings, each group representing a single locale.
  ///
  void UpdateLocales(const std::vector<std::string>& locales);

  //----------------------------------------------------------------------------
  /// @brief      Update the user settings data in the framework.
  ///
  /// @param[in]  data  The user settings data.
  ///
  void UpdateUserSettingsData(const std::string& data);

  //----------------------------------------------------------------------------
  /// @brief      Updates the lifecycle state data in the framework.
  ///
  /// @param[in]  data  The lifecycle state data.
  ///
  void UpdateInitialLifecycleState(const std::string& data);

  //----------------------------------------------------------------------------
  /// @brief      Notifies the PlatformConfiguration that the embedder has
  ///             expressed an opinion about whether the accessibility tree
  ///             should be generated or not. This call originates in the
  ///             platform view and is forwarded to the PlatformConfiguration
  ///             here by the engine.
  ///
  /// @param[in]  enabled  Whether the accessibility tree is enabled or
  ///                      disabled.
  ///
  void UpdateSemanticsEnabled(bool enabled);

  //----------------------------------------------------------------------------
  /// @brief      Forward the preference of accessibility features that must be
  ///             enabled in the semantics tree to the framwork.
  ///
  /// @param[in]  flags  The accessibility features that must be generated in
  ///             the semantics tree.
  ///
  void UpdateAccessibilityFeatures(int32_t flags);

  //----------------------------------------------------------------------------
  /// @brief      Notifies the PlatformConfiguration that the client has sent
  ///             it a message. This call originates in the platform view and
  ///             has been forwarded through the engine to here.
  ///
  /// @param[in]  message  The message sent from the embedder to the Dart
  ///                      application.
  ///
  void DispatchPlatformMessage(std::unique_ptr<PlatformMessage> message);

  //----------------------------------------------------------------------------
  /// @brief      Notifies the PlatformConfiguration that the client has sent
  ///             it pointer events. This call originates in the platform view
  ///             and has been forwarded through the engine to here.
  ///
  /// @param[in]  packet  The pointer event(s) serialized into a packet.
  ///
  void DispatchPointerDataPacket(const PointerDataPacket& packet);

  //----------------------------------------------------------------------------
  /// @brief      Notifies the framework that the embedder encountered an
  ///             accessibility related action on the specified node. This call
  ///             originates on the platform view and has been forwarded to the
  ///             platform configuration here by the engine.
  ///
  /// @param[in]  node_id The identifier of the accessibility node.
  /// @param[in]  action  The accessibility related action performed on the
  ///                     node of the specified ID.
  /// @param[in]  args    Optional data that applies to the specified action.
  ///
  void DispatchSemanticsAction(int32_t node_id,
                               SemanticsAction action,
                               fml::MallocMapping args);

  //----------------------------------------------------------------------------
  /// @brief      Notifies the framework that it is time to begin working on a
  ///             new frame previously scheduled via a call to
  ///             `PlatformConfigurationClient::ScheduleFrame`. This call
  ///             originates in the animator.
  ///
  ///             The frame time given as the argument indicates the point at
  ///             which the current frame interval began. It is very slightly
  ///             (because of scheduling overhead) in the past. If a new layer
  ///             tree is not produced and given to the raster task runner
  ///             within one frame interval from this point, the Flutter
  ///             application will jank.
  ///
  ///             This method calls the `::_beginFrame` method in `hooks.dart`.
  ///
  /// @param[in]  frame_time  The point at which the current frame interval
  ///                         began. May be used by animation interpolators,
  ///                         physics simulations, etc..
  ///
  /// @param[in]  frame_number The frame number recorded by the animator. Used
  ///                          by the framework to associate frame specific
  ///                          debug information with frame timings and timeline
  ///                          events.
  ///
  void BeginFrame(fml::TimePoint frame_time, uint64_t frame_number);

  //----------------------------------------------------------------------------
  /// @brief      Dart code cannot fully measure the time it takes for a
  ///             specific frame to be rendered. This is because Dart code only
  ///             runs on the UI task runner. That is only a small part of the
  ///             overall frame workload. The raster task runner frame workload
  ///             is executed on a thread where Dart code cannot run (and hence
  ///             instrument). Besides, due to the pipelined nature of rendering
  ///             in Flutter, there may be multiple frame workloads being
  ///             processed at any given time. However, for non-Timeline based
  ///             profiling, it is useful for trace collection and processing to
  ///             happen in Dart. To do this, the raster task runner frame
  ///             workloads need to be instrumented separately. After a set
  ///             number of these profiles have been gathered, they need to be
  ///             reported back to Dart code. The engine reports this extra
  ///             instrumentation information back to the framework by invoking
  ///             this method at predefined intervals.
  ///
  /// @see        `FrameTiming`
  ///
  /// @param[in]  timings  Collection of `FrameTiming::kStatisticsCount` * 'n'
  ///                      values for `n` frames whose timings have not been
  ///                      reported yet. Many of the values are timestamps, but
  ///                      a collection of integers is reported here for easier
  ///                      conversions to Dart objects. The timestamps are
  ///                      measured against the system monotonic clock measured
  ///                      in microseconds.
  ///
  void ReportTimings(std::vector<int64_t> timings);

  //----------------------------------------------------------------------------
  /// @brief      Retrieves the Window with the given ID managed by the
  ///             `PlatformConfiguration`.
  ///
  /// @param[in] window_id The id of the window to find and return.
  ///
  /// @return     a pointer to the Window. Returns nullptr if the ID is not
  ///             found.
  ///
  Window* get_window(int window_id);

  //----------------------------------------------------------------------------
  /// @brief      Responds to a previous platform message to the engine from the
  ///             framework.
  ///
  /// @param[in] response_id The unique id that identifies the original platform
  ///                        message to respond to.
  /// @param[in] data        The data to send back in the response.
  ///
  void CompletePlatformMessageResponse(int response_id,
                                       std::vector<uint8_t> data);

  //----------------------------------------------------------------------------
  /// @brief      Responds to a previous platform message to the engine from the
  ///             framework with an empty response.
  ///
  /// @param[in] response_id The unique id that identifies the original platform
  ///                        message to respond to.
  ///
  void CompletePlatformMessageEmptyResponse(int response_id);

  Dart_Handle on_error() { return on_error_.Get(); }

 private:
  PlatformConfigurationClient* client_;
  tonic::DartPersistentValue on_error_;
  tonic::DartPersistentValue update_displays_;
  tonic::DartPersistentValue update_locales_;
  tonic::DartPersistentValue update_user_settings_data_;
  tonic::DartPersistentValue update_initial_lifecycle_state_;
  tonic::DartPersistentValue update_semantics_enabled_;
  tonic::DartPersistentValue update_accessibility_features_;
  tonic::DartPersistentValue dispatch_platform_message_;
  tonic::DartPersistentValue dispatch_pointer_data_packet_;
  tonic::DartPersistentValue dispatch_semantics_action_;
  tonic::DartPersistentValue begin_frame_;
  tonic::DartPersistentValue draw_frame_;
  tonic::DartPersistentValue report_timings_;

  std::unordered_map<int64_t, std::unique_ptr<Window>> windows_;

  // ID starts at 1 because an ID of 0 indicates that no response is expected.
  int next_response_id_ = 1;
  std::unordered_map<int, fml::RefPtr<PlatformMessageResponse>>
      pending_responses_;
};

//----------------------------------------------------------------------------
/// An inteface that the result of `Dart_CurrentIsolateGroupData` should
/// implement for registering background isolates to work.
class PlatformMessageHandlerStorage {
 public:
  virtual ~PlatformMessageHandlerStorage() = default;
  virtual void SetPlatformMessageHandler(
      int64_t root_isolate_token,
      std::weak_ptr<PlatformMessageHandler> handler) = 0;

  virtual std::weak_ptr<PlatformMessageHandler> GetPlatformMessageHandler(
      int64_t root_isolate_token) const = 0;
};

//----------------------------------------------------------------------------
// API exposed as FFI calls in Dart.
//
// These are probably not supposed to be called directly, and should instead
// be called through their sibling API in `PlatformConfiguration` or
// `PlatformConfigurationClient`.
//
// These are intentionally undocumented. Refer instead to the sibling methods
// above.
//----------------------------------------------------------------------------
class PlatformConfigurationNativeApi {
 public:
  static Dart_Handle ImplicitViewEnabled();

  static std::string DefaultRouteName();

  static void ScheduleFrame();

  static void Render(Scene* scene);

  static void UpdateSemantics(SemanticsUpdate* update);

  static void SetNeedsReportTimings(bool value);

  static Dart_Handle GetPersistentIsolateData();

  static Dart_Handle ComputePlatformResolvedLocale(
      Dart_Handle supportedLocalesHandle);

  static void SetIsolateDebugName(const std::string& name);

  static Dart_Handle SendPlatformMessage(const std::string& name,
                                         Dart_Handle callback,
                                         Dart_Handle data_handle);

  static Dart_Handle SendPortPlatformMessage(const std::string& name,
                                             Dart_Handle identifier,
                                             Dart_Handle send_port,
                                             Dart_Handle data_handle);

  static void RespondToPlatformMessage(int response_id,
                                       const tonic::DartByteData& data);

  //--------------------------------------------------------------------------
  /// @brief      Requests the Dart VM to adjusts the GC heuristics based on
  ///             the requested `performance_mode`. Returns the old performance
  ///             mode.
  ///
  ///             Requesting a performance mode doesn't guarantee any
  ///             performance characteristics. This is best effort, and should
  ///             be used after careful consideration of the various GC
  ///             trade-offs.
  ///
  /// @param[in]  performance_mode The requested performance mode. Please refer
  ///                              to documentation of `Dart_PerformanceMode`
  ///                              for more details about what each performance
  ///                              mode does.
  ///
  static int RequestDartPerformanceMode(int mode);

  //--------------------------------------------------------------------------
  /// @brief      Returns the current performance mode of the Dart VM. Defaults
  /// to `Dart_PerformanceMode_Default` if no prior requests to change the
  /// performance mode have been made.
  static Dart_PerformanceMode GetDartPerformanceMode();

  static int64_t GetRootIsolateToken();

  static void RegisterBackgroundIsolate(int64_t root_isolate_token);

 private:
  static Dart_PerformanceMode current_performance_mode_;
};

}  // namespace flutter

#endif  // FLUTTER_LIB_UI_WINDOW_PLATFORM_CONFIGURATION_H_
