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

#include "chrome/browser/ash/arc/input_overlay/ui/action_view.h"

#include <algorithm>

#include "ash/app_list/app_list_util.h"
#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/arc/input_overlay/actions/action.h"
#include "chrome/browser/ash/arc/input_overlay/actions/input_element.h"
#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h"
#include "chrome/browser/ash/arc/input_overlay/constants.h"
#include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h"
#include "chrome/browser/ash/arc/input_overlay/touch_injector.h"
#include "chrome/browser/ash/arc/input_overlay/ui/action_label.h"
#include "chrome/browser/ash/arc/input_overlay/ui/arrow_container.h"
#include "chrome/browser/ash/arc/input_overlay/ui/reposition_controller.h"
#include "chrome/browser/ash/arc/input_overlay/ui/touch_point.h"
#include "chrome/browser/ash/arc/input_overlay/util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"

namespace arc::input_overlay {

namespace {
constexpr int kAttachMargin = 8;
}

ActionView::ActionView(Action* action,
                       DisplayOverlayController* display_overlay_controller)
    : views::View(),
      action_(action),
      display_overlay_controller_(display_overlay_controller) {}
ActionView::~ActionView() = default;

void ActionView::OnActionInputBindingUpdated() {
  SetViewContent(BindingOption::kCurrent);
}

void ActionView::OnContentBoundsSizeChanged() {
  SetPositionFromCenterPosition(action_->GetUICenterPosition());
}

void ActionView::SetDisplayMode(DisplayMode mode, ActionLabel* editing_label) {
  // Set display mode for ActionLabel first and then other components update the
  // layout according to ActionLabel.
  if (!editing_label) {
    for (arc::input_overlay::ActionLabel* label : labels_) {
      label->SetDisplayMode(mode);
    }
  } else {
    editing_label->SetDisplayMode(mode);
  }

  if (mode == DisplayMode::kView) {
    display_mode_ = DisplayMode::kView;
    if (!IsInputBound(action_->GetCurrentDisplayedInput())) {
      SetVisible(false);
    }
    RemoveTouchPoint();
  }
  if (mode == DisplayMode::kEdit) {
    display_mode_ = DisplayMode::kEdit;
    AddTouchPoint();
    if (!IsInputBound(*action_->current_input())) {
      SetVisible(true);
    }
  }
}

void ActionView::SetPositionFromCenterPosition(
    const gfx::PointF& center_position) {
  DCHECK(touch_point_center_);
  const int left =
      std::max(0, (int)(center_position.x() - touch_point_center_->x()));
  const int top =
      std::max(0, (int)(center_position.y() - touch_point_center_->y()));
  // SetPosition function needs the top-left position.
  SetPosition(gfx::Point(left, top));
}

void ActionView::ShowFocusInfoMsg(std::string_view message, views::View* view) {
  view->GetViewAccessibility().SetDescription(base::UTF8ToUTF16(message));
}

void ActionView::ChangeInputBinding(
    Action* action,
    ActionLabel* action_label,
    std::unique_ptr<InputElement> input_element) {
  display_overlay_controller_->OnInputBindingChange(action,
                                                    std::move(input_element));
  SetDisplayMode(DisplayMode::kEdit, action_label);
}

void ActionView::RemoveNewState() {
  for (arc::input_overlay::ActionLabel* label : labels_) {
    label->RemoveNewState();
  }
}

void ActionView::ApplyMousePressed(const ui::MouseEvent& event) {
  reposition_controller_->OnMousePressed(event);
}

void ActionView::ApplyMouseDragged(const ui::MouseEvent& event) {
  reposition_controller_->OnMouseDragged(event);
}

void ActionView::ApplyMouseReleased(const ui::MouseEvent& event) {
  if (!reposition_controller_->OnMouseReleased(event)) {
    ShowButtonOptionsMenu();
  }
}

void ActionView::ApplyGestureEvent(ui::GestureEvent* event) {
  if (!reposition_controller_->OnGestureEvent(event)) {
    ShowButtonOptionsMenu();
  }
}

bool ActionView::ApplyKeyPressed(const ui::KeyEvent& event) {
  return reposition_controller_->OnKeyPressed(event);
}

bool ActionView::ApplyKeyReleased(const ui::KeyEvent& event) {
  return reposition_controller_->OnKeyReleased(event);
}

void ActionView::ShowButtonOptionsMenu() {
  DCHECK(display_overlay_controller_);
  display_overlay_controller_->AddButtonOptionsMenuWidget(action_);
}

void ActionView::OnDraggingCallback() {
  MayUpdateLabelPosition();
  display_overlay_controller_->SetButtonOptionsMenuWidgetVisibility(
      /*is_visible=*/false);
}

void ActionView::OnMouseDragEndCallback() {
  action_->BindPosition(GetTouchCenterInWindow());
  display_overlay_controller_->SetButtonOptionsMenuWidgetVisibility(
      /*is_visible=*/true);

  RecordInputOverlayActionReposition(
      display_overlay_controller_->GetPackageName(),
      RepositionType::kMouseDragRepostion,
      display_overlay_controller_->GetWindowStateType());
}

void ActionView::OnGestureDragEndCallback() {
  action_->BindPosition(GetTouchCenterInWindow());
  display_overlay_controller_->SetButtonOptionsMenuWidgetVisibility(
      /*is_visible=*/true);

  RecordInputOverlayActionReposition(
      display_overlay_controller_->GetPackageName(),
      RepositionType::kTouchscreenDragRepostion,
      display_overlay_controller_->GetWindowStateType());
}

void ActionView::OnKeyPressedCallback() {
  MayUpdateLabelPosition();
}

void ActionView::OnKeyReleasedCallback() {
  action_->BindPosition(GetTouchCenterInWindow());
  RecordInputOverlayActionReposition(
      display_overlay_controller_->GetPackageName(),
      RepositionType::kKeyboardArrowKeyReposition,
      display_overlay_controller_->GetWindowStateType());
}

void ActionView::SetTouchPointCenter(const gfx::Point& touch_point_center) {
  touch_point_center_ = touch_point_center;
  if (touch_point_) {
    touch_point_->OnCenterPositionChanged(*touch_point_center_);
  }
}

void ActionView::AddTouchPoint(ActionType action_type) {
  if (touch_point_) {
    return;
  }

  DCHECK(touch_point_center_);
  touch_point_ = TouchPoint::Show(this, action_type, *touch_point_center_);
}

void ActionView::RemoveTouchPoint() {
  if (!touch_point_) {
    return;
  }

  RemoveChildViewT(touch_point_);
  touch_point_ = nullptr;
}

gfx::Point ActionView::GetTouchCenterInWindow() const {
  if (!touch_point_center_) {
    auto point = action_->GetUICenterPosition();
    return gfx::Point(point.x(), point.y());
  }

  auto pos = *touch_point_center_;
  pos.Offset(origin().x(), origin().y());
  return pos;
}

gfx::Point ActionView::CalculateAttachViewPositionInRootWindow(
    const gfx::Rect& available_bounds,
    const gfx::Point& window_content_origin,
    ArrowContainer* attached_view) const {
  auto origin_in_window = origin();
  origin_in_window.Offset(window_content_origin.x(), window_content_origin.y());

  // Check if `attached_view` can be placed on the left side or right side of
  // this view. It depends on if there is enough space in its own display. If
  // there is enough space on both sides, `can_attach_on_left` and
  // `can_attach_on_right` are true.
  bool can_attach_on_left = false, can_attach_on_right = false;

  const auto attached_view_size = attached_view->GetPreferredSize();
  // Width of `attached_view` including the margin of this view.
  const int attached_view_width_extra =
      kAttachMargin + attached_view_size.width();
  if (origin_in_window.x() + width() + attached_view_width_extra <=
      available_bounds.width()) {
    can_attach_on_right = true;
  }

  if (origin_in_window.x() - attached_view_width_extra >= 0) {
    can_attach_on_left = true;
  }

  // Calculate the position of x.
  int x = 0;
  const auto touch_center_in_window = GetTouchCenterInWindow();
  // If the display space is not considered, the position of `attached_view` is
  // toward to the center of the game window, which means if this view is on the
  // left of the window, then `attached_view` should be placed on the right side
  // of this view.
  bool should_attach_on_right =
      touch_center_in_window.x() < parent()->size().width() / 2.0;

  // `final_attach_on_left` is the final decision based on
  // `should_attach_on_right`, `can_attach_on_left` and `can_attach_on_right`.
  bool final_attach_on_left = false;
  if (should_attach_on_right) {
    if (!can_attach_on_right && can_attach_on_left) {
      // Attach `attached_view` on the left side of this view.
      x = origin_in_window.x() - attached_view_width_extra;
      final_attach_on_left = true;
    } else {
      // Attach `attached_view` on the right side of this view.
      x = origin_in_window.x() + width() + kAttachMargin;
      if (x + attached_view_size.width() > available_bounds.width()) {
        x = available_bounds.width() - attached_view_size.width();
      }
    }
  } else {
    if (!can_attach_on_left && can_attach_on_right) {
      // Attach `attached_view` on the right side of this view.
      x = origin_in_window.x() + width() + kAttachMargin;
    } else {
      // Attach `attached_view` on the left side of this view.
      x = std::max(0, origin_in_window.x() - attached_view_width_extra);
      final_attach_on_left = true;
    }
  }

  attached_view->SetArrowOnLeft(!final_attach_on_left);

  // Check y position to make sure that `attached_view` shows completely inside
  // of the display.
  int y = std::max(0, window_content_origin.y() + touch_center_in_window.y() -
                          attached_view_size.height() / 2);
  y = std::min(y, available_bounds.height() - attached_view_size.height());
  attached_view->SetArrowVerticalOffset(
      touch_center_in_window.y() -
      (y - window_content_origin.y() + attached_view_size.height() / 2));
  return gfx::Point(x, y);
}

void ActionView::AddedToWidget() {
  SetRepositionController();
}

void ActionView::SetRepositionController() {
  if (reposition_controller_) {
    return;
  }
  reposition_controller_ = std::make_unique<RepositionController>(this);
  reposition_controller_->set_dragging_callback(base::BindRepeating(
      &ActionView::OnDraggingCallback, base::Unretained(this)));
  reposition_controller_->set_mouse_drag_end_callback(base::BindRepeating(
      &ActionView::OnMouseDragEndCallback, base::Unretained(this)));
  reposition_controller_->set_gesture_drag_end_callback(base::BindRepeating(
      &ActionView::OnGestureDragEndCallback, base::Unretained(this)));
  reposition_controller_->set_key_pressed_callback(base::BindRepeating(
      &ActionView::OnKeyPressedCallback, base::Unretained(this)));
  reposition_controller_->set_key_released_callback(base::BindRepeating(
      &ActionView::OnKeyReleasedCallback, base::Unretained(this)));
}

BEGIN_METADATA(ActionView)
END_METADATA

}  // namespace arc::input_overlay
