// 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/extensions/extension_tabs_module.h"

#include "base/json/json_writer.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_accessibility_api.h"
#include "chrome/browser/extensions/extension_accessibility_api_constants.h"
#include "chrome/browser/extensions/extension_event_router.h"
#include "chrome/browser/extensions/extension_function_dispatcher.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/extensions/extension.h"
#include "content/common/notification_service.h"

namespace keys = extension_accessibility_api_constants;

// Returns the AccessibilityControlInfo serialized into a JSON string,
// consisting of an array of a single object of type AccessibilityObject,
// as defined in the accessibility extension api's json schema.
std::string ControlInfoToJsonString(const AccessibilityControlInfo* info) {
  ListValue args;
  DictionaryValue* dict = new DictionaryValue();
  info->SerializeToDict(dict);
  args.Append(dict);
  std::string json_args;
  base::JSONWriter::Write(&args, false, &json_args);
  return json_args;
}

ExtensionAccessibilityEventRouter*
    ExtensionAccessibilityEventRouter::GetInstance() {
  return Singleton<ExtensionAccessibilityEventRouter>::get();
}

ExtensionAccessibilityEventRouter::ExtensionAccessibilityEventRouter()
    : enabled_(false) {}

ExtensionAccessibilityEventRouter::~ExtensionAccessibilityEventRouter() {
  STLDeleteElements(&on_enabled_listeners_);
  STLDeleteElements(&on_disabled_listeners_);
}

void ExtensionAccessibilityEventRouter::ObserveProfile(Profile* profile) {
  last_focused_control_dict_.Clear();

  if (registrar_.IsEmpty()) {
    registrar_.Add(this,
                   NotificationType::ACCESSIBILITY_WINDOW_OPENED,
                   NotificationService::AllSources());
    registrar_.Add(this,
                   NotificationType::ACCESSIBILITY_WINDOW_CLOSED,
                   NotificationService::AllSources());
    registrar_.Add(this,
                   NotificationType::ACCESSIBILITY_CONTROL_FOCUSED,
                   NotificationService::AllSources());
    registrar_.Add(this,
                   NotificationType::ACCESSIBILITY_CONTROL_ACTION,
                   NotificationService::AllSources());
    registrar_.Add(this,
                   NotificationType::ACCESSIBILITY_TEXT_CHANGED,
                   NotificationService::AllSources());
    registrar_.Add(this,
                   NotificationType::ACCESSIBILITY_MENU_OPENED,
                   NotificationService::AllSources());
    registrar_.Add(this,
                   NotificationType::ACCESSIBILITY_MENU_CLOSED,
                   NotificationService::AllSources());
  }
}

void ExtensionAccessibilityEventRouter::Observe(
    NotificationType type,
    const NotificationSource& source,
    const NotificationDetails& details) {
  switch (type.value) {
    case NotificationType::ACCESSIBILITY_WINDOW_OPENED:
      OnWindowOpened(Details<const AccessibilityWindowInfo>(details).ptr());
      break;
    case NotificationType::ACCESSIBILITY_WINDOW_CLOSED:
      OnWindowClosed(Details<const AccessibilityWindowInfo>(details).ptr());
      break;
    case NotificationType::ACCESSIBILITY_CONTROL_FOCUSED:
      OnControlFocused(Details<const AccessibilityControlInfo>(details).ptr());
      break;
    case NotificationType::ACCESSIBILITY_CONTROL_ACTION:
      OnControlAction(Details<const AccessibilityControlInfo>(details).ptr());
      break;
    case NotificationType::ACCESSIBILITY_TEXT_CHANGED:
      OnTextChanged(Details<const AccessibilityControlInfo>(details).ptr());
      break;
    case NotificationType::ACCESSIBILITY_MENU_OPENED:
      OnMenuOpened(Details<const AccessibilityMenuInfo>(details).ptr());
      break;
    case NotificationType::ACCESSIBILITY_MENU_CLOSED:
      OnMenuClosed(Details<const AccessibilityMenuInfo>(details).ptr());
      break;
    default:
      NOTREACHED();
  }
}

void ExtensionAccessibilityEventRouter::SetAccessibilityEnabled(bool enabled) {
  if (enabled_ != enabled) {
    enabled_ = enabled;
    if (enabled_) {
      for (unsigned int i = 0; i < on_enabled_listeners_.size(); i++) {
        on_enabled_listeners_[i]->Run();
      }
    } else {
      for (unsigned int i = 0; i < on_disabled_listeners_.size(); i++) {
        on_disabled_listeners_[i]->Run();
      }
    }
  }
}

bool ExtensionAccessibilityEventRouter::IsAccessibilityEnabled() const {
  return enabled_;
}

void ExtensionAccessibilityEventRouter::AddOnEnabledListener(
    Callback* callback) {
  on_enabled_listeners_.push_back(callback);
}

void ExtensionAccessibilityEventRouter::AddOnDisabledListener(
    Callback* callback) {
  on_disabled_listeners_.push_back(callback);
}

void ExtensionAccessibilityEventRouter::OnWindowOpened(
    const AccessibilityWindowInfo* info) {
  std::string json_args = ControlInfoToJsonString(info);
  DispatchEvent(info->profile(), keys::kOnWindowOpened, json_args);
}

void ExtensionAccessibilityEventRouter::OnWindowClosed(
    const AccessibilityWindowInfo* info) {
  std::string json_args = ControlInfoToJsonString(info);
  DispatchEvent(info->profile(), keys::kOnWindowClosed, json_args);
}

void ExtensionAccessibilityEventRouter::OnControlFocused(
    const AccessibilityControlInfo* info) {
  last_focused_control_dict_.Clear();
  info->SerializeToDict(&last_focused_control_dict_);
  std::string json_args = ControlInfoToJsonString(info);
  DispatchEvent(info->profile(), keys::kOnControlFocused, json_args);
}

void ExtensionAccessibilityEventRouter::OnControlAction(
    const AccessibilityControlInfo* info) {
  std::string json_args = ControlInfoToJsonString(info);
  DispatchEvent(info->profile(), keys::kOnControlAction, json_args);
}

void ExtensionAccessibilityEventRouter::OnTextChanged(
    const AccessibilityControlInfo* info) {
  std::string json_args = ControlInfoToJsonString(info);
  DispatchEvent(info->profile(), keys::kOnTextChanged, json_args);
}

void ExtensionAccessibilityEventRouter::OnMenuOpened(
    const AccessibilityMenuInfo* info) {
  std::string json_args = ControlInfoToJsonString(info);
  DispatchEvent(info->profile(), keys::kOnMenuOpened, json_args);
}

void ExtensionAccessibilityEventRouter::OnMenuClosed(
    const AccessibilityMenuInfo* info) {
  std::string json_args = ControlInfoToJsonString(info);
  DispatchEvent(info->profile(), keys::kOnMenuClosed, json_args);
}

void ExtensionAccessibilityEventRouter::DispatchEvent(
    Profile* profile,
    const char* event_name,
    const std::string& json_args) {
  if (enabled_ && profile && profile->GetExtensionEventRouter()) {
    profile->GetExtensionEventRouter()->DispatchEventToRenderers(
        event_name, json_args, NULL, GURL());
  }
}

bool SetAccessibilityEnabledFunction::RunImpl() {
  bool enabled;
  EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &enabled));
  ExtensionAccessibilityEventRouter::GetInstance()
      ->SetAccessibilityEnabled(enabled);
  return true;
}

bool GetFocusedControlFunction::RunImpl() {
  // Get the serialized dict from the last focused control and return it.
  // However, if the dict is empty, that means we haven't seen any focus
  // events yet, so return null instead.
  ExtensionAccessibilityEventRouter *accessibility_event_router =
      ExtensionAccessibilityEventRouter::GetInstance();
  DictionaryValue *last_focused_control_dict =
      accessibility_event_router->last_focused_control_dict();
  if (last_focused_control_dict->size()) {
    result_.reset(last_focused_control_dict->DeepCopyWithoutEmptyChildren());
  } else {
    result_.reset(Value::CreateNullValue());
  }
  return true;
}