// 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 "ash/accelerators/key_hold_detector.h"

#include <utility>

#include "ash/shell.h"
#include "base/bind.h"
#include "base/threading/thread_task_runner_handle.h"
#include "ui/aura/window_tracker.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event_dispatcher.h"
#include "ui/events/event_sink.h"

namespace ash {
namespace {

void DispatchPressedEvent(const ui::KeyEvent& key_event,
                          std::unique_ptr<aura::WindowTracker> tracker) {
  // The target window may be gone.
  if (tracker->windows().empty())
    return;
  ui::KeyEvent event(key_event);
  aura::Window* target = *(tracker->windows().begin());
  ignore_result(target->GetHost()->GetEventSink()->OnEventFromSource(&event));
}

void PostPressedEvent(ui::KeyEvent* event) {
  // Modify RELEASED event to PRESSED event.
  const ui::KeyEvent pressed_event(
      ui::ET_KEY_PRESSED, event->key_code(), event->code(),
      event->flags() | ui::EF_SHIFT_DOWN | ui::EF_IS_SYNTHESIZED);
  std::unique_ptr<aura::WindowTracker> tracker(new aura::WindowTracker);
  tracker->Add(static_cast<aura::Window*>(event->target()));

  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE,
      base::BindOnce(&DispatchPressedEvent, pressed_event, std::move(tracker)));
}

}  // namespace

KeyHoldDetector::KeyHoldDetector(std::unique_ptr<Delegate> delegate)
    : state_(INITIAL), delegate_(std::move(delegate)) {}

KeyHoldDetector::~KeyHoldDetector() = default;

void KeyHoldDetector::OnKeyEvent(ui::KeyEvent* event) {
  if (!delegate_->ShouldProcessEvent(event))
    return;

  if (delegate_->IsStartEvent(event)) {
    switch (state_) {
      case INITIAL:
        // Pass through posted event.
        if (event->flags() & ui::EF_IS_SYNTHESIZED) {
          event->set_flags(event->flags() & ~ui::EF_IS_SYNTHESIZED);
          return;
        }
        state_ = PRESSED;
        if (delegate_->ShouldStopEventPropagation()) {
          // Don't process ET_KEY_PRESSED event yet. The ET_KEY_PRESSED
          // event will be generated upon ET_KEY_RELEASEED event below.
          event->StopPropagation();
        }
        break;
      case PRESSED:
        state_ = HOLD;
        FALLTHROUGH;
      case HOLD:
        delegate_->OnKeyHold(event);
        if (delegate_->ShouldStopEventPropagation())
          event->StopPropagation();
        break;
    }
  } else if (event->type() == ui::ET_KEY_RELEASED) {
    switch (state_) {
      case INITIAL:
        break;
      case PRESSED: {
        if (delegate_->ShouldStopEventPropagation()) {
          PostPressedEvent(event);
          event->StopPropagation();
        }
        break;
      }
      case HOLD: {
        delegate_->OnKeyUnhold(event);
        if (delegate_->ShouldStopEventPropagation())
          event->StopPropagation();
        break;
      }
    }
    state_ = INITIAL;
  }
}

}  // namespace ash
