// Copyright 2017 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 "components/exo/gaming_seat.h"
#include "components/exo/gamepad_delegate.h"
#include "components/exo/gaming_seat_delegate.h"
#include "components/exo/shell_surface.h"
#include "components/exo/surface.h"
#include "ui/events/ozone/gamepad/gamepad_provider_ozone.h"

namespace exo {

////////////////////////////////////////////////////////////////////////////////
// GamingSeat, public:

GamingSeat::GamingSeat(GamingSeatDelegate* delegate) : delegate_(delegate) {
  auto* helper = WMHelper::GetInstance();
  helper->AddFocusObserver(this);
  OnWindowFocused(helper->GetFocusedWindow(), nullptr);
}

GamingSeat::~GamingSeat() {
  if (focused_)
    ui::GamepadProviderOzone::GetInstance()->RemoveGamepadObserver(this);
  delegate_->OnGamingSeatDestroying(this);
  // Disconnect all the gamepads.
  for (auto& entry : gamepads_)
    entry.second->OnRemoved();

  WMHelper::GetInstance()->RemoveFocusObserver(this);
}

////////////////////////////////////////////////////////////////////////////////
// ui::aura::client::FocusChangeObserver overrides:

void GamingSeat::OnWindowFocused(aura::Window* gained_focus,
                                 aura::Window* lost_focus) {
  Surface* target = nullptr;
  if (gained_focus) {
    target = Surface::AsSurface(gained_focus);
    if (!target) {
      aura::Window* top_level_window = gained_focus->GetToplevelWindow();
      if (top_level_window)
        target = ShellSurface::GetMainSurface(top_level_window);
    }
  }

  bool focused = target && delegate_->CanAcceptGamepadEventsForSurface(target);
  if (focused_ != focused) {
    focused_ = focused;
    if (focused) {
      ui::GamepadProviderOzone::GetInstance()->AddGamepadObserver(this);
      OnGamepadDevicesUpdated();
    } else {
      ui::GamepadProviderOzone::GetInstance()->RemoveGamepadObserver(this);
    }
  }
}

////////////////////////////////////////////////////////////////////////////////
// ui::GamepadObserver overrides:

void GamingSeat::OnGamepadDevicesUpdated() {
  std::vector<ui::InputDevice> gamepad_devices =
      ui::GamepadProviderOzone::GetInstance()->GetGamepadDevices();

  base::flat_map<int, GamepadDelegate*> new_gamepads;

  // Copy the "still connected gamepads".
  for (auto& device : gamepad_devices) {
    auto it = gamepads_.find(device.id);
    if (it != gamepads_.end()) {
      new_gamepads[device.id] = it->second;
      gamepads_.erase(it);
    }
  }

  // Remove each disconected gamepad.
  for (auto& entry : gamepads_)
    entry.second->OnRemoved();

  // Add each new connected gamepad.
  for (auto& device : gamepad_devices) {
    if (new_gamepads.find(device.id) == new_gamepads.end())
      new_gamepads[device.id] = delegate_->GamepadAdded();
  }

  new_gamepads.swap(gamepads_);
}

void GamingSeat::OnGamepadEvent(const ui::GamepadEvent& event) {
  auto it = gamepads_.find(event.device_id());
  if (it == gamepads_.end())
    return;

  switch (event.type()) {
    case ui::GamepadEventType::BUTTON:
      it->second->OnButton(event.code(), event.value(), event.value());
      break;
    case ui::GamepadEventType::AXIS:
      it->second->OnAxis(event.code(), event.value());
      break;
    case ui::GamepadEventType::FRAME:
      it->second->OnFrame();
      break;
  }
}

}  // namespace exo
