// 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/ozone/evdev/input_injector_evdev.h"

#include "base/bind.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/ozone/device/device_manager.h"
#include "ui/events/ozone/evdev/cursor_delegate_evdev.h"
#include "ui/events/ozone/evdev/event_converter_test_util.h"
#include "ui/events/ozone/evdev/event_factory_evdev.h"
#include "ui/events/ozone/events_ozone.h"
#include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"

namespace ui {

using testing::AllOf;
using testing::InSequence;
using testing::Property;

class EventObserver {
 public:
  void EventDispatchCallback(Event* event) {
    DispatchEventFromNativeUiEvent(
        event, base::Bind(&EventObserver::OnEvent, base::Unretained(this)));
  }

  void OnEvent(Event* event) {
    if (event->IsMouseEvent()) {
      if (event->type() == ET_MOUSEWHEEL) {
        OnMouseWheelEvent(static_cast<MouseWheelEvent*>(event));
      } else {
        OnMouseEvent(static_cast<MouseEvent*>(event));
      }
    }
  }

  // Mock functions for intercepting mouse events.
  MOCK_METHOD1(OnMouseEvent, void(MouseEvent* event));
  MOCK_METHOD1(OnMouseWheelEvent, void(MouseWheelEvent* event));
};

class MockCursorEvdev : public CursorDelegateEvdev {
 public:
  MockCursorEvdev() {}
  ~MockCursorEvdev() override {}

  // CursorDelegateEvdev:
  void MoveCursorTo(gfx::AcceleratedWidget widget,
                    const gfx::PointF& location) override {
    cursor_location_ = location;
  }
  void MoveCursorTo(const gfx::PointF& location) override {
    cursor_location_ = location;
  }
  void MoveCursor(const gfx::Vector2dF& delta) override {
    cursor_location_ = gfx::PointF(delta.x(), delta.y());
  }
  bool IsCursorVisible() override { return 1; }
  gfx::Rect GetCursorConfinedBounds() override {
    NOTIMPLEMENTED();
    return gfx::Rect();
  }
  gfx::PointF GetLocation() override { return cursor_location_; }

 private:
  // The location of the mock cursor.
  gfx::PointF cursor_location_;

  DISALLOW_COPY_AND_ASSIGN(MockCursorEvdev);
};

MATCHER_P4(MatchesMouseEvent, type, button, x, y, "") {
  if (arg->type() != type) {
    *result_listener << "Expected type: " << type << " actual: " << arg->type()
                     << " (" << arg->name() << ")";
    return false;
  }
  if (button == EF_LEFT_MOUSE_BUTTON && !arg->IsLeftMouseButton()) {
    *result_listener << "Expected the left button flag is set.";
    return false;
  }
  if (button == EF_RIGHT_MOUSE_BUTTON && !arg->IsRightMouseButton()) {
    *result_listener << "Expected the right button flag is set.";
    return false;
  }
  if (button == EF_MIDDLE_MOUSE_BUTTON && !arg->IsMiddleMouseButton()) {
    *result_listener << "Expected the middle button flag is set.";
    return false;
  }
  if (arg->x() != x || arg->y() != y) {
    *result_listener << "Expected location: (" << x << ", " << y
                     << ") actual: (" << arg->x() << ", " << arg->y() << ")";
    return false;
  }
  return true;
}

class InputInjectorEvdevTest : public testing::Test {
 public:
  InputInjectorEvdevTest();

 protected:
  void SimulateMouseClick(int x, int y, EventFlags button, int count);
  void ExpectClick(int x, int y, int button, int count);

  EventObserver event_observer_;
  EventDispatchCallback dispatch_callback_;
  MockCursorEvdev cursor_;

  scoped_ptr<DeviceManager> device_manager_;
  scoped_ptr<EventFactoryEvdev> event_factory_;

  InputInjectorEvdev injector_;

  base::MessageLoop message_loop_;
  base::RunLoop run_loop_;

 private:
  DISALLOW_COPY_AND_ASSIGN(InputInjectorEvdevTest);
};

InputInjectorEvdevTest::InputInjectorEvdevTest()
    : dispatch_callback_(base::Bind(&EventObserver::EventDispatchCallback,
                                    base::Unretained(&event_observer_))),
      device_manager_(CreateDeviceManagerForTest()),
      event_factory_(CreateEventFactoryEvdevForTest(
          &cursor_,
          device_manager_.get(),
          ui::KeyboardLayoutEngineManager::GetKeyboardLayoutEngine(),
          dispatch_callback_)),
      injector_(CreateDeviceEventDispatcherEvdevForTest(event_factory_.get()),
                &cursor_) {
}

void InputInjectorEvdevTest::SimulateMouseClick(int x,
                                                int y,
                                                EventFlags button,
                                                int count) {
  injector_.MoveCursorTo(gfx::PointF(x, y));
  for (int i = 0; i < count; i++) {
    injector_.InjectMouseButton(button, true);
    injector_.InjectMouseButton(button, false);
  }
}

void InputInjectorEvdevTest::ExpectClick(int x, int y, int button, int count) {
  InSequence dummy;
  EXPECT_CALL(event_observer_,
              OnMouseEvent(MatchesMouseEvent(ET_MOUSE_MOVED, EF_NONE, x, y)));

  for (int i = 0; i < count; i++) {
    EXPECT_CALL(event_observer_, OnMouseEvent(MatchesMouseEvent(
                                     ET_MOUSE_PRESSED, button, x, y)));
    EXPECT_CALL(event_observer_, OnMouseEvent(MatchesMouseEvent(
                                     ET_MOUSE_RELEASED, button, x, y)));
  }
}

TEST_F(InputInjectorEvdevTest, LeftClick) {
  ExpectClick(12, 13, EF_LEFT_MOUSE_BUTTON, 1);
  SimulateMouseClick(12, 13, EF_LEFT_MOUSE_BUTTON, 1);
  run_loop_.RunUntilIdle();
}

TEST_F(InputInjectorEvdevTest, RightClick) {
  ExpectClick(12, 13, EF_RIGHT_MOUSE_BUTTON, 1);
  SimulateMouseClick(12, 13, EF_RIGHT_MOUSE_BUTTON, 1);
  run_loop_.RunUntilIdle();
}

TEST_F(InputInjectorEvdevTest, DoubleClick) {
  ExpectClick(12, 13, EF_LEFT_MOUSE_BUTTON, 2);
  SimulateMouseClick(12, 13, EF_LEFT_MOUSE_BUTTON, 2);
  run_loop_.RunUntilIdle();
}

TEST_F(InputInjectorEvdevTest, MouseMoved) {
  injector_.MoveCursorTo(gfx::PointF(1, 1));
  run_loop_.RunUntilIdle();
  EXPECT_EQ(cursor_.GetLocation(), gfx::PointF(1, 1));
}

TEST_F(InputInjectorEvdevTest, MouseDragged) {
  InSequence dummy;
  EXPECT_CALL(event_observer_,
              OnMouseEvent(MatchesMouseEvent(ET_MOUSE_PRESSED,
                                             EF_LEFT_MOUSE_BUTTON, 0, 0)));
  EXPECT_CALL(event_observer_,
              OnMouseEvent(MatchesMouseEvent(ET_MOUSE_DRAGGED,
                                             EF_LEFT_MOUSE_BUTTON, 1, 1)));
  EXPECT_CALL(event_observer_,
              OnMouseEvent(MatchesMouseEvent(ET_MOUSE_DRAGGED,
                                             EF_LEFT_MOUSE_BUTTON, 2, 3)));
  EXPECT_CALL(event_observer_,
              OnMouseEvent(MatchesMouseEvent(ET_MOUSE_RELEASED,
                                             EF_LEFT_MOUSE_BUTTON, 2, 3)));
  injector_.InjectMouseButton(EF_LEFT_MOUSE_BUTTON, true);
  injector_.MoveCursorTo(gfx::PointF(1, 1));
  injector_.MoveCursorTo(gfx::PointF(2, 3));
  injector_.InjectMouseButton(EF_LEFT_MOUSE_BUTTON, false);
  run_loop_.RunUntilIdle();
}

TEST_F(InputInjectorEvdevTest, MouseWheel) {
  InSequence dummy;
  EXPECT_CALL(event_observer_, OnMouseWheelEvent(AllOf(
                                   MatchesMouseEvent(ET_MOUSEWHEEL, 0, 10, 20),
                                   Property(&MouseWheelEvent::x_offset, 0),
                                   Property(&MouseWheelEvent::y_offset, 100))));
  EXPECT_CALL(event_observer_, OnMouseWheelEvent(AllOf(
                                   MatchesMouseEvent(ET_MOUSEWHEEL, 0, 10, 20),
                                   Property(&MouseWheelEvent::x_offset, 100),
                                   Property(&MouseWheelEvent::y_offset, 0))));
  injector_.MoveCursorTo(gfx::PointF(10, 20));
  injector_.InjectMouseWheel(0, 100);
  injector_.InjectMouseWheel(100, 0);
  run_loop_.RunUntilIdle();
}

}  // namespace ui