// Copyright 2014 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/events/devices/device_data_manager.h"

#include "base/at_exit.h"
#include "base/bind.h"
#include "base/logging.h"
#include "ui/display/display.h"
#include "ui/events/devices/input_device_event_observer.h"
#include "ui/gfx/geometry/point3_f.h"

// This macro provides the implementation for the observer notification methods.
#define NOTIFY_OBSERVERS(method_decl, observer_call)      \
  void DeviceDataManager::method_decl {                   \
    for (InputDeviceEventObserver& observer : observers_) \
      observer.observer_call;                             \
  }

namespace ui {

namespace {

bool InputDeviceEquals(const ui::InputDevice& a, const ui::InputDevice& b) {
  return a.id == b.id;
}

}  // namespace

DeviceDataManager::TouchscreenInfo::TouchscreenInfo() {
  Reset();
}

void DeviceDataManager::TouchscreenInfo::Reset() {
  radius_scale = 1.0;
  target_display = display::Display::kInvalidDisplayID;
  device_transform = gfx::Transform();
}

// static
DeviceDataManager* DeviceDataManager::instance_ = nullptr;

DeviceDataManager::DeviceDataManager() {
  InputDeviceManager::SetInstance(this);
}

DeviceDataManager::~DeviceDataManager() {
  InputDeviceManager::ClearInstance();
}

// static
DeviceDataManager* DeviceDataManager::instance() { return instance_; }

// static
void DeviceDataManager::set_instance(DeviceDataManager* instance) {
  DCHECK(instance)
      << "Must reset the DeviceDataManager using DeleteInstance().";
  DCHECK(!instance_) << "Can not set multiple instances of DeviceDataManager.";
  instance_ = instance;
}

// static
void DeviceDataManager::CreateInstance() {
  if (instance())
    return;

  set_instance(new DeviceDataManager());

  // TODO(bruthig): Replace the DeleteInstance callbacks with explicit calls.
  base::AtExitManager::RegisterTask(base::Bind(DeleteInstance));
}

// static
void DeviceDataManager::DeleteInstance() {
  if (instance_) {
    delete instance_;
    instance_ = nullptr;
  }
}

// static
DeviceDataManager* DeviceDataManager::GetInstance() {
  CHECK(instance_) << "DeviceDataManager was not created.";
  return instance_;
}

// static
bool DeviceDataManager::HasInstance() {
  return instance_ != nullptr;
}

void DeviceDataManager::ClearTouchDeviceAssociations() {
  for (auto& touch_info : touch_map_)
    touch_info.Reset();
}

bool DeviceDataManager::IsTouchDeviceIdValid(
    int touch_device_id) const {
  return (touch_device_id > 0 && touch_device_id < kMaxDeviceNum);
}

void DeviceDataManager::UpdateTouchInfoForDisplay(
    int64_t target_display_id,
    int touch_device_id,
    const gfx::Transform& touch_transformer) {
  if (IsTouchDeviceIdValid(touch_device_id)) {
    touch_map_[touch_device_id].target_display = target_display_id;
    touch_map_[touch_device_id].device_transform = touch_transformer;
  }
}

void DeviceDataManager::UpdateTouchRadiusScale(int touch_device_id,
                                               double scale) {
  if (IsTouchDeviceIdValid(touch_device_id))
    touch_map_[touch_device_id].radius_scale = scale;
}

void DeviceDataManager::ApplyTouchRadiusScale(int touch_device_id,
                                              double* radius) {
  if (IsTouchDeviceIdValid(touch_device_id))
    *radius = (*radius) * touch_map_[touch_device_id].radius_scale;
}

void DeviceDataManager::ApplyTouchTransformer(int touch_device_id,
                                              float* x,
                                              float* y) {
  if (IsTouchDeviceIdValid(touch_device_id)) {
    gfx::Point3F point(*x, *y, 0.0);
    const gfx::Transform& trans = touch_map_[touch_device_id].device_transform;
    trans.TransformPoint(&point);
    *x = point.x();
    *y = point.y();
  }
}

const std::vector<TouchscreenDevice>& DeviceDataManager::GetTouchscreenDevices()
    const {
  return touchscreen_devices_;
}

const std::vector<InputDevice>& DeviceDataManager::GetKeyboardDevices() const {
  return keyboard_devices_;
}

const std::vector<InputDevice>& DeviceDataManager::GetMouseDevices() const {
  return mouse_devices_;
}

const std::vector<InputDevice>& DeviceDataManager::GetTouchpadDevices() const {
  return touchpad_devices_;
}

bool DeviceDataManager::AreDeviceListsComplete() const {
  return device_lists_complete_;
}

int64_t DeviceDataManager::GetTargetDisplayForTouchDevice(
    int touch_device_id) const {
  if (IsTouchDeviceIdValid(touch_device_id))
    return touch_map_[touch_device_id].target_display;
  return display::Display::kInvalidDisplayID;
}

void DeviceDataManager::OnTouchscreenDevicesUpdated(
    const std::vector<TouchscreenDevice>& devices) {
  if (devices.size() == touchscreen_devices_.size() &&
      std::equal(devices.begin(),
                 devices.end(),
                 touchscreen_devices_.begin(),
                 InputDeviceEquals)) {
    return;
  }
  touchscreen_devices_ = devices;
  NotifyObserversTouchscreenDeviceConfigurationChanged();
}

void DeviceDataManager::OnKeyboardDevicesUpdated(
    const std::vector<InputDevice>& devices) {
  if (devices.size() == keyboard_devices_.size() &&
      std::equal(devices.begin(),
                 devices.end(),
                 keyboard_devices_.begin(),
                 InputDeviceEquals)) {
    return;
  }
  keyboard_devices_ = devices;
  NotifyObserversKeyboardDeviceConfigurationChanged();
}

void DeviceDataManager::OnMouseDevicesUpdated(
    const std::vector<InputDevice>& devices) {
  if (devices.size() == mouse_devices_.size() &&
      std::equal(devices.begin(),
                 devices.end(),
                 mouse_devices_.begin(),
                 InputDeviceEquals)) {
    return;
  }
  mouse_devices_ = devices;
  NotifyObserversMouseDeviceConfigurationChanged();
}

void DeviceDataManager::OnTouchpadDevicesUpdated(
    const std::vector<InputDevice>& devices) {
  if (devices.size() == touchpad_devices_.size() &&
      std::equal(devices.begin(),
                 devices.end(),
                 touchpad_devices_.begin(),
                 InputDeviceEquals)) {
    return;
  }
  touchpad_devices_ = devices;
  NotifyObserversTouchpadDeviceConfigurationChanged();
}

void DeviceDataManager::OnDeviceListsComplete() {
  if (!device_lists_complete_) {
    device_lists_complete_ = true;
    NotifyObserversDeviceListsComplete();
  }
}

void DeviceDataManager::OnStylusStateChanged(StylusState state) {
  NotifyObserversStylusStateChanged(state);
}

NOTIFY_OBSERVERS(NotifyObserversTouchscreenDeviceConfigurationChanged(),
                 OnTouchscreenDeviceConfigurationChanged());

NOTIFY_OBSERVERS(NotifyObserversKeyboardDeviceConfigurationChanged(),
                 OnKeyboardDeviceConfigurationChanged());

NOTIFY_OBSERVERS(NotifyObserversMouseDeviceConfigurationChanged(),
                 OnMouseDeviceConfigurationChanged());

NOTIFY_OBSERVERS(NotifyObserversTouchpadDeviceConfigurationChanged(),
                 OnTouchpadDeviceConfigurationChanged());

NOTIFY_OBSERVERS(NotifyObserversDeviceListsComplete(), OnDeviceListsComplete());

NOTIFY_OBSERVERS(NotifyObserversStylusStateChanged(StylusState state),
                 OnStylusStateChanged(state));

void DeviceDataManager::AddObserver(InputDeviceEventObserver* observer) {
  observers_.AddObserver(observer);
}

void DeviceDataManager::RemoveObserver(InputDeviceEventObserver* observer) {
  observers_.RemoveObserver(observer);
}

void DeviceDataManager::SetTouchscreensEnabled(bool enabled) {
  touch_screens_enabled_ = enabled;
}

bool DeviceDataManager::AreTouchscreensEnabled() const {
  return touch_screens_enabled_;
}

}  // namespace ui
