// Copyright 2013 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 "content/common/input/synthetic_web_input_event_builders.h"

#include "base/logging.h"
#include "content/common/input/web_mouse_wheel_event_traits.h"
#include "content/common/input/web_touch_event_traits.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"

namespace content {

using blink::WebInputEvent;
using blink::WebKeyboardEvent;
using blink::WebGestureEvent;
using blink::WebMouseEvent;
using blink::WebMouseWheelEvent;
using blink::WebTouchEvent;
using blink::WebTouchPoint;

WebMouseEvent SyntheticWebMouseEventBuilder::Build(
    blink::WebInputEvent::Type type) {
  return WebMouseEvent(type, WebInputEvent::kNoModifiers,
                       ui::EventTimeForNow());
}

WebMouseEvent SyntheticWebMouseEventBuilder::Build(
    blink::WebInputEvent::Type type,
    float window_x,
    float window_y,
    int modifiers,
    blink::WebPointerProperties::PointerType pointer_type) {
  DCHECK(WebInputEvent::IsMouseEventType(type));
  WebMouseEvent result(type, modifiers, ui::EventTimeForNow());
  result.SetPositionInWidget(window_x, window_y);
  result.SetPositionInScreen(window_x, window_y);
  result.SetModifiers(modifiers);
  result.pointer_type = pointer_type;
  result.id = ui::MouseEvent::kMousePointerId;
  return result;
}

WebMouseWheelEvent SyntheticWebMouseWheelEventBuilder::Build(
    WebMouseWheelEvent::Phase phase) {
  WebMouseWheelEvent result(WebInputEvent::kMouseWheel,
                            WebInputEvent::kNoModifiers, ui::EventTimeForNow());
  result.phase = phase;
  result.event_action = WebMouseWheelEventTraits::GetEventAction(result);
  return result;
}

WebMouseWheelEvent SyntheticWebMouseWheelEventBuilder::Build(
    float x,
    float y,
    float dx,
    float dy,
    int modifiers,
    bool precise,
    bool scroll_by_page) {
  return Build(x, y, 0, 0, dx, dy, modifiers, precise, scroll_by_page);
}

WebMouseWheelEvent SyntheticWebMouseWheelEventBuilder::Build(
    float x,
    float y,
    float global_x,
    float global_y,
    float dx,
    float dy,
    int modifiers,
    bool precise,
    bool scroll_by_page) {
  WebMouseWheelEvent result(WebInputEvent::kMouseWheel, modifiers,
                            ui::EventTimeForNow());
  result.SetPositionInScreen(global_x, global_y);
  result.SetPositionInWidget(x, y);
  result.delta_x = dx;
  result.delta_y = dy;
  if (dx)
    result.wheel_ticks_x = dx > 0.0f ? 1.0f : -1.0f;
  if (dy)
    result.wheel_ticks_y = dy > 0.0f ? 1.0f : -1.0f;
  result.has_precise_scrolling_deltas = precise;
  result.scroll_by_page = scroll_by_page;
  result.event_action = WebMouseWheelEventTraits::GetEventAction(result);
  return result;
}

WebKeyboardEvent SyntheticWebKeyboardEventBuilder::Build(
    WebInputEvent::Type type) {
  DCHECK(WebInputEvent::IsKeyboardEventType(type));
  WebKeyboardEvent result(type, WebInputEvent::kNoModifiers,
                          ui::EventTimeForNow());
  result.windows_key_code = ui::VKEY_L;  // non-null made up value.
  return result;
}

WebGestureEvent SyntheticWebGestureEventBuilder::Build(
    WebInputEvent::Type type,
    blink::WebGestureDevice source_device,
    int modifiers) {
  DCHECK(WebInputEvent::IsGestureEventType(type));
  WebGestureEvent result(type, modifiers, ui::EventTimeForNow(), source_device);
  if (type == WebInputEvent::kGestureTap ||
      type == WebInputEvent::kGestureTapUnconfirmed ||
      type == WebInputEvent::kGestureDoubleTap) {
    result.data.tap.tap_count = 1;
    result.data.tap.width = 10;
    result.data.tap.height = 10;
  } else if (WebInputEvent::IsPinchGestureEventType(type)) {
    result.SetNeedsWheelEvent(source_device ==
                              blink::kWebGestureDeviceTouchpad);
  }
  return result;
}

WebGestureEvent SyntheticWebGestureEventBuilder::BuildScrollBegin(
    float dx_hint,
    float dy_hint,
    blink::WebGestureDevice source_device,
    int pointer_count) {
  WebGestureEvent result =
      Build(WebInputEvent::kGestureScrollBegin, source_device);
  result.data.scroll_begin.delta_x_hint = dx_hint;
  result.data.scroll_begin.delta_y_hint = dy_hint;
  result.data.scroll_begin.pointer_count = pointer_count;
  return result;
}

WebGestureEvent SyntheticWebGestureEventBuilder::BuildScrollUpdate(
    float dx,
    float dy,
    int modifiers,
    blink::WebGestureDevice source_device) {
  WebGestureEvent result =
      Build(WebInputEvent::kGestureScrollUpdate, source_device, modifiers);
  result.data.scroll_update.delta_x = dx;
  result.data.scroll_update.delta_y = dy;
  return result;
}

WebGestureEvent SyntheticWebGestureEventBuilder::BuildPinchUpdate(
    float scale,
    float anchor_x,
    float anchor_y,
    int modifiers,
    blink::WebGestureDevice source_device) {
  WebGestureEvent result =
      Build(WebInputEvent::kGesturePinchUpdate, source_device, modifiers);
  result.data.pinch_update.scale = scale;
  result.SetPositionInWidget(blink::WebFloatPoint(anchor_x, anchor_y));
  result.SetPositionInScreen(blink::WebFloatPoint(anchor_x, anchor_y));
  return result;
}

WebGestureEvent SyntheticWebGestureEventBuilder::BuildFling(
    float velocity_x,
    float velocity_y,
    blink::WebGestureDevice source_device) {
  WebGestureEvent result =
      Build(WebInputEvent::kGestureFlingStart, source_device);
  result.data.fling_start.velocity_x = velocity_x;
  result.data.fling_start.velocity_y = velocity_y;
  return result;
}

SyntheticWebTouchEvent::SyntheticWebTouchEvent() : WebTouchEvent() {
  unique_touch_event_id = ui::GetNextTouchEventId();
  SetTimestamp(ui::EventTimeForNow());
  pointer_id_ = 0;
}

void SyntheticWebTouchEvent::ResetPoints() {
  int activePointCount = 0;
  unsigned count = 0;
  for (unsigned int i = 0; i < kTouchesLengthCap; ++i) {
    switch (touches[i].state) {
      case WebTouchPoint::kStatePressed:
      case WebTouchPoint::kStateMoved:
      case WebTouchPoint::kStateStationary:
        touches[i].state = WebTouchPoint::kStateStationary;
        ++activePointCount;
        ++count;
        break;
      case WebTouchPoint::kStateReleased:
      case WebTouchPoint::kStateCancelled:
        touches[i] = WebTouchPoint();
        ++count;
        break;
      case WebTouchPoint::kStateUndefined:
        break;
    }
    if (count >= touches_length)
      break;
  }
  touches_length = activePointCount;
  type_ = WebInputEvent::kUndefined;
  moved_beyond_slop_region = false;
  unique_touch_event_id = ui::GetNextTouchEventId();
}

int SyntheticWebTouchEvent::PressPoint(float x, float y) {
  int index = FirstFreeIndex();
  if (index == -1)
    return -1;
  WebTouchPoint& point = touches[index];
  point.id = pointer_id_++;
  point.SetPositionInWidget(x, y);
  point.SetPositionInScreen(x, y);
  point.state = WebTouchPoint::kStatePressed;
  // Use radius != default(25.f) to test the value is propagated correctly
  point.radius_x = point.radius_y = 20.f;
  point.rotation_angle = 1.f;
  point.force = 1.f;
  point.tilt_x = point.tilt_y = 0;
  point.pointer_type = blink::WebPointerProperties::PointerType::kTouch;
  ++touches_length;
  WebTouchEventTraits::ResetType(WebInputEvent::kTouchStart, TimeStamp(), this);
  return index;
}

void SyntheticWebTouchEvent::MovePoint(int index, float x, float y) {
  CHECK_GE(index, 0);
  CHECK_LT(index, kTouchesLengthCap);
  // Always set this bit to avoid otherwise unexpected touchmove suppression.
  // The caller can opt-out explicitly, if necessary.
  moved_beyond_slop_region = true;
  WebTouchPoint& point = touches[index];
  point.SetPositionInWidget(x, y);
  point.SetPositionInScreen(x, y);
  touches[index].state = WebTouchPoint::kStateMoved;
  WebTouchEventTraits::ResetType(WebInputEvent::kTouchMove, TimeStamp(), this);
}

void SyntheticWebTouchEvent::ReleasePoint(int index) {
  CHECK_GE(index, 0);
  CHECK_LT(index, kTouchesLengthCap);
  touches[index].state = WebTouchPoint::kStateReleased;
  touches[index].force = 0.f;
  WebTouchEventTraits::ResetType(WebInputEvent::kTouchEnd, TimeStamp(), this);
}

void SyntheticWebTouchEvent::CancelPoint(int index) {
  CHECK_GE(index, 0);
  CHECK_LT(index, kTouchesLengthCap);
  touches[index].state = WebTouchPoint::kStateCancelled;
  WebTouchEventTraits::ResetType(WebInputEvent::kTouchCancel, TimeStamp(),
                                 this);
}

void SyntheticWebTouchEvent::SetTimestamp(base::TimeTicks timestamp) {
  SetTimeStamp(timestamp);
}

int SyntheticWebTouchEvent::FirstFreeIndex() {
  for (size_t i = 0; i < kTouchesLengthCap; ++i) {
    if (touches[i].state == WebTouchPoint::kStateUndefined)
      return i;
  }
  return -1;
}

}  // namespace content
