// Copyright 2016 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/mus/pointer_watcher_event_router.h"

#include "ui/aura/client/capture_client.h"
#include "ui/aura/mus/window_tree_client.h"
#include "ui/aura/window.h"
#include "ui/display/screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/views/pointer_watcher.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"

namespace views {
namespace {

bool HasPointerWatcher(
    base::ObserverList<views::PointerWatcher, true>* observer_list) {
  return observer_list->begin() != observer_list->end();
}

}  // namespace

PointerWatcherEventRouter::PointerWatcherEventRouter(
    aura::WindowTreeClient* window_tree_client)
    : window_tree_client_(window_tree_client) {
  window_tree_client->AddObserver(this);
}

PointerWatcherEventRouter::~PointerWatcherEventRouter() {
  if (window_tree_client_)
    window_tree_client_->RemoveObserver(this);
}

void PointerWatcherEventRouter::AddPointerWatcher(PointerWatcher* watcher,
                                                  bool wants_moves) {
  // Pointer watchers cannot be added multiple times.
  DCHECK(!move_watchers_.HasObserver(watcher));
  DCHECK(!non_move_watchers_.HasObserver(watcher));
  if (wants_moves) {
    move_watchers_.AddObserver(watcher);
    if (event_types_ != EventTypes::MOVE_EVENTS) {
      event_types_ = EventTypes::MOVE_EVENTS;
      const bool wants_moves = true;
      window_tree_client_->StartPointerWatcher(wants_moves);
    }
  } else {
    non_move_watchers_.AddObserver(watcher);
    if (event_types_ == EventTypes::NONE) {
      event_types_ = EventTypes::NON_MOVE_EVENTS;
      const bool wants_moves = false;
      window_tree_client_->StartPointerWatcher(wants_moves);
    }
  }
}

void PointerWatcherEventRouter::RemovePointerWatcher(PointerWatcher* watcher) {
  if (non_move_watchers_.HasObserver(watcher)) {
    non_move_watchers_.RemoveObserver(watcher);
  } else {
    DCHECK(move_watchers_.HasObserver(watcher));
    move_watchers_.RemoveObserver(watcher);
  }
  const EventTypes types = DetermineEventTypes();
  if (types == event_types_)
    return;

  event_types_ = types;
  switch (types) {
    case EventTypes::NONE:
      window_tree_client_->StopPointerWatcher();
      break;
    case EventTypes::NON_MOVE_EVENTS:
      window_tree_client_->StartPointerWatcher(false);
      break;
    case EventTypes::MOVE_EVENTS:
      // It isn't possible to remove an observer and transition to wanting move
      // events. This could only happen if there is a bug in the add logic.
      NOTREACHED();
      break;
  }
}

void PointerWatcherEventRouter::OnPointerEventObserved(
    const ui::PointerEvent& event,
    aura::Window* target) {
  Widget* target_widget = nullptr;
  ui::PointerEvent updated_event(event);
  if (target) {
    aura::Window* window = target;
    while (window && !target_widget) {
      target_widget = Widget::GetWidgetForNativeView(window);
      if (!target_widget) {
        // Widget::GetWidgetForNativeView() uses NativeWidgetAura. Views with
        // aura-mus may also create DesktopNativeWidgetAura.
        DesktopNativeWidgetAura* desktop_native_widget_aura =
            DesktopNativeWidgetAura::ForWindow(target);
        if (desktop_native_widget_aura) {
          target_widget = static_cast<internal::NativeWidgetPrivate*>(
                              desktop_native_widget_aura)
                              ->GetWidget();
        }
      }
      window = window->parent();
    }
    if (target_widget) {
      gfx::Point widget_relative_location(event.location());
      aura::Window::ConvertPointToTarget(target, target_widget->GetNativeView(),
                                         &widget_relative_location);
      updated_event.set_location(widget_relative_location);
    }
  }

  // The mojo input events type converter uses the event root_location field
  // to store screen coordinates. Screen coordinates really should be returned
  // separately. See http://crbug.com/608547
  gfx::Point location_in_screen = event.root_location();
  for (PointerWatcher& observer : move_watchers_) {
    observer.OnPointerEventObserved(
        updated_event, location_in_screen,
        target_widget ? target_widget->GetNativeWindow() : target);
  }
  if (event.type() != ui::ET_POINTER_MOVED) {
    for (PointerWatcher& observer : non_move_watchers_) {
      observer.OnPointerEventObserved(
          updated_event, location_in_screen,
          target_widget ? target_widget->GetNativeWindow() : target);
    }
  }
}

void PointerWatcherEventRouter::AttachToCaptureClient(
    aura::client::CaptureClient* capture_client) {
  capture_client->AddObserver(this);
}

void PointerWatcherEventRouter::DetachFromCaptureClient(
    aura::client::CaptureClient* capture_client) {
  capture_client->RemoveObserver(this);
}

PointerWatcherEventRouter::EventTypes
PointerWatcherEventRouter::DetermineEventTypes() {
  if (HasPointerWatcher(&move_watchers_))
    return EventTypes::MOVE_EVENTS;

  if (HasPointerWatcher(&non_move_watchers_))
    return EventTypes::NON_MOVE_EVENTS;

  return EventTypes::NONE;
}

void PointerWatcherEventRouter::OnCaptureChanged(aura::Window* lost_capture,
                                                 aura::Window* gained_capture) {
  const ui::MouseEvent mouse_event(ui::ET_MOUSE_CAPTURE_CHANGED, gfx::Point(),
                                   gfx::Point(), ui::EventTimeForNow(), 0, 0);
  const ui::PointerEvent event(mouse_event);
  gfx::Point location_in_screen =
      display::Screen::GetScreen()->GetCursorScreenPoint();
  for (PointerWatcher& observer : move_watchers_)
    observer.OnPointerEventObserved(event, location_in_screen, nullptr);
  for (PointerWatcher& observer : non_move_watchers_)
    observer.OnPointerEventObserved(event, location_in_screen, nullptr);
}

void PointerWatcherEventRouter::OnWillDestroyClient(
    aura::WindowTreeClient* client) {
  // We expect that all observers have been removed by this time.
  DCHECK_EQ(event_types_, EventTypes::NONE);
  DCHECK_EQ(client, window_tree_client_);
  window_tree_client_->RemoveObserver(this);
  window_tree_client_ = nullptr;
}

}  // namespace views
