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

#include "components/input/gesture_event_queue.h"

#include "base/auto_reset.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "components/input/touchpad_tap_suppression_controller.h"
#include "components/input/touchscreen_tap_suppression_controller.h"
#include "ui/events/blink/blink_features.h"
#include "ui/events/blink/web_input_event_traits.h"

using blink::WebGestureEvent;
using blink::WebInputEvent;

namespace input {

GestureEventQueue::GestureEventWithLatencyInfoAckState::
    GestureEventWithLatencyInfoAckState(
        const GestureEventWithLatencyInfo& event)
    : GestureEventWithLatencyInfo(event) {}

GestureEventWithLatencyInfoAndCallback::GestureEventWithLatencyInfoAndCallback(
    const GestureEventWithLatencyInfo& event,
    DispatchToRendererCallback callback)
    : GestureEventWithLatencyInfo(event),
      dispatch_callback(std::move(callback)) {}

GestureEventWithLatencyInfoAndCallback::GestureEventWithLatencyInfoAndCallback(
    GestureEventWithLatencyInfoAndCallback&& event)
    : GestureEventWithLatencyInfo(event.event, event.latency),
      dispatch_callback(std::move(event.dispatch_callback)) {}

GestureEventWithLatencyInfoAndCallback::
    ~GestureEventWithLatencyInfoAndCallback() = default;

GestureEventQueue::Config::Config() = default;

GestureEventQueue::GestureEventQueue(
    GestureEventQueueClient* client,
    FlingControllerEventSenderClient* fling_event_sender_client,
    FlingControllerSchedulerClient* fling_scheduler_client,
    const Config& config)
    : client_(client),
      scrolling_in_progress_(false),
      debounce_interval_(config.debounce_interval),
      fling_controller_(fling_event_sender_client,
                        fling_scheduler_client,
                        config.fling_config) {
  DCHECK(client);
  DCHECK(fling_event_sender_client);
  DCHECK(fling_scheduler_client);
}

GestureEventQueue::~GestureEventQueue() = default;

bool GestureEventQueue::DebounceOrForwardEvent(
    const GestureEventWithLatencyInfo& gesture_event,
    DispatchToRendererCallback& dispatch_callback) {
  // GFS and GFC should have been filtered in PassToFlingController.
  DCHECK_NE(gesture_event.event.GetType(),
            WebInputEvent::Type::kGestureFlingStart);
  DCHECK_NE(gesture_event.event.GetType(),
            WebInputEvent::Type::kGestureFlingCancel);
  if (!ShouldForwardForBounceReduction(gesture_event, dispatch_callback)) {
    return false;
  }

  ForwardGestureEvent(gesture_event, dispatch_callback);
  return true;
}

bool GestureEventQueue::PassToFlingController(
    const GestureEventWithLatencyInfo& gesture_event) {
  return fling_controller_.ObserveAndMaybeConsumeGestureEvent(gesture_event);
}

void GestureEventQueue::QueueDeferredEvents(
    const GestureEventWithLatencyInfo& gesture_event,
    DispatchToRendererCallback& dispatch_callback) {
  deferred_gesture_queue_.emplace_back(gesture_event,
                                       std::move(dispatch_callback));
}

GestureEventQueue::GestureWithCallbackQueue
GestureEventQueue::TakeDeferredEvents() {
  GestureWithCallbackQueue deferred_gesture_events;
  deferred_gesture_events.swap(deferred_gesture_queue_);
  return deferred_gesture_events;
}

void GestureEventQueue::StopFling() {
  fling_controller_.StopFling();
}

gfx::Vector2dF GestureEventQueue::CurrentFlingVelocity() const {
  return fling_controller_.CurrentFlingVelocity();
}

bool GestureEventQueue::FlingInProgressForTest() const {
  return fling_controller_.fling_in_progress();
}

bool GestureEventQueue::ShouldForwardForBounceReduction(
    const GestureEventWithLatencyInfo& gesture_event,
    DispatchToRendererCallback& dispatch_callback) {
  if (debounce_interval_ <= base::TimeDelta()) {
    return true;
  }

  WebInputEvent::Type event_type = gesture_event.event.GetType();
  if (event_type == WebInputEvent::Type::kGestureScrollEnd) {
    // We no longer care about the previous GestureScrollEnd.
    // Note: this needs to be reset before the fling_in_progress() exit.
    scroll_end_filtered_by_deboucing_deferral_queue_ = false;
  }

  // Don't debounce any gesture events while a fling is in progress on the
  // browser side. A GSE event in this case ends fling progress and it shouldn't
  // get cancelled by its next GSB event.
  if (fling_controller_.fling_in_progress()) {
    return true;
  }

  switch (event_type) {
    case WebInputEvent::Type::kGestureScrollUpdate:
      // This will restart the timer if it is already running.
      debounce_deferring_timer_.Start(
          FROM_HERE, debounce_interval_, this,
          &GestureEventQueue::SendScrollEndingEventsNow);
      scrolling_in_progress_ = true;
      debouncing_deferral_queue_.clear();
      return true;

    case WebInputEvent::Type::kGesturePinchBegin:
    case WebInputEvent::Type::kGesturePinchEnd:
    case WebInputEvent::Type::kGesturePinchUpdate:
      return true;
    case WebInputEvent::Type::kGestureScrollBegin:
      // GSB event should not get filtered by the debouncing_deferral_queue_
      // when its previous GSE event is forwarded to the renderer.
      if (!scroll_end_filtered_by_deboucing_deferral_queue_) {
        return true;
      } else {
        debouncing_deferral_queue_.emplace_back(gesture_event,
                                                std::move(dispatch_callback));
        return false;
      }
    case WebInputEvent::Type::kGestureScrollEnd:
      // GSEs generated by the fling controller should not get pushed to the
      // debouncing_deferral_queue_.
      if (gesture_event.event.data.scroll_end.generated_by_fling_controller) {
        scrolling_in_progress_ = false;
        return true;
      }
      if (scrolling_in_progress_) {
        debouncing_deferral_queue_.emplace_back(gesture_event,
                                                std::move(dispatch_callback));
        scroll_end_filtered_by_deboucing_deferral_queue_ = true;
        return false;
      }
      return true;
    default:
      if (scrolling_in_progress_) {
        debouncing_deferral_queue_.emplace_back(gesture_event,
                                                std::move(dispatch_callback));
        return false;
      }
      return true;
  }
}

void GestureEventQueue::ForwardGestureEvent(
    const GestureEventWithLatencyInfo& gesture_event,
    DispatchToRendererCallback& dispatch_callback) {
  // GFS and GFC should have been filtered in PassToFlingController to get
  // handled by fling controller.
  DCHECK_NE(gesture_event.event.GetType(),
            WebInputEvent::Type::kGestureFlingStart);
  DCHECK_NE(gesture_event.event.GetType(),
            WebInputEvent::Type::kGestureFlingCancel);
  sent_events_awaiting_ack_.push_back(gesture_event);
  client_->SendGestureEventImmediately(gesture_event, dispatch_callback);
  // `ForwardGestureEvent` could be called on enqueued events as well, DCHECK
  // ensures the corresponding callback was run.
  CHECK(!dispatch_callback);
}

void GestureEventQueue::ProcessGestureAck(
    blink::mojom::InputEventResultSource ack_source,
    blink::mojom::InputEventResultState ack_result,
    WebInputEvent::Type type,
    const ui::LatencyInfo& latency) {
  TRACE_EVENT0("input", "GestureEventQueue::ProcessGestureAck");

  if (sent_events_awaiting_ack_.empty()) {
    DLOG(ERROR) << "Received unexpected ACK for event type " << type;
    return;
  }

  // ACKs could come back out of order. We want to cache them to restore the
  // original order.
  for (auto& outstanding_event : sent_events_awaiting_ack_) {
    if (outstanding_event.ack_state() !=
        blink::mojom::InputEventResultState::kUnknown) {
      continue;
    }
    if (outstanding_event.event.GetType() == type) {
      outstanding_event.latency.AddNewLatencyFrom(latency);
      outstanding_event.set_ack_info(ack_source, ack_result);
      break;
    }
  }

  AckCompletedEvents();
}

void GestureEventQueue::AckCompletedEvents() {
  // Don't allow re-entrancy into this method otherwise
  // the ordering of acks won't be preserved.
  if (processing_acks_) {
    return;
  }
  base::AutoReset<bool> process_acks(&processing_acks_, true);
  while (!sent_events_awaiting_ack_.empty()) {
    auto iter = sent_events_awaiting_ack_.begin();
    if (iter->ack_state() == blink::mojom::InputEventResultState::kUnknown) {
      break;
    }
    GestureEventWithLatencyInfoAckState event = *iter;
    sent_events_awaiting_ack_.erase(iter);

    AckGestureEventToClient(event, event.ack_source(), event.ack_state());
  }
}

void GestureEventQueue::AckGestureEventToClient(
    const GestureEventWithLatencyInfo& event_with_latency,
    blink::mojom::InputEventResultSource ack_source,
    blink::mojom::InputEventResultState ack_result) {
  client_->OnGestureEventAck(event_with_latency, ack_source, ack_result);
}

TouchpadTapSuppressionController*
GestureEventQueue::GetTouchpadTapSuppressionController() {
  return fling_controller_.GetTouchpadTapSuppressionController();
}

void GestureEventQueue::SendScrollEndingEventsNow() {
  scrolling_in_progress_ = false;
  if (debouncing_deferral_queue_.empty()) {
    return;
  }
  GestureWithCallbackQueue debouncing_deferral_queue;
  debouncing_deferral_queue.swap(debouncing_deferral_queue_);
  for (auto& gesture_with_callback : debouncing_deferral_queue) {
    if (!fling_controller_.ObserveAndMaybeConsumeGestureEvent(
            gesture_with_callback)) {
      if (gesture_with_callback.event.GetType() ==
          WebInputEvent::Type::kGestureScrollEnd) {
        scroll_end_filtered_by_deboucing_deferral_queue_ = false;
      }
      ForwardGestureEvent(gesture_with_callback,
                          gesture_with_callback.dispatch_callback);
    } else {
      std::move(gesture_with_callback.dispatch_callback)
          .Run(gesture_with_callback.event,
               DispatchToRendererResult::kNotDispatched);
    }
  }
}

void GestureEventQueue::OnWheelEventAck(
    const MouseWheelEventWithLatencyInfo& event,
    blink::mojom::InputEventResultSource ack_source,
    blink::mojom::InputEventResultState ack_result) {
  fling_controller_.OnWheelEventAck(event, ack_source, ack_result);
}

}  // namespace input
