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

#include "ui/touch_selection/touch_handle_drawable_aura.h"

#include "ui/aura/window.h"
#include "ui/aura/window_targeter.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/hit_test.h"
#include "ui/base/models/image_model.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_manager.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/events/event.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/outsets_f.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/image/image.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_observer.h"
#include "ui/touch_selection//vector_icons/vector_icons.h"

namespace ui {
namespace {

// Padding to apply horizontally around and vertically below the handle image,
// so that touch events near the handle image are targeted to the handle.
constexpr int kSelectionHandlePadding = 6;

// Max opacity of the selection handle image.
constexpr float kSelectionHandleMaxOpacity = 0.8f;

// Epsilon value used to compare float values to zero.
constexpr float kEpsilon = 1e-8f;

// Returns the appropriate handle vector icon based on the handle orientation.
ImageModel GetHandleVectorIcon(TouchHandleOrientation orientation) {
  const gfx::VectorIcon* icon = nullptr;
  switch (orientation) {
    case TouchHandleOrientation::LEFT:
      icon = &kTextSelectionHandleLeftIcon;
      break;
    case TouchHandleOrientation::CENTER:
      icon = &kTextSelectionHandleCenterIcon;
      break;
    case TouchHandleOrientation::RIGHT:
      icon = &kTextSelectionHandleRightIcon;
      break;
    case TouchHandleOrientation::UNDEFINED:
      NOTREACHED() << "Invalid touch handle bound type.";
  }
  return ImageModel::FromVectorIcon(*icon,
                                    /*color_id=*/kColorSysPrimary);
}

bool IsNearlyZero(float value) {
  return std::abs(value) < kEpsilon;
}

}  // namespace

TouchHandleDrawableAura::TouchHandleDrawableAura(aura::Window* parent)
    : window_(std::make_unique<aura::Window>(/*delegate=*/nullptr)),
      enabled_(false),
      alpha_(0),
      orientation_(TouchHandleOrientation::UNDEFINED) {
  window_->Init(LAYER_TEXTURED);
  window_->SetTransparent(true);
  window_->set_owned_by_parent(false);
  window_->SetEventTargetingPolicy(aura::EventTargetingPolicy::kNone);
  window_->layer()->set_delegate(this);
  parent->AddChild(window_.get());

  theme_observation_.Observe(NativeTheme::GetInstanceForNativeUi());
}

TouchHandleDrawableAura::~TouchHandleDrawableAura() = default;

void TouchHandleDrawableAura::UpdateWindowBounds() {
  gfx::Rect window_bounds(gfx::ToRoundedPoint(targetable_origin_),
                          handle_image_.Size());
  // Offset the window bounds to account for space between the origin of the
  // targetable area and the handle image.
  window_bounds.Offset(kSelectionHandlePadding, 0);
  window_->SetBounds(window_bounds);
}

bool TouchHandleDrawableAura::IsVisible() const {
  return enabled_ && !IsNearlyZero(alpha_);
}

void TouchHandleDrawableAura::SetEnabled(bool enabled) {
  if (enabled == enabled_)
    return;

  enabled_ = enabled;
  if (IsVisible())
    window_->Show();
  else
    window_->Hide();
}

void TouchHandleDrawableAura::SetOrientation(TouchHandleOrientation orientation,
                                             bool mirror_vertical,
                                             bool mirror_horizontal) {
  // TODO(AviD): Implement adaptive handle orientation logic for Aura
  DCHECK(!mirror_vertical);
  DCHECK(!mirror_horizontal);

  if (orientation_ == orientation)
    return;
  orientation_ = orientation;

  handle_image_ = GetHandleVectorIcon(orientation);
  UpdateWindowBounds();
  window_->SchedulePaintInRect(gfx::Rect(window_->bounds().size()));
}

void TouchHandleDrawableAura::SetOrigin(const gfx::PointF& position) {
  targetable_origin_ = position;
  UpdateWindowBounds();
}

void TouchHandleDrawableAura::SetAlpha(float alpha) {
  if (alpha == alpha_)
    return;

  alpha_ = alpha;
  window_->layer()->SetOpacity(alpha_ * kSelectionHandleMaxOpacity);

  if (IsVisible())
    window_->Show();
  else
    window_->Hide();
}

gfx::RectF TouchHandleDrawableAura::GetVisibleBounds() const {
  // These bounds are used to determine the area that can be used for targeting
  // the handle, so we include the transparent padding added around the handle
  // image even though it technically isn't visible.
  gfx::RectF targetable_bounds(window_->bounds());
  targetable_bounds.Outset(gfx::OutsetsF::TLBR(0, kSelectionHandlePadding,
                                               kSelectionHandlePadding,
                                               kSelectionHandlePadding));
  return targetable_bounds;
}

float TouchHandleDrawableAura::GetDrawableHorizontalPaddingRatio() const {
  // The ratio returned by this function is used to position the touch handle
  // targetable area relative to the focal point (e.g. bottom of text caret).
  // So, even though padding is applied on both the left and right of the handle
  // image, we compute the ratio based on the padding on only one side.
  return kSelectionHandlePadding /
         (window_->bounds().width() + 2.0f * kSelectionHandlePadding);
}

void TouchHandleDrawableAura::OnPaintLayer(const PaintContext& context) {
  PaintRecorder recorder(context, window_->bounds().size());
  if (!handle_image_.IsEmpty()) {
    recorder.canvas()->DrawImageInt(
        handle_image_.Rasterize(ColorProviderManager::Get().GetColorProviderFor(
            NativeTheme::GetInstanceForNativeUi()->GetColorProviderKey(
                nullptr))),
        0, 0);
  }
}

void TouchHandleDrawableAura::OnNativeThemeUpdated(
    NativeTheme* observed_theme) {
  window_->SchedulePaintInRect(gfx::Rect(window_->bounds().size()));
}

#if BUILDFLAG(ARKWEB_CLIPBOARD)
void TouchHandleDrawableAura::SetEdge(const gfx::PointF& top,
                                      const gfx::PointF& bottom) {}
#endif
}  // namespace ui