// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_VIZ_COMMON_HIT_TEST_HIT_TEST_QUERY_H_
#define COMPONENTS_VIZ_COMMON_HIT_TEST_HIT_TEST_QUERY_H_

#include <string>
#include <vector>

#include "base/memory/safe_ref.h"
#include "components/viz/common/hit_test/aggregated_hit_test_region.h"
#include "components/viz/common/viz_common_export.h"
#include "ui/gfx/geometry/point_f.h"

namespace viz {

struct Target {
  FrameSinkId frame_sink_id;
  // Coordinates in the coordinate system of the target FrameSinkId.
  gfx::PointF location_in_target;
  // Different flags are defined in services/viz/public/mojom/hit_test/
  // hit_test_region_list.mojom.
  uint32_t flags = 0;
};

enum class EventSource {
  MOUSE,
  TOUCH,
  ANY,
};

// Finds the target for a given location based on the AggregatedHitTestRegion
// list aggregated by HitTestAggregator.
// TODO(crbug.com/41460939): Handle 3d space cases correctly.
class VIZ_COMMON_EXPORT HitTestQuery {
 public:
  class DataProvider {
   public:
    virtual ~DataProvider() = default;
    // Gets HitTestData from `HitTestAggregator` on VizCompositor thread.
    virtual const std::vector<AggregatedHitTestRegion>& GetHitTestData()
        const = 0;
  };

  explicit HitTestQuery(std::optional<base::SafeRef<DataProvider>> provider);

  HitTestQuery(const HitTestQuery&) = delete;
  HitTestQuery& operator=(const HitTestQuery&) = delete;

  virtual ~HitTestQuery();

  // HitTestAggregator has sent the most recent |hit_test_data| for targeting/
  // transforming requests.
  void OnAggregatedHitTestRegionListUpdated(
      const std::vector<AggregatedHitTestRegion>& hit_test_data);

  // Finds Target for |location_in_root|, including the FrameSinkId of the
  // target, updated location in the coordinate system of the target and
  // hit-test flags for the target.
  // Assumptions about the AggregatedHitTestRegion list received.
  // 1. The list is in ascending (front to back) z-order.
  // 2. Children count includes children of children.
  // 3. After applying transform to the incoming point, point is in the same
  // coordinate system as the bounds it is comparing against. We shouldn't
  // need to apply rect's origin offset as it should be included in this
  // transform.
  // For example,
  //  +e-------------+
  //  |   +c---------|
  //  | 1 |+a--+     |
  //  |   || 2 |     |
  //  |   |+b--------|
  //  |   ||         |
  //  |   ||   3     |
  //  +--------------+
  // In this case, after applying identity transform, 1 is in the coordinate
  // system of e; apply the transfrom-from-e-to-c and transform-from-c-to-a
  // then we get 2 in the coordinate system of a; apply the
  // transfrom-from-e-to-c and transform-from-c-to-b then we get 3 in the
  // coordinate system of b.
  Target FindTargetForLocation(EventSource event_source,
                               const gfx::PointF& location_in_root) const;

  // Same as FindTargetForLocation(), but starts from |frame_sink_id|.
  // |location| is in the coordinate space of |frame_sink_id|. Returns an empty
  // target if |frame_sink_id| is not found.
  Target FindTargetForLocationStartingFrom(
      EventSource event_source,
      const gfx::PointF& location,
      const FrameSinkId& frame_sink_id) const;

  // When a target window is already known, e.g. capture/latched window, convert
  // |location_in_root| to be in the coordinate space of the target and store
  // that in |transformed_location|. Return true if the transform is successful
  // and false otherwise.
  // |target_ancestors| contains the FrameSinkId from target to root.
  // |target_ancestors.front()| is the target, and |target_ancestors.back()|
  // is the root.
  bool TransformLocationForTarget(
      const std::vector<FrameSinkId>& target_ancestors,
      const gfx::PointF& location_in_root,
      gfx::PointF* transformed_location) const;

  // Gets the transform from root to |target| in physical pixels. Returns true
  // and stores the result into |transform| if successful, returns false
  // otherwise. This is potentially a little more expensive than
  // TransformLocationForTarget(). So if the path from root to target is known,
  // then that is the preferred API.
  bool GetTransformToTarget(const FrameSinkId& target,
                            gfx::Transform* transform) const;

  // Returns whether client has submitted hit test data for |frame_sink_id|.
  // Note that this returns false even if the embedder has submitted hit-test
  // data for |frame_sink_id|.
  bool ContainsActiveFrameSinkId(const FrameSinkId& frame_sink_id) const;

  // Returns hit-test data, using indentation to visualize the tree structure.
  std::string PrintHitTestData() const;
  const std::vector<AggregatedHitTestRegion>& GetHitTestData() const {
    return hit_test_data_;
  }

  // Returns true if |id| is present in |hit_test_data|. If |id| is present
  // |index| is set accordingly.
  bool FindIndexOfFrameSink(const FrameSinkId& id, size_t* index) const;

 protected:
  // The FindTargetForLocation() functions call into this.
  // If |is_location_relative_to_parent| is true, |location| is relative to
  // the parent, otherwise it is in the coordinate space of |frame_sink_id|.
  // Virtual for testing.
  virtual Target FindTargetForLocationStartingFromImpl(
      EventSource event_source,
      const gfx::PointF& location,
      const FrameSinkId& frame_sink_id,
      bool is_location_relative_to_parent) const;

 private:
  // Helper function to find |target| for |location| in the |region_index|,
  // returns true if a target is found and false otherwise. If
  // |is_location_relative_to_parent| is true, |location| is in the coordinate
  // space of |region_index|'s parent, otherwise it is in the coordinate space
  // of |region_index|.
  bool FindTargetInRegionForLocation(EventSource event_source,
                                     const gfx::PointF& location,
                                     size_t region_index,
                                     bool is_location_relative_to_parent,
                                     const FrameSinkId& root_view_frame_sink_id,
                                     Target* target) const;

  // Transform |location_in_target| to be in |region_index|'s coordinate space.
  // |location_in_target| is in the coordinate space of |region_index|'s parent
  // at the beginning.
  bool TransformLocationForTargetRecursively(
      const std::vector<FrameSinkId>& target_ancestors,
      size_t target_ancestor,
      size_t region_index,
      gfx::PointF* location_in_target) const;

  bool GetTransformToTargetRecursively(const FrameSinkId& target,
                                       size_t region_index,
                                       gfx::Transform* transform) const;

  // Returns updated aggregated hit test data from stored `hit_test_data` in the
  // browser and uses `provider_` to get updated hit test data on VizCompositor
  // thread.
  const std::vector<AggregatedHitTestRegion>& GetHitTestRegionData() const;

  std::vector<AggregatedHitTestRegion> hit_test_data_;

  // DataProvider is expected to outlive |this|. `HitTestAggregator` is the
  // provider on VizCompositorThread whereas it's null on CrBrowserMain.
  std::optional<base::SafeRef<DataProvider>> provider_;
};

}  // namespace viz

#endif  // COMPONENTS_VIZ_COMMON_HIT_TEST_HIT_TEST_QUERY_H_
