// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/public/common/input/web_input_event.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/web_gesture_event.h"
#include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
#include "third_party/blink/public/common/input/web_pointer_event.h"

namespace blink {

namespace {

WebMouseEvent CreateWebMouseMoveEvent() {
  WebMouseEvent mouse_event;
  mouse_event.SetType(WebInputEvent::Type::kMouseMove);
  mouse_event.id = 1;
  mouse_event.pointer_type = WebPointerProperties::PointerType::kMouse;
  return mouse_event;
}

WebPointerEvent CreateWebPointerMoveEvent() {
  WebPointerEvent pointer_event;
  pointer_event.SetType(WebInputEvent::Type::kPointerMove);
  pointer_event.id = 1;
  pointer_event.pointer_type = WebPointerProperties::PointerType::kMouse;
  return pointer_event;
}

WebTouchEvent CreateWebTouchMoveEvent() {
  WebTouchPoint touch_point;
  touch_point.id = 1;
  touch_point.state = WebTouchPoint::State::kStateMoved;
  touch_point.pointer_type = WebPointerProperties::PointerType::kTouch;

  WebTouchEvent touch_event;
  touch_event.SetType(WebInputEvent::Type::kTouchMove);
  touch_event.touches[touch_event.touches_length++] = touch_point;
  return touch_event;
}

}  // namespace

TEST(WebInputEventTest, TouchEventCoalescing) {
  WebTouchEvent coalesced_event = CreateWebTouchMoveEvent();
  coalesced_event.SetType(WebInputEvent::Type::kTouchMove);
  coalesced_event.touches[0].movement_x = 5;
  coalesced_event.touches[0].movement_y = 10;

  WebTouchEvent event_to_be_coalesced = CreateWebTouchMoveEvent();
  event_to_be_coalesced.touches[0].movement_x = 3;
  event_to_be_coalesced.touches[0].movement_y = -4;

  EXPECT_TRUE(coalesced_event.CanCoalesce(event_to_be_coalesced));
  coalesced_event.Coalesce(event_to_be_coalesced);
  EXPECT_EQ(8, coalesced_event.touches[0].movement_x);
  EXPECT_EQ(6, coalesced_event.touches[0].movement_y);

  coalesced_event.touches[0].pointer_type =
      WebPointerProperties::PointerType::kPen;
  EXPECT_FALSE(coalesced_event.CanCoalesce(event_to_be_coalesced));

  coalesced_event = CreateWebTouchMoveEvent();
  event_to_be_coalesced = CreateWebTouchMoveEvent();
  event_to_be_coalesced.SetModifiers(WebInputEvent::kControlKey);
  EXPECT_FALSE(coalesced_event.CanCoalesce(event_to_be_coalesced));
}

TEST(WebInputEventTest, WebMouseWheelEventCoalescing) {
  WebMouseWheelEvent coalesced_event(
      WebInputEvent::Type::kMouseWheel, WebInputEvent::kNoModifiers,
      WebInputEvent::GetStaticTimeStampForTests());
  coalesced_event.delta_x = 1;
  coalesced_event.delta_y = 1;

  WebMouseWheelEvent event_to_be_coalesced(
      WebInputEvent::Type::kMouseWheel, WebInputEvent::kNoModifiers,
      WebInputEvent::GetStaticTimeStampForTests());
  event_to_be_coalesced.delta_x = 3;
  event_to_be_coalesced.delta_y = 4;

  EXPECT_TRUE(coalesced_event.CanCoalesce(event_to_be_coalesced));
  coalesced_event.Coalesce(event_to_be_coalesced);
  EXPECT_EQ(4, coalesced_event.delta_x);
  EXPECT_EQ(5, coalesced_event.delta_y);

  event_to_be_coalesced.phase = WebMouseWheelEvent::kPhaseBegan;
  coalesced_event.phase = WebMouseWheelEvent::kPhaseEnded;
  EXPECT_FALSE(coalesced_event.CanCoalesce(event_to_be_coalesced));

  // With timer based wheel scroll latching, we break the latching sequence on
  // direction change when all prior GSU events in the current sequence are
  // ignored. To do so we dispatch the pending wheel event with phaseEnded and
  // the first wheel event in the opposite direction will have phaseBegan. The
  // GSB generated from this wheel event will cause a new hittesting. To make
  // sure that a GSB will actually get created we should not coalesce the wheel
  // event with synthetic kPhaseBegan to one with synthetic kPhaseEnded.
  event_to_be_coalesced.has_synthetic_phase = true;
  coalesced_event.has_synthetic_phase = true;
  EXPECT_FALSE(coalesced_event.CanCoalesce(event_to_be_coalesced));

  event_to_be_coalesced.phase = WebMouseWheelEvent::kPhaseChanged;
  coalesced_event.phase = WebMouseWheelEvent::kPhaseBegan;
  EXPECT_TRUE(coalesced_event.CanCoalesce(event_to_be_coalesced));
  coalesced_event.Coalesce(event_to_be_coalesced);
  EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, coalesced_event.phase);
  EXPECT_EQ(7, coalesced_event.delta_x);
  EXPECT_EQ(9, coalesced_event.delta_y);
}

TEST(WebInputEventTest, WebGestureEventCoalescing) {
  WebGestureEvent coalesced_event(WebInputEvent::Type::kGestureScrollUpdate,
                                  WebInputEvent::kNoModifiers,
                                  WebInputEvent::GetStaticTimeStampForTests());
  coalesced_event.data.scroll_update.delta_x = 1;
  coalesced_event.data.scroll_update.delta_y = 1;
  coalesced_event.data.scroll_update.delta_x_unconstrained = 2;
  coalesced_event.data.scroll_update.delta_y_unconstrained = 3;

  WebGestureEvent event_to_be_coalesced(
      WebInputEvent::Type::kGestureScrollUpdate, WebInputEvent::kNoModifiers,
      WebInputEvent::GetStaticTimeStampForTests());
  event_to_be_coalesced.data.scroll_update.delta_x = 3;
  event_to_be_coalesced.data.scroll_update.delta_y = 4;
  event_to_be_coalesced.data.scroll_update.delta_x_unconstrained = 5;
  event_to_be_coalesced.data.scroll_update.delta_y_unconstrained = 6;

  EXPECT_TRUE(coalesced_event.CanCoalesce(event_to_be_coalesced));
  coalesced_event.Coalesce(event_to_be_coalesced);
  EXPECT_EQ(4, coalesced_event.data.scroll_update.delta_x);
  EXPECT_EQ(5, coalesced_event.data.scroll_update.delta_y);
  EXPECT_EQ(7, coalesced_event.data.scroll_update.delta_x_unconstrained);
  EXPECT_EQ(9, coalesced_event.data.scroll_update.delta_y_unconstrained);
}

TEST(WebInputEventTest, GesturePinchUpdateCoalescing) {
  gfx::PointF position(10.f, 10.f);
  WebGestureEvent coalesced_event(
      WebInputEvent::Type::kGesturePinchUpdate, WebInputEvent::kNoModifiers,
      WebInputEvent::GetStaticTimeStampForTests(), WebGestureDevice::kTouchpad);
  coalesced_event.data.pinch_update.scale = 1.1f;
  coalesced_event.SetPositionInWidget(position);

  WebGestureEvent event_to_be_coalesced(coalesced_event);

  ASSERT_TRUE(coalesced_event.CanCoalesce(event_to_be_coalesced));
  coalesced_event.Coalesce(event_to_be_coalesced);
  EXPECT_FLOAT_EQ(1.21, coalesced_event.data.pinch_update.scale);

  // Allow the updates to be coalesced if the anchors are nearly equal.
  position.Offset(0.1f, 0.1f);
  event_to_be_coalesced.SetPositionInWidget(position);
  coalesced_event.data.pinch_update.scale = 1.1f;
  ASSERT_TRUE(coalesced_event.CanCoalesce(event_to_be_coalesced));
  coalesced_event.Coalesce(event_to_be_coalesced);
  EXPECT_FLOAT_EQ(1.21, coalesced_event.data.pinch_update.scale);

  // The anchors are no longer considered equal, so don't coalesce.
  position.Offset(1.f, 1.f);
  event_to_be_coalesced.SetPositionInWidget(position);
  EXPECT_FALSE(coalesced_event.CanCoalesce(event_to_be_coalesced));

  // Don't logically coalesce touchpad pinch events as touchpad pinch events
  // don't occur within a gesture scroll sequence.
  EXPECT_FALSE(WebGestureEvent::IsCompatibleScrollorPinch(event_to_be_coalesced,
                                                          coalesced_event));

  // Touchscreen pinch events can be logically coalesced.
  coalesced_event.SetSourceDevice(WebGestureDevice::kTouchscreen);
  event_to_be_coalesced.SetSourceDevice(WebGestureDevice::kTouchscreen);
  coalesced_event.data.pinch_update.scale = 1.1f;
  ASSERT_TRUE(WebGestureEvent::IsCompatibleScrollorPinch(event_to_be_coalesced,
                                                         coalesced_event));

  std::unique_ptr<WebGestureEvent> logical_scroll, logical_pinch;
  std::tie(logical_scroll, logical_pinch) =
      WebGestureEvent::CoalesceScrollAndPinch(nullptr, coalesced_event,
                                              event_to_be_coalesced);
  ASSERT_NE(nullptr, logical_scroll);
  ASSERT_NE(nullptr, logical_pinch);
  ASSERT_EQ(WebInputEvent::Type::kGestureScrollUpdate,
            logical_scroll->GetType());
  ASSERT_EQ(WebInputEvent::Type::kGesturePinchUpdate, logical_pinch->GetType());
  EXPECT_FLOAT_EQ(1.21, logical_pinch->data.pinch_update.scale);
}

TEST(WebInputEventTest, MouseEventCoalescing) {
  WebMouseEvent coalesced_event = CreateWebMouseMoveEvent();
  WebMouseEvent event_to_be_coalesced = CreateWebMouseMoveEvent();
  EXPECT_TRUE(coalesced_event.CanCoalesce(event_to_be_coalesced));

  // Test coalescing movements.
  coalesced_event.movement_x = 5;
  coalesced_event.movement_y = 10;

  event_to_be_coalesced.movement_x = 3;
  event_to_be_coalesced.movement_y = -4;
  EXPECT_TRUE(coalesced_event.CanCoalesce(event_to_be_coalesced));
  coalesced_event.Coalesce(event_to_be_coalesced);
  EXPECT_EQ(8, coalesced_event.movement_x);
  EXPECT_EQ(6, coalesced_event.movement_y);

  // Test id.
  coalesced_event = CreateWebMouseMoveEvent();
  event_to_be_coalesced = CreateWebMouseMoveEvent();
  event_to_be_coalesced.id = 3;
  EXPECT_FALSE(coalesced_event.CanCoalesce(event_to_be_coalesced));

  // Test pointer_type.
  coalesced_event = CreateWebMouseMoveEvent();
  event_to_be_coalesced = CreateWebMouseMoveEvent();
  event_to_be_coalesced.pointer_type = WebPointerProperties::PointerType::kPen;
  EXPECT_FALSE(coalesced_event.CanCoalesce(event_to_be_coalesced));

  // Test modifiers
  coalesced_event = CreateWebMouseMoveEvent();
  event_to_be_coalesced = CreateWebMouseMoveEvent();
  event_to_be_coalesced.SetModifiers(WebInputEvent::kControlKey);
  EXPECT_FALSE(coalesced_event.CanCoalesce(event_to_be_coalesced));
}

TEST(WebInputEventTest, PointerEventCoalescing) {
  WebPointerEvent coalesced_event = CreateWebPointerMoveEvent();
  WebPointerEvent event_to_be_coalesced = CreateWebPointerMoveEvent();
  EXPECT_TRUE(coalesced_event.CanCoalesce(event_to_be_coalesced));

  // Test coalescing movements.
  coalesced_event.movement_x = 5;
  coalesced_event.movement_y = 10;

  event_to_be_coalesced.movement_x = 3;
  event_to_be_coalesced.movement_y = -4;
  EXPECT_TRUE(coalesced_event.CanCoalesce(event_to_be_coalesced));
  coalesced_event.Coalesce(event_to_be_coalesced);
  EXPECT_EQ(8, coalesced_event.movement_x);
  EXPECT_EQ(6, coalesced_event.movement_y);

  // Test id.
  coalesced_event = CreateWebPointerMoveEvent();
  event_to_be_coalesced = CreateWebPointerMoveEvent();
  event_to_be_coalesced.id = 3;
  EXPECT_FALSE(coalesced_event.CanCoalesce(event_to_be_coalesced));

  // Test pointer_type.
  coalesced_event = CreateWebPointerMoveEvent();
  event_to_be_coalesced = CreateWebPointerMoveEvent();
  event_to_be_coalesced.pointer_type = WebPointerProperties::PointerType::kPen;
  EXPECT_FALSE(coalesced_event.CanCoalesce(event_to_be_coalesced));

  // Test modifiers
  coalesced_event = CreateWebPointerMoveEvent();
  event_to_be_coalesced = CreateWebPointerMoveEvent();
  event_to_be_coalesced.SetModifiers(WebInputEvent::kControlKey);
  EXPECT_FALSE(coalesced_event.CanCoalesce(event_to_be_coalesced));
}

}  // namespace blink
