// 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 "content/common/input/input_event_struct_traits.h"

#include "base/i18n/char_iterator.h"
#include "content/common/input_messages.h"
#include "third_party/WebKit/public/platform/WebKeyboardEvent.h"
#include "ui/latency/mojo/latency_info_struct_traits.h"

namespace mojo {
namespace {

void CopyString(blink::WebUChar* dst, const base::string16& text) {
  base::i18n::UTF16CharIterator iter(&text);
  size_t pos = 0;
  while (!iter.end() && pos < blink::WebKeyboardEvent::kTextLengthCap - 1) {
    dst[pos++] = iter.get();
    iter.Advance();
  }
  dst[pos] = '\0';
}

content::mojom::PointerDataPtr PointerDataFromPointerProperties(
    const blink::WebPointerProperties& pointer,
    content::mojom::MouseDataPtr mouse_data) {
  return content::mojom::PointerData::New(
      pointer.id, pointer.force, pointer.tilt_x, pointer.tilt_y,
      pointer.tangential_pressure, pointer.twist, pointer.button,
      pointer.pointer_type, pointer.movement_x, pointer.movement_y,
      pointer.PositionInWidget(), pointer.PositionInScreen(),
      std::move(mouse_data));
}

void PointerPropertiesFromPointerData(
    const content::mojom::PointerDataPtr& pointer_data,
    blink::WebPointerProperties* pointer_properties) {
  pointer_properties->id = pointer_data->pointer_id;
  pointer_properties->force = pointer_data->force;
  pointer_properties->tilt_x = pointer_data->tilt_x;
  pointer_properties->tilt_y = pointer_data->tilt_y;
  pointer_properties->tangential_pressure = pointer_data->tangential_pressure;
  pointer_properties->twist = pointer_data->twist;
  pointer_properties->button = pointer_data->button;
  pointer_properties->pointer_type = pointer_data->pointer_type;
  pointer_properties->movement_x = pointer_data->movement_x;
  pointer_properties->movement_y = pointer_data->movement_y;
}

void TouchPointPropertiesFromPointerData(
    const content::mojom::TouchPointPtr& mojo_touch_point,
    blink::WebTouchPoint* touch_point) {
  PointerPropertiesFromPointerData(mojo_touch_point->pointer_data, touch_point);
  touch_point->state = mojo_touch_point->state;
  touch_point->radius_x = mojo_touch_point->radius_x;
  touch_point->radius_y = mojo_touch_point->radius_y;
  touch_point->rotation_angle = mojo_touch_point->rotation_angle;
  touch_point->SetPositionInWidget(
      mojo_touch_point->pointer_data->widget_position.x(),
      mojo_touch_point->pointer_data->widget_position.y());
  touch_point->SetPositionInScreen(
      mojo_touch_point->pointer_data->screen_position.x(),
      mojo_touch_point->pointer_data->screen_position.y());
}

// TODO(dtapuska): Remove once SetPositionInXXX moves to WebPointerProperties.
void MouseEventPropertiesFromPointerData(
    const content::mojom::PointerDataPtr& pointer_data,
    blink::WebMouseEvent* mouse_event) {
  PointerPropertiesFromPointerData(pointer_data, mouse_event);
  mouse_event->SetPositionInWidget(pointer_data->widget_position.x(),
                                   pointer_data->widget_position.y());
  mouse_event->SetPositionInScreen(pointer_data->screen_position.x(),
                                   pointer_data->screen_position.y());
}

}  // namespace

bool StructTraits<content::mojom::EventDataView, InputEventUniquePtr>::Read(
    content::mojom::EventDataView event,
    InputEventUniquePtr* out) {
  DCHECK(!out->get());

  out->reset(new content::InputEvent());

  blink::WebInputEvent::Type type;
  if (!event.ReadType(&type))
    return false;

  if (blink::WebInputEvent::IsKeyboardEventType(type)) {
    content::mojom::KeyDataPtr key_data;
    if (!event.ReadKeyData<content::mojom::KeyDataPtr>(&key_data))
      return false;

    (*out)->web_event.reset(new blink::WebKeyboardEvent(
        type, event.modifiers(), event.timestamp_seconds()));

    blink::WebKeyboardEvent* key_event =
        static_cast<blink::WebKeyboardEvent*>((*out)->web_event.get());
    key_event->windows_key_code = key_data->windows_key_code;
    key_event->native_key_code = key_data->native_key_code;
    key_event->dom_code = key_data->dom_code;
    key_event->dom_key = key_data->dom_key;
    key_event->is_system_key = key_data->is_system_key;
    key_event->is_browser_shortcut = key_data->is_browser_shortcut;
    CopyString(key_event->text, key_data->text);
    CopyString(key_event->unmodified_text, key_data->unmodified_text);
  } else if (blink::WebInputEvent::IsGestureEventType(type)) {
    content::mojom::GestureDataPtr gesture_data;
    if (!event.ReadGestureData<content::mojom::GestureDataPtr>(&gesture_data))
      return false;
    (*out)->web_event.reset(new blink::WebGestureEvent(
        type, event.modifiers(), event.timestamp_seconds()));

    blink::WebGestureEvent* gesture_event =
        static_cast<blink::WebGestureEvent*>((*out)->web_event.get());
    gesture_event->x = gesture_data->widget_position.x();
    gesture_event->y = gesture_data->widget_position.y();
    gesture_event->global_x = gesture_data->screen_position.x();
    gesture_event->global_y = gesture_data->screen_position.y();
    gesture_event->source_device = gesture_data->source_device;
    gesture_event->unique_touch_event_id = gesture_data->unique_touch_event_id;
    gesture_event->resending_plugin_id = gesture_data->resending_plugin_id;

    if (gesture_data->contact_size) {
      switch (type) {
        default:
          break;
        case blink::WebInputEvent::Type::kGestureTapDown:
        case blink::WebInputEvent::Type::kGestureTapUnconfirmed:
        case blink::WebInputEvent::Type::kGestureDoubleTap:
          gesture_event->data.tap_down.width =
              gesture_data->contact_size->width();
          gesture_event->data.tap_down.height =
              gesture_data->contact_size->height();
          break;
        case blink::WebInputEvent::Type::kGestureShowPress:
          gesture_event->data.show_press.width =
              gesture_data->contact_size->width();
          gesture_event->data.show_press.height =
              gesture_data->contact_size->height();
          break;
        case blink::WebInputEvent::Type::kGestureTap:
          gesture_event->data.tap.width = gesture_data->contact_size->width();
          gesture_event->data.tap.height = gesture_data->contact_size->height();
          break;
        case blink::WebInputEvent::Type::kGestureLongPress:
          gesture_event->data.long_press.width =
              gesture_data->contact_size->width();
          gesture_event->data.long_press.height =
              gesture_data->contact_size->height();
          break;

        case blink::WebInputEvent::Type::kGestureTwoFingerTap:
          gesture_event->data.two_finger_tap.first_finger_width =
              gesture_data->contact_size->width();
          gesture_event->data.two_finger_tap.first_finger_height =
              gesture_data->contact_size->height();
          break;
        case blink::WebInputEvent::Type::kGestureScrollBegin:
          gesture_event->data.scroll_begin.delta_x_hint =
              gesture_data->scroll_data->delta_x;
          gesture_event->data.scroll_begin.delta_y_hint =
              gesture_data->scroll_data->delta_y;
          gesture_event->data.scroll_begin.delta_hint_units =
              gesture_data->scroll_data->delta_units;
          gesture_event->data.scroll_begin.target_viewport =
              gesture_data->scroll_data->target_viewport;
          gesture_event->data.scroll_begin.inertial_phase =
              gesture_data->scroll_data->inertial_phase;
          gesture_event->data.scroll_begin.synthetic =
              gesture_data->scroll_data->synthetic;
          gesture_event->data.scroll_begin.pointer_count =
              gesture_data->scroll_data->pointer_count;
          break;
        case blink::WebInputEvent::Type::kGestureScrollEnd:
          gesture_event->data.scroll_end.delta_units =
              gesture_data->scroll_data->delta_units;
          gesture_event->data.scroll_end.inertial_phase =
              gesture_data->scroll_data->inertial_phase;
          gesture_event->data.scroll_end.synthetic =
              gesture_data->scroll_data->synthetic;
          break;
        case blink::WebInputEvent::Type::kGestureScrollUpdate:
          gesture_event->data.scroll_update.delta_x =
              gesture_data->scroll_data->delta_x;
          gesture_event->data.scroll_update.delta_y =
              gesture_data->scroll_data->delta_y;
          gesture_event->data.scroll_update.delta_units =
              gesture_data->scroll_data->delta_units;
          gesture_event->data.scroll_update.inertial_phase =
              gesture_data->scroll_data->inertial_phase;
          if (gesture_data->scroll_data->update_details) {
            gesture_event->data.scroll_update.velocity_x =
                gesture_data->scroll_data->update_details->velocity_x;
            gesture_event->data.scroll_update.velocity_y =
                gesture_data->scroll_data->update_details->velocity_y;
            gesture_event->data.scroll_update
                .previous_update_in_sequence_prevented =
                gesture_data->scroll_data->update_details
                    ->previous_update_in_sequence_prevented;
            gesture_event->data.scroll_update.prevent_propagation =
                gesture_data->scroll_data->update_details->prevent_propagation;
          }
          break;
      }
    }

    if (gesture_data->scroll_data) {
      switch (type) {
        default:
          break;
        case blink::WebInputEvent::Type::kGestureScrollBegin:
          gesture_event->data.scroll_begin.delta_x_hint =
              gesture_data->scroll_data->delta_x;
          gesture_event->data.scroll_begin.delta_y_hint =
              gesture_data->scroll_data->delta_y;
          gesture_event->data.scroll_begin.delta_hint_units =
              gesture_data->scroll_data->delta_units;
          gesture_event->data.scroll_begin.target_viewport =
              gesture_data->scroll_data->target_viewport;
          gesture_event->data.scroll_begin.inertial_phase =
              gesture_data->scroll_data->inertial_phase;
          gesture_event->data.scroll_begin.synthetic =
              gesture_data->scroll_data->synthetic;
          gesture_event->data.scroll_begin.pointer_count =
              gesture_data->scroll_data->pointer_count;
          break;
        case blink::WebInputEvent::Type::kGestureScrollEnd:
          gesture_event->data.scroll_end.delta_units =
              gesture_data->scroll_data->delta_units;
          gesture_event->data.scroll_end.inertial_phase =
              gesture_data->scroll_data->inertial_phase;
          gesture_event->data.scroll_end.synthetic =
              gesture_data->scroll_data->synthetic;
          break;
        case blink::WebInputEvent::Type::kGestureScrollUpdate:
          gesture_event->data.scroll_update.delta_x =
              gesture_data->scroll_data->delta_x;
          gesture_event->data.scroll_update.delta_y =
              gesture_data->scroll_data->delta_y;
          gesture_event->data.scroll_update.delta_units =
              gesture_data->scroll_data->delta_units;
          gesture_event->data.scroll_update.inertial_phase =
              gesture_data->scroll_data->inertial_phase;
          if (gesture_data->scroll_data->update_details) {
            gesture_event->data.scroll_update.velocity_x =
                gesture_data->scroll_data->update_details->velocity_x;
            gesture_event->data.scroll_update.velocity_y =
                gesture_data->scroll_data->update_details->velocity_y;
            gesture_event->data.scroll_update
                .previous_update_in_sequence_prevented =
                gesture_data->scroll_data->update_details
                    ->previous_update_in_sequence_prevented;
            gesture_event->data.scroll_update.prevent_propagation =
                gesture_data->scroll_data->update_details->prevent_propagation;
          }
          break;
      }
    }

    if (gesture_data->fling_data) {
      switch (type) {
        default:
          break;
        case blink::WebInputEvent::Type::kGestureFlingStart:
          gesture_event->data.fling_start.velocity_x =
              gesture_data->fling_data->velocity_x;
          gesture_event->data.fling_start.velocity_y =
              gesture_data->fling_data->velocity_y;
          gesture_event->data.fling_start.target_viewport =
              gesture_data->fling_data->target_viewport;
          break;
        case blink::WebInputEvent::Type::kGestureFlingCancel:
          gesture_event->data.fling_cancel.target_viewport =
              gesture_data->fling_data->target_viewport;
          gesture_event->data.fling_cancel.prevent_boosting =
              gesture_data->fling_data->prevent_boosting;
          break;
      }
    }

    if (gesture_data->pinch_data &&
        type == blink::WebInputEvent::Type::kGesturePinchUpdate) {
      gesture_event->data.pinch_update.zoom_disabled =
          gesture_data->pinch_data->zoom_disabled;
      gesture_event->data.pinch_update.scale = gesture_data->pinch_data->scale;
    }
  } else if (blink::WebInputEvent::IsTouchEventType(type)) {
    content::mojom::TouchDataPtr touch_data;
    if (!event.ReadTouchData<content::mojom::TouchDataPtr>(&touch_data))
      return false;

    (*out)->web_event.reset(new blink::WebTouchEvent(
        type, event.modifiers(), event.timestamp_seconds()));

    blink::WebTouchEvent* touch_event =
        static_cast<blink::WebTouchEvent*>((*out)->web_event.get());
    std::vector<content::mojom::TouchPointPtr> touches;
    unsigned i;
    for (i = 0; i < touch_data->touches.size() &&
                i < blink::WebTouchEvent::kTouchesLengthCap;
         ++i) {
      blink::WebTouchPoint& touch_point = touch_event->touches[i];
      TouchPointPropertiesFromPointerData(touch_data->touches[i], &touch_point);
    }

    touch_event->touches_length = i;
    touch_event->dispatch_type = touch_data->cancelable;
    touch_event->moved_beyond_slop_region =
        touch_data->moved_beyond_slop_region;
    touch_event->touch_start_or_first_touch_move =
        touch_data->touch_start_or_first_move;
    touch_event->unique_touch_event_id = touch_data->unique_touch_event_id;
  } else if (blink::WebInputEvent::IsMouseEventType(type) ||
             type == blink::WebInputEvent::Type::kMouseWheel) {
    content::mojom::PointerDataPtr pointer_data;
    if (!event.ReadPointerData<content::mojom::PointerDataPtr>(&pointer_data))
      return false;

    if (blink::WebInputEvent::IsMouseEventType(type)) {
      (*out)->web_event.reset(new blink::WebMouseEvent(
          type, event.modifiers(), event.timestamp_seconds()));
    } else {
      (*out)->web_event.reset(new blink::WebMouseWheelEvent(
          type, event.modifiers(), event.timestamp_seconds()));
    }

    blink::WebMouseEvent* mouse_event =
        static_cast<blink::WebMouseEvent*>((*out)->web_event.get());

    MouseEventPropertiesFromPointerData(pointer_data, mouse_event);
    if (pointer_data->mouse_data) {
      mouse_event->click_count = pointer_data->mouse_data->click_count;

      if (type == blink::WebInputEvent::Type::kMouseWheel &&
          pointer_data->mouse_data->wheel_data) {
        blink::WebMouseWheelEvent* wheel_event =
            static_cast<blink::WebMouseWheelEvent*>(mouse_event);
        content::mojom::WheelDataPtr& wheel_data =
            pointer_data->mouse_data->wheel_data;
        wheel_event->delta_x = wheel_data->delta_x;
        wheel_event->delta_y = wheel_data->delta_y;
        wheel_event->wheel_ticks_x = wheel_data->wheel_ticks_x;
        wheel_event->wheel_ticks_y = wheel_data->wheel_ticks_y;
        wheel_event->acceleration_ratio_x = wheel_data->acceleration_ratio_x;
        wheel_event->acceleration_ratio_y = wheel_data->acceleration_ratio_y;
        wheel_event->resending_plugin_id = wheel_data->resending_plugin_id;
        wheel_event->phase =
            static_cast<blink::WebMouseWheelEvent::Phase>(wheel_data->phase);
        wheel_event->momentum_phase =
            static_cast<blink::WebMouseWheelEvent::Phase>(
                wheel_data->momentum_phase);
        wheel_event->scroll_by_page = wheel_data->scroll_by_page;
        wheel_event->has_precise_scrolling_deltas =
            wheel_data->has_precise_scrolling_deltas;
        wheel_event->dispatch_type = wheel_data->cancelable;
      }
    }

  } else {
    return false;
  }

  return event.ReadLatency(&((*out)->latency_info));
}

void* StructTraits<content::mojom::EventDataView,
                   InputEventUniquePtr>::SetUpContext(const InputEventUniquePtr&
                                                          event) {
  InputEventSerializationContext* context =
      new InputEventSerializationContext();

  if (!event->web_event)
    return context;

  if (blink::WebInputEvent::IsKeyboardEventType(event->web_event->GetType())) {
    const blink::WebKeyboardEvent* key_event =
        static_cast<const blink::WebKeyboardEvent*>(event->web_event.get());
    context->key_data = content::mojom::KeyData::New(
        key_event->dom_key, key_event->dom_code, key_event->windows_key_code,
        key_event->native_key_code, key_event->is_system_key,
        key_event->is_browser_shortcut, key_event->text,
        key_event->unmodified_text);
    return context;
  }
  if (blink::WebInputEvent::IsGestureEventType(event->web_event->GetType())) {
    const blink::WebGestureEvent* gesture_event =
        static_cast<const blink::WebGestureEvent*>(event->web_event.get());

    context->gesture_data = content::mojom::GestureData::New();
    content::mojom::GestureDataPtr& gesture_data = context->gesture_data;
    gesture_data->screen_position = gesture_event->PositionInScreen();
    gesture_data->widget_position = gesture_event->PositionInWidget();
    gesture_data->source_device = gesture_event->source_device;
    gesture_data->unique_touch_event_id = gesture_event->unique_touch_event_id;
    gesture_data->resending_plugin_id = gesture_event->resending_plugin_id;

    switch (gesture_event->GetType()) {
      default:
        break;
      case blink::WebInputEvent::Type::kGestureTapDown:
        gesture_data->contact_size =
            gfx::Size(gesture_event->data.tap_down.width,
                      gesture_event->data.tap_down.height);
        break;
      case blink::WebInputEvent::Type::kGestureShowPress:
        gesture_data->contact_size =
            gfx::Size(gesture_event->data.show_press.width,
                      gesture_event->data.show_press.height);
        break;
      case blink::WebInputEvent::Type::kGestureTap:
      case blink::WebInputEvent::Type::kGestureTapUnconfirmed:
      case blink::WebInputEvent::Type::kGestureDoubleTap:
        gesture_data->contact_size = gfx::Size(gesture_event->data.tap.width,
                                               gesture_event->data.tap.height);
        gesture_data->tap_data =
            content::mojom::TapData::New(gesture_event->data.tap.tap_count);
        break;
      case blink::WebInputEvent::Type::kGestureLongPress:
        gesture_data->contact_size =
            gfx::Size(gesture_event->data.long_press.width,
                      gesture_event->data.long_press.height);
        break;

      case blink::WebInputEvent::Type::kGestureTwoFingerTap:
        gesture_data->contact_size =
            gfx::Size(gesture_event->data.two_finger_tap.first_finger_width,
                      gesture_event->data.two_finger_tap.first_finger_height);
        break;
      case blink::WebInputEvent::Type::kGestureScrollBegin:
        gesture_data->scroll_data = content::mojom::ScrollData::New(
            gesture_event->data.scroll_begin.delta_x_hint,
            gesture_event->data.scroll_begin.delta_y_hint,
            gesture_event->data.scroll_begin.delta_hint_units,
            gesture_event->data.scroll_begin.target_viewport,
            gesture_event->data.scroll_begin.inertial_phase,
            gesture_event->data.scroll_begin.synthetic,
            gesture_event->data.scroll_begin.pointer_count, nullptr);
        break;
      case blink::WebInputEvent::Type::kGestureScrollEnd:
        gesture_data->scroll_data = content::mojom::ScrollData::New(
            0, 0, gesture_event->data.scroll_end.delta_units, false,
            gesture_event->data.scroll_end.inertial_phase,
            gesture_event->data.scroll_end.synthetic, 0, nullptr);
        break;
      case blink::WebInputEvent::Type::kGestureScrollUpdate:
        gesture_data->scroll_data = content::mojom::ScrollData::New(
            gesture_event->data.scroll_update.delta_x,
            gesture_event->data.scroll_update.delta_y,
            gesture_event->data.scroll_update.delta_units, false,
            gesture_event->data.scroll_update.inertial_phase, false, 0,
            content::mojom::ScrollUpdate::New(
                gesture_event->data.scroll_update.velocity_x,
                gesture_event->data.scroll_update.velocity_y,
                gesture_event->data.scroll_update
                    .previous_update_in_sequence_prevented,
                gesture_event->data.scroll_update.prevent_propagation));
        break;
      case blink::WebInputEvent::Type::kGestureFlingStart:
        gesture_data->fling_data = content::mojom::FlingData::New(
            gesture_event->data.fling_start.velocity_x,
            gesture_event->data.fling_start.velocity_y,
            gesture_event->data.fling_start.target_viewport, false);
        break;
      case blink::WebInputEvent::Type::kGestureFlingCancel:
        gesture_data->fling_data = content::mojom::FlingData::New(
            0, 0, gesture_event->data.fling_cancel.target_viewport,
            gesture_event->data.fling_cancel.prevent_boosting);
        break;
      case blink::WebInputEvent::Type::kGesturePinchUpdate:
        gesture_data->pinch_data = content::mojom::PinchData::New(
            gesture_event->data.pinch_update.zoom_disabled,
            gesture_event->data.pinch_update.scale);
        break;
    }
    return context;
  }
  if (blink::WebInputEvent::IsTouchEventType(event->web_event->GetType())) {
    const blink::WebTouchEvent* touch_event =
        static_cast<const blink::WebTouchEvent*>(event->web_event.get());

    context->touch_data = content::mojom::TouchData::New(
        touch_event->dispatch_type, touch_event->moved_beyond_slop_region,
        touch_event->touch_start_or_first_touch_move,
        touch_event->unique_touch_event_id,
        std::vector<content::mojom::TouchPointPtr>());

    for (unsigned i = 0; i < touch_event->touches_length; ++i) {
      content::mojom::PointerDataPtr pointer_data =
          PointerDataFromPointerProperties(touch_event->touches[i], nullptr);
      context->touch_data->touches.emplace_back(content::mojom::TouchPoint::New(
          touch_event->touches[i].state, touch_event->touches[i].radius_x,
          touch_event->touches[i].radius_y,
          touch_event->touches[i].rotation_angle, std::move(pointer_data)));
    }

    return context;
  }

  bool is_wheel_event =
      event->web_event->GetType() == blink::WebInputEvent::Type::kMouseWheel;
  if (blink::WebInputEvent::IsMouseEventType(event->web_event->GetType()) ||
      is_wheel_event) {
    const blink::WebMouseEvent* mouse_event =
        static_cast<const blink::WebMouseEvent*>(event->web_event.get());

    content::mojom::WheelDataPtr wheel_data;
    if (is_wheel_event) {
      const blink::WebMouseWheelEvent* wheel_event =
          static_cast<const blink::WebMouseWheelEvent*>(mouse_event);
      wheel_data = content::mojom::WheelData::New(
          wheel_event->delta_x, wheel_event->delta_y,
          wheel_event->wheel_ticks_x, wheel_event->wheel_ticks_y,
          wheel_event->acceleration_ratio_x, wheel_event->acceleration_ratio_y,
          wheel_event->resending_plugin_id, wheel_event->phase,
          wheel_event->momentum_phase, wheel_event->scroll_by_page,
          wheel_event->has_precise_scrolling_deltas,
          wheel_event->dispatch_type);
    }

    context->pointer_data = PointerDataFromPointerProperties(
        *mouse_event, content::mojom::MouseData::New(mouse_event->click_count,
                                                     std::move(wheel_data)));
    return context;
  }

  return context;
}

void StructTraits<content::mojom::EventDataView, InputEventUniquePtr>::
    TearDownContext(const InputEventUniquePtr& event, void* context) {
  delete static_cast<InputEventSerializationContext*>(context);
}

StructTraits<content::mojom::EventDataView, InputEventUniquePtr>::
    InputEventSerializationContext::InputEventSerializationContext() {}

StructTraits<content::mojom::EventDataView, InputEventUniquePtr>::
    InputEventSerializationContext::~InputEventSerializationContext() {}

}  // namespace mojo
