// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/accessibility/native_view_accessibility_base.h"

#include "base/memory/ptr_util.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/controls/native/native_view_host.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"

namespace views {

namespace {

bool IsAccessibilityFocusableWhenEnabled(View* view) {
  return view->focus_behavior() != View::FocusBehavior::NEVER &&
         view->IsDrawn();
}

// Used to determine if a View should be ignored by accessibility clients by
// being a non-keyboard-focusable child of a keyboard-focusable ancestor. E.g.,
// LabelButtons contain Labels, but a11y should just show that there's a button.
bool IsViewUnfocusableChildOfFocusableAncestor(View* view) {
  if (IsAccessibilityFocusableWhenEnabled(view))
    return false;

  while (view->parent()) {
    view = view->parent();
    if (IsAccessibilityFocusableWhenEnabled(view))
      return true;
  }
  return false;
}

ui::AXPlatformNode* FromNativeWindow(gfx::NativeWindow native_window) {
  Widget* widget = Widget::GetWidgetForNativeWindow(native_window);
  if (!widget)
    return nullptr;

  View* view = widget->GetRootView();
  if (!view)
    return nullptr;

  gfx::NativeViewAccessible native_view_accessible =
      view->GetNativeViewAccessible();
  if (!native_view_accessible)
    return nullptr;

  return ui::AXPlatformNode::FromNativeViewAccessible(native_view_accessible);
}

}  // namespace

NativeViewAccessibilityBase::NativeViewAccessibilityBase(View* view)
    : ViewAccessibility(view) {
  ax_node_ = ui::AXPlatformNode::Create(this);
  DCHECK(ax_node_);

  static bool first_time = true;
  if (first_time) {
    ui::AXPlatformNode::RegisterNativeWindowHandler(
        base::BindRepeating(&FromNativeWindow));
    first_time = false;
  }
}

NativeViewAccessibilityBase::~NativeViewAccessibilityBase() {
  ax_node_->Destroy();
}

gfx::NativeViewAccessible NativeViewAccessibilityBase::GetNativeObject() {
  return ax_node_->GetNativeViewAccessible();
}

void NativeViewAccessibilityBase::NotifyAccessibilityEvent(
    ui::AXEvent event_type) {
  ax_node_->NotifyAccessibilityEvent(event_type);
}

// ui::AXPlatformNodeDelegate

const ui::AXNodeData& NativeViewAccessibilityBase::GetData() const {
  // Clear it, then populate it.
  data_ = ui::AXNodeData();
  GetAccessibleNodeData(&data_);

  // View::IsDrawn is true if a View is visible and all of its ancestors are
  // visible too, since invisibility inherits.
  //
  // TODO(dmazzoni): Maybe consider moving this to ViewAccessibility?
  // This will require ensuring that Chrome OS invalidates the whole
  // subtree when a View changes its visibility state.
  if (!view()->IsDrawn())
    data_.AddState(ui::AX_STATE_INVISIBLE);

  // Make sure this element is excluded from the a11y tree if there's a
  // focusable parent. All keyboard focusable elements should be leaf nodes.
  // Exceptions to this rule will themselves be accessibility focusable.
  //
  // TODO(dmazzoni): this code was added to support MacViews acccessibility,
  // because we needed a way to mark a View as a leaf node in the
  // accessibility tree. We need to replace this with a cross-platform
  // solution that works for ChromeVox, too, and move it to ViewAccessibility.
  if (IsViewUnfocusableChildOfFocusableAncestor(view()))
    data_.role = ui::AX_ROLE_IGNORED;

  return data_;
}

const ui::AXTreeData& NativeViewAccessibilityBase::GetTreeData() const {
  CR_DEFINE_STATIC_LOCAL(ui::AXTreeData, empty_data, ());
  return empty_data;
}

int NativeViewAccessibilityBase::GetChildCount() {
  int child_count = view()->child_count();

  std::vector<Widget*> child_widgets;
  PopulateChildWidgetVector(&child_widgets);
  child_count += child_widgets.size();

  return child_count;
}

gfx::NativeViewAccessible NativeViewAccessibilityBase::ChildAtIndex(int index) {
  // If this is a root view, our widget might have child widgets. Include
  std::vector<Widget*> child_widgets;
  PopulateChildWidgetVector(&child_widgets);
  int child_widget_count = static_cast<int>(child_widgets.size());

  if (index < view()->child_count()) {
    return view()->child_at(index)->GetNativeViewAccessible();
  } else if (index < view()->child_count() + child_widget_count) {
    Widget* child_widget = child_widgets[index - view()->child_count()];
    return child_widget->GetRootView()->GetNativeViewAccessible();
  }

  return nullptr;
}

gfx::NativeWindow NativeViewAccessibilityBase::GetTopLevelWidget() {
  if (view()->GetWidget())
    return view()->GetWidget()->GetTopLevelWidget()->GetNativeWindow();
  return nullptr;
}

gfx::NativeViewAccessible NativeViewAccessibilityBase::GetParent() {
  if (view()->parent())
    return view()->parent()->GetNativeViewAccessible();

  if (Widget* widget = view()->GetWidget()) {
    Widget* top_widget = widget->GetTopLevelWidget();
    if (top_widget && widget != top_widget && top_widget->GetRootView())
      return top_widget->GetRootView()->GetNativeViewAccessible();
  }

  return nullptr;
}

gfx::Rect NativeViewAccessibilityBase::GetScreenBoundsRect() const {
  return view()->GetBoundsInScreen();
}

gfx::NativeViewAccessible NativeViewAccessibilityBase::HitTestSync(int x,
                                                                   int y) {
  if (!view() || !view()->GetWidget())
    return nullptr;

  // Search child widgets first, since they're on top in the z-order.
  std::vector<Widget*> child_widgets;
  PopulateChildWidgetVector(&child_widgets);
  for (Widget* child_widget : child_widgets) {
    View* child_root_view = child_widget->GetRootView();
    gfx::Point point(x, y);
    View::ConvertPointFromScreen(child_root_view, &point);
    if (child_root_view->HitTestPoint(point))
      return child_root_view->GetNativeViewAccessible();
  }

  gfx::Point point(x, y);
  View::ConvertPointFromScreen(view(), &point);
  if (!view()->HitTestPoint(point))
    return nullptr;

  // Check if the point is within any of the immediate children of this
  // view. We don't have to search further because AXPlatformNode will
  // do a recursive hit test if we return anything other than |this| or NULL.
  for (int i = view()->child_count() - 1; i >= 0; --i) {
    View* child_view = view()->child_at(i);
    if (!child_view->visible())
      continue;

    gfx::Point point_in_child_coords(point);
    view()->ConvertPointToTarget(view(), child_view, &point_in_child_coords);
    if (child_view->HitTestPoint(point_in_child_coords))
      return child_view->GetNativeViewAccessible();
  }

  // If it's not inside any of our children, it's inside this view.
  return GetNativeObject();
}

gfx::NativeViewAccessible NativeViewAccessibilityBase::GetFocus() {
  FocusManager* focus_manager = view()->GetFocusManager();
  View* focused_view =
      focus_manager ? focus_manager->GetFocusedView() : nullptr;
  return focused_view ? focused_view->GetNativeViewAccessible() : nullptr;
}

ui::AXPlatformNode* NativeViewAccessibilityBase::GetFromNodeID(int32_t id) {
  return nullptr;
}

int NativeViewAccessibilityBase::GetIndexInParent() const {
  return -1;
}

gfx::AcceleratedWidget
NativeViewAccessibilityBase::GetTargetForNativeAccessibilityEvent() {
  return gfx::kNullAcceleratedWidget;
}

bool NativeViewAccessibilityBase::AccessibilityPerformAction(
    const ui::AXActionData& data) {
  return view()->HandleAccessibleAction(data);
}

bool NativeViewAccessibilityBase::ShouldIgnoreHoveredStateForTesting() {
  return false;
}

bool NativeViewAccessibilityBase::IsOffscreen() const {
  // TODO: need to implement.
  return false;
}

std::set<int32_t> NativeViewAccessibilityBase::GetReverseRelations(
    ui::AXIntAttribute attr,
    int32_t dst_id) {
  return std::set<int32_t>();
}

std::set<int32_t> NativeViewAccessibilityBase::GetReverseRelations(
    ui::AXIntListAttribute attr,
    int32_t dst_id) {
  return std::set<int32_t>();
}

const ui::AXUniqueId& NativeViewAccessibilityBase::GetUniqueId() const {
  return ViewAccessibility::GetUniqueId();
}

gfx::RectF NativeViewAccessibilityBase::GetBoundsInScreen() const {
  return gfx::RectF(view()->GetBoundsInScreen());
}

void NativeViewAccessibilityBase::PopulateChildWidgetVector(
    std::vector<Widget*>* result_child_widgets) {
  // Only attach child widgets to the root view.
  Widget* widget = view()->GetWidget();
  // Note that during window close, a Widget may exist in a state where it has
  // no NativeView, but hasn't yet torn down its view hierarchy.
  if (!widget || !widget->GetNativeView() || widget->GetRootView() != view())
    return;

  std::set<Widget*> child_widgets;
  Widget::GetAllOwnedWidgets(widget->GetNativeView(), &child_widgets);
  for (auto iter = child_widgets.begin(); iter != child_widgets.end(); ++iter) {
    Widget* child_widget = *iter;
    DCHECK_NE(widget, child_widget);

    if (!child_widget->IsVisible())
      continue;

    if (widget->GetNativeWindowProperty(kWidgetNativeViewHostKey))
      continue;

    result_child_widgets->push_back(child_widget);
  }
}

}  // namespace views
