// Copyright (c) 2012 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/corewm/compound_event_filter.h"

#include "base/containers/hash_tables.h"
#include "base/logging.h"
#include "ui/aura/client/activation_client.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/env.h"
#include "ui/aura/root_window.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_tracker.h"
#include "ui/base/hit_test.h"
#include "ui/events/event.h"

namespace views {
namespace corewm {

namespace {

bool ShouldHideCursorOnKeyEvent(const ui::KeyEvent& event) {
#if defined(OS_CHROMEOS)
  // All alt and control key commands are ignored.
  if (event.IsAltDown() || event.IsControlDown())
    return false;

  static bool inited = false;
  static base::hash_set<int32> ignored_keys;
  if (!inited) {
    // Modifiers.
    ignored_keys.insert(ui::VKEY_SHIFT);
    ignored_keys.insert(ui::VKEY_CONTROL);
    ignored_keys.insert(ui::VKEY_MENU);

    // Search key == VKEY_LWIN.
    ignored_keys.insert(ui::VKEY_LWIN);

    // Function keys.
    for (int key = ui::VKEY_F1; key <= ui::VKEY_F24; ++key)
      ignored_keys.insert(key);

    // Media keys.
    for (int key = ui::VKEY_BROWSER_BACK; key <= ui::VKEY_MEDIA_LAUNCH_APP2;
         ++key) {
      ignored_keys.insert(key);
    }

#if defined(OS_POSIX)
    ignored_keys.insert(ui::VKEY_WLAN);
    ignored_keys.insert(ui::VKEY_POWER);
    ignored_keys.insert(ui::VKEY_BRIGHTNESS_DOWN);
    ignored_keys.insert(ui::VKEY_BRIGHTNESS_UP);
    ignored_keys.insert(ui::VKEY_KBD_BRIGHTNESS_DOWN);
    ignored_keys.insert(ui::VKEY_KBD_BRIGHTNESS_UP);
#endif

    inited = true;
  }

  if (ignored_keys.count(event.key_code()) > 0)
    return false;

  return true;
#else  // !defined(OS_CHROMEOS)
  return false;
#endif  // defined(OS_CHROMEOS)
}

// Returns true if the cursor should be hidden on touch events.
bool ShouldHideCursorOnTouch() {
#if defined(OS_CHROMEOS)
  return true;
#else
  // Not necessary on windows as windows does it for us. If we do need this
  // funcionality on linux (non-chromeos) we need to make sure
  // CompoundEventFilter shows on the right root (it currently doesn't always).
  return false;
#endif
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// CompoundEventFilter, public:

CompoundEventFilter::CompoundEventFilter() {
}

CompoundEventFilter::~CompoundEventFilter() {
  // Additional filters are not owned by CompoundEventFilter and they
  // should all be removed when running here. |handlers_| has
  // check_empty == true and will DCHECK failure if it is not empty.
}

// static
gfx::NativeCursor CompoundEventFilter::CursorForWindowComponent(
    int window_component) {
  switch (window_component) {
    case HTBOTTOM:
      return ui::kCursorSouthResize;
    case HTBOTTOMLEFT:
      return ui::kCursorSouthWestResize;
    case HTBOTTOMRIGHT:
      return ui::kCursorSouthEastResize;
    case HTLEFT:
      return ui::kCursorWestResize;
    case HTRIGHT:
      return ui::kCursorEastResize;
    case HTTOP:
      return ui::kCursorNorthResize;
    case HTTOPLEFT:
      return ui::kCursorNorthWestResize;
    case HTTOPRIGHT:
      return ui::kCursorNorthEastResize;
    default:
      return ui::kCursorNull;
  }
}

void CompoundEventFilter::AddHandler(ui::EventHandler* handler) {
  handlers_.AddObserver(handler);
}

void CompoundEventFilter::RemoveHandler(ui::EventHandler* handler) {
  handlers_.RemoveObserver(handler);
}

////////////////////////////////////////////////////////////////////////////////
// CompoundEventFilter, private:

void CompoundEventFilter::UpdateCursor(aura::Window* target,
                                       ui::MouseEvent* event) {
  // If drag and drop is in progress, let the drag drop client set the cursor
  // instead of setting the cursor here.
  aura::Window* root_window = target->GetRootWindow();
  aura::client::DragDropClient* drag_drop_client =
      aura::client::GetDragDropClient(root_window);
  if (drag_drop_client && drag_drop_client->IsDragDropInProgress())
    return;

  aura::client::CursorClient* cursor_client =
      aura::client::GetCursorClient(root_window);
  if (cursor_client) {
    gfx::NativeCursor cursor = target->GetCursor(event->location());
    if (event->flags() & ui::EF_IS_NON_CLIENT) {
      int window_component =
          target->delegate()->GetNonClientComponent(event->location());
      cursor = CursorForWindowComponent(window_component);
    }

    cursor_client->SetCursor(cursor);
  }
}

void CompoundEventFilter::FilterKeyEvent(ui::KeyEvent* event) {
  if (handlers_.might_have_observers()) {
    ObserverListBase<ui::EventHandler>::Iterator it(handlers_);
    ui::EventHandler* handler;
    while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL)
      handler->OnKeyEvent(event);
  }
}

void CompoundEventFilter::FilterMouseEvent(ui::MouseEvent* event) {
  if (handlers_.might_have_observers()) {
    ObserverListBase<ui::EventHandler>::Iterator it(handlers_);
    ui::EventHandler* handler;
    while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL)
      handler->OnMouseEvent(event);
  }
}

void CompoundEventFilter::FilterTouchEvent(ui::TouchEvent* event) {
  if (handlers_.might_have_observers()) {
    ObserverListBase<ui::EventHandler>::Iterator it(handlers_);
    ui::EventHandler* handler;
    while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL)
      handler->OnTouchEvent(event);
  }
}

void CompoundEventFilter::SetCursorVisibilityOnEvent(aura::Window* target,
                                                     ui::Event* event,
                                                     bool show) {
  if (event->flags() & ui::EF_IS_SYNTHESIZED)
    return;

  aura::client::CursorClient* client =
      aura::client::GetCursorClient(target->GetRootWindow());
  if (!client)
    return;

  if (show)
    client->ShowCursor();
  else
    client->HideCursor();
}

void CompoundEventFilter::SetMouseEventsEnableStateOnEvent(aura::Window* target,
                                                           ui::Event* event,
                                                           bool enable) {
  if (event->flags() & ui::EF_IS_SYNTHESIZED)
    return;
  aura::client::CursorClient* client =
      aura::client::GetCursorClient(target->GetRootWindow());
  if (!client)
    return;

  if (enable)
    client->EnableMouseEvents();
  else
    client->DisableMouseEvents();
}

////////////////////////////////////////////////////////////////////////////////
// CompoundEventFilter, ui::EventHandler implementation:

void CompoundEventFilter::OnKeyEvent(ui::KeyEvent* event) {
  if (ShouldHideCursorOnKeyEvent(*event)) {
    SetCursorVisibilityOnEvent(
        static_cast<aura::Window*>(event->target()), event, false);
  }

  FilterKeyEvent(event);
}

void CompoundEventFilter::OnMouseEvent(ui::MouseEvent* event) {
  aura::Window* window = static_cast<aura::Window*>(event->target());
  aura::WindowTracker window_tracker;
  window_tracker.Add(window);

  // We must always update the cursor, otherwise the cursor can get stuck if an
  // event filter registered with us consumes the event.
  // It should also update the cursor for clicking and wheels for ChromeOS boot.
  // When ChromeOS is booted, it hides the mouse cursor but immediate mouse
  // operation will show the cursor.
  // We also update the cursor for mouse enter in case a mouse cursor is sent to
  // outside of the root window and moved back for some reasons (e.g. running on
  // on Desktop for testing, or a bug in pointer barrier).
  if (event->type() == ui::ET_MOUSE_ENTERED ||
      event->type() == ui::ET_MOUSE_MOVED ||
      event->type() == ui::ET_MOUSE_PRESSED ||
      event->type() == ui::ET_MOUSEWHEEL) {
    SetMouseEventsEnableStateOnEvent(window, event, true);
    SetCursorVisibilityOnEvent(window, event, true);
    UpdateCursor(window, event);
  }

  FilterMouseEvent(event);
}

void CompoundEventFilter::OnScrollEvent(ui::ScrollEvent* event) {
}

void CompoundEventFilter::OnTouchEvent(ui::TouchEvent* event) {
  FilterTouchEvent(event);
  if (ShouldHideCursorOnTouch() && !event->handled() &&
      event->type() == ui::ET_TOUCH_PRESSED &&
      !aura::Env::GetInstance()->IsMouseButtonDown()) {
    SetMouseEventsEnableStateOnEvent(
        static_cast<aura::Window*>(event->target()), event, false);
  }
}

void CompoundEventFilter::OnGestureEvent(ui::GestureEvent* event) {
  if (handlers_.might_have_observers()) {
    ObserverListBase<ui::EventHandler>::Iterator it(handlers_);
    ui::EventHandler* handler;
    while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL)
      handler->OnGestureEvent(event);
  }
}

}  // namespace corewm
}  // namespace views
