// Copyright (c) 2011 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 "chrome/browser/ui/views/accessibility_event_router_views.h"

#include "base/basictypes.h"
#include "base/callback.h"
#include "base/message_loop.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_accessibility_api.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "content/common/notification_type.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/accessibility/accessible_view_state.h"
#include "views/controls/button/checkbox.h"
#include "views/controls/button/custom_button.h"
#include "views/controls/button/menu_button.h"
#include "views/controls/button/native_button.h"
#include "views/controls/combobox/combobox.h"
#include "views/controls/link.h"
#include "views/controls/menu/menu_item_view.h"
#include "views/controls/menu/submenu_view.h"
#include "views/controls/textfield/textfield.h"
#include "views/view.h"
#include "views/widget/native_widget.h"
#include "views/widget/widget.h"
#include "views/window/window.h"

using views::FocusManager;

AccessibilityEventRouterViews::AccessibilityEventRouterViews()
    : most_recent_profile_(NULL),
      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
}

AccessibilityEventRouterViews::~AccessibilityEventRouterViews() {
}

// static
AccessibilityEventRouterViews* AccessibilityEventRouterViews::GetInstance() {
  return Singleton<AccessibilityEventRouterViews>::get();
}

void AccessibilityEventRouterViews::HandleAccessibilityEvent(
    views::View* view, ui::AccessibilityTypes::Event event_type) {
  if (!ExtensionAccessibilityEventRouter::GetInstance()->
      IsAccessibilityEnabled()) {
    return;
  }

  switch (event_type) {
    case ui::AccessibilityTypes::EVENT_FOCUS:
      DispatchAccessibilityNotification(
          view, NotificationType::ACCESSIBILITY_CONTROL_FOCUSED);
      break;
    case ui::AccessibilityTypes::EVENT_MENUSTART:
    case ui::AccessibilityTypes::EVENT_MENUPOPUPSTART:
      DispatchAccessibilityNotification(
          view, NotificationType::ACCESSIBILITY_MENU_OPENED);
      break;
    case ui::AccessibilityTypes::EVENT_MENUEND:
    case ui::AccessibilityTypes::EVENT_MENUPOPUPEND:
      DispatchAccessibilityNotification(
          view, NotificationType::ACCESSIBILITY_MENU_CLOSED);
      break;
    case ui::AccessibilityTypes::EVENT_TEXT_CHANGED:
    case ui::AccessibilityTypes::EVENT_SELECTION_CHANGED:
      DispatchAccessibilityNotification(
          view, NotificationType::ACCESSIBILITY_TEXT_CHANGED);
      break;
    case ui::AccessibilityTypes::EVENT_VALUE_CHANGED:
      DispatchAccessibilityNotification(
          view, NotificationType::ACCESSIBILITY_CONTROL_ACTION);
      break;
    case ui::AccessibilityTypes::EVENT_ALERT:
    case ui::AccessibilityTypes::EVENT_NAME_CHANGED:
      // TODO(dmazzoni): re-evaluate this list later and see
      // if supporting any of these would be useful feature requests or
      // they'd just be superfluous.
      NOTIMPLEMENTED();
      break;
  }
}

void AccessibilityEventRouterViews::HandleMenuItemFocused(
    const std::wstring& menu_name,
    const std::wstring& menu_item_name,
    int item_index,
    int item_count,
    bool has_submenu) {
  if (!ExtensionAccessibilityEventRouter::GetInstance()->
      IsAccessibilityEnabled()) {
    return;
  }

  if (!most_recent_profile_)
    return;

  AccessibilityMenuItemInfo info(
      most_recent_profile_,
      WideToUTF8(menu_item_name),
      has_submenu,
      item_index,
      item_count);
  SendAccessibilityNotification(
      NotificationType::ACCESSIBILITY_CONTROL_FOCUSED, &info);
}

//
// Private methods
//

std::string AccessibilityEventRouterViews::GetViewName(views::View* view) {
  ui::AccessibleViewState state;
  view->GetAccessibleState(&state);
  return UTF16ToUTF8(state.name);
}

void AccessibilityEventRouterViews::DispatchAccessibilityNotification(
    views::View* view, NotificationType type) {
  // Get the profile associated with this view. If it's not found, use
  // the most recent profile where accessibility events were sent, or
  // the default profile.
  Profile* profile = NULL;
  views::Window* window = view->GetWindow();
  if (window) {
    profile = reinterpret_cast<Profile*>(
        window->AsWidget()->native_widget()->GetNativeWindowProperty(
            Profile::kProfileKey));
  }
  if (!profile)
    profile = most_recent_profile_;
  if (!profile)
    profile = g_browser_process->profile_manager()->GetDefaultProfile();
  if (!profile) {
    NOTREACHED();
    return;
  }

  most_recent_profile_ = profile;
  std::string class_name = view->GetClassName();

  if (class_name == views::Checkbox::kViewClassName) {
    SendCheckboxNotification(view, type, profile);
  } else if (class_name == views::MenuButton::kViewClassName ||
      type == NotificationType::ACCESSIBILITY_MENU_OPENED ||
      type == NotificationType::ACCESSIBILITY_MENU_CLOSED) {
    SendMenuNotification(view, type, profile);
  } else if (IsMenuEvent(view, type)) {
    SendMenuItemNotification(view, type, profile);
  } else if (class_name == views::CustomButton::kViewClassName ||
             class_name == views::NativeButton::kViewClassName ||
             class_name == views::TextButton::kViewClassName) {
    SendButtonNotification(view, type, profile);
  } else if (class_name == views::Link::kViewClassName) {
    SendLinkNotification(view, type, profile);
  } else if (class_name == LocationBarView::kViewClassName) {
    SendLocationBarNotification(view, type, profile);
  } else if (class_name == views::Textfield::kViewClassName) {
    SendTextfieldNotification(view, type, profile);
  } else if (class_name == views::Combobox::kViewClassName) {
    SendComboboxNotification(view, type, profile);
  }
}

void AccessibilityEventRouterViews::SendButtonNotification(
    views::View* view, NotificationType type, Profile* profile) {
  AccessibilityButtonInfo info(profile, GetViewName(view));
  SendAccessibilityNotification(type, &info);
}

void AccessibilityEventRouterViews::SendLinkNotification(
    views::View* view, NotificationType type, Profile* profile) {
  AccessibilityLinkInfo info(profile, GetViewName(view));
  SendAccessibilityNotification(type, &info);
}

void AccessibilityEventRouterViews::SendMenuNotification(
    views::View* view, NotificationType type, Profile* profile) {
  AccessibilityMenuInfo info(profile, GetViewName(view));
  SendAccessibilityNotification(type, &info);
}

void AccessibilityEventRouterViews::SendMenuItemNotification(
    views::View* view, NotificationType type, Profile* profile) {
  std::string name = GetViewName(view);

  bool has_submenu = false;
  int index = -1;
  int count = -1;

  if (view->GetClassName() == views::MenuItemView::kViewClassName)
    has_submenu = static_cast<views::MenuItemView*>(view)->HasSubmenu();

  views::View* parent_menu = view->parent();
  while (parent_menu != NULL && parent_menu->GetClassName() !=
         views::SubmenuView::kViewClassName) {
    parent_menu = parent_menu->parent();
  }
  if (parent_menu) {
    count = 0;
    RecursiveGetMenuItemIndexAndCount(parent_menu, view, &index, &count);
  }

  AccessibilityMenuItemInfo info(profile, name, has_submenu, index, count);
  SendAccessibilityNotification(type, &info);
}

void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
    views::View* menu, views::View* item, int* index, int* count) {
  for (int i = 0; i < menu->child_count(); ++i) {
    views::View* child = menu->GetChildViewAt(i);
    int previous_count = *count;
    RecursiveGetMenuItemIndexAndCount(child, item, index, count);
    if (child->GetClassName() == views::MenuItemView::kViewClassName &&
        *count == previous_count) {
      if (item == child)
        *index = *count;
      (*count)++;
    } else if (child->GetClassName() == views::TextButton::kViewClassName) {
      if (item == child)
        *index = *count;
      (*count)++;
    }
  }
}

bool AccessibilityEventRouterViews::IsMenuEvent(
    views::View* view, NotificationType type) {
  if (type == NotificationType::ACCESSIBILITY_MENU_OPENED ||
      type == NotificationType::ACCESSIBILITY_MENU_CLOSED)
    return true;

  while (view) {
    ui::AccessibleViewState state;
    view->GetAccessibleState(&state);
    ui::AccessibilityTypes::Role role = state.role;
    if (role == ui::AccessibilityTypes::ROLE_MENUITEM ||
        role == ui::AccessibilityTypes::ROLE_MENUPOPUP) {
      return true;
    }
    view = view->parent();
  }

  return false;
}

void AccessibilityEventRouterViews::SendLocationBarNotification(
    views::View* view, NotificationType type, Profile* profile) {
  ui::AccessibleViewState state;
  view->GetAccessibleState(&state);
  std::string name = UTF16ToUTF8(state.name);
  AccessibilityTextBoxInfo info(profile, name, false);
  std::string value = UTF16ToUTF8(state.value);
  info.SetValue(value, state.selection_start, state.selection_end);
  SendAccessibilityNotification(type, &info);
}

void AccessibilityEventRouterViews::SendTextfieldNotification(
    views::View* view, NotificationType type, Profile* profile) {
  ui::AccessibleViewState state;
  view->GetAccessibleState(&state);
  std::string name = UTF16ToUTF8(state.name);
  views::Textfield* textfield = static_cast<views::Textfield*>(view);
  bool password = textfield->IsPassword();
  AccessibilityTextBoxInfo info(profile, name, password);
  std::string value = UTF16ToUTF8(state.value);
  info.SetValue(value, state.selection_start, state.selection_end);
  SendAccessibilityNotification(type, &info);
}

void AccessibilityEventRouterViews::SendComboboxNotification(
    views::View* view, NotificationType type, Profile* profile) {
  ui::AccessibleViewState state;
  view->GetAccessibleState(&state);
  std::string name = UTF16ToUTF8(state.name);
  std::string value = UTF16ToUTF8(state.value);
  AccessibilityComboBoxInfo info(
      profile, name, value, state.index, state.count);
  SendAccessibilityNotification(type, &info);
}

void AccessibilityEventRouterViews::SendCheckboxNotification(
    views::View* view, NotificationType type, Profile* profile) {
  ui::AccessibleViewState state;
  view->GetAccessibleState(&state);
  std::string name = UTF16ToUTF8(state.name);
  std::string value = UTF16ToUTF8(state.value);
  AccessibilityCheckboxInfo info(
      profile, name, state.state == ui::AccessibilityTypes::STATE_CHECKED);
  SendAccessibilityNotification(type, &info);
}