// 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 "content/browser/renderer_host/input/gesture_event_queue.h"

#include "base/auto_reset.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
#include "content/browser/renderer_host/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 content {

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

GestureEventQueue::Config::Config() {
}

GestureEventQueue::GestureEventQueue(
    GestureEventQueueClient* client,
    FlingControllerEventSenderClient* fling_event_sender_client,
    FlingControllerSchedulerClient* fling_scheduler_client,
    const Config& config)
    : client_(client),
      fling_in_progress_(false),
      scrolling_in_progress_(false),
      ignore_next_ack_(false),
      allow_multiple_inflight_events_(
          base::FeatureList::IsEnabled(features::kVsyncAlignedInputEvents)),
      debounce_interval_(config.debounce_interval),
      fling_controller_(this,
                        fling_event_sender_client,
                        fling_scheduler_client,
                        config.fling_config) {
  DCHECK(client);
  DCHECK(fling_event_sender_client);
  DCHECK(fling_scheduler_client);
}

GestureEventQueue::~GestureEventQueue() { }

bool GestureEventQueue::DebounceOrQueueEvent(
    const GestureEventWithLatencyInfo& gesture_event) {
  // GFS should have been filtered in FlingControllerFilterEvent.
  DCHECK_NE(gesture_event.event.GetType(), WebInputEvent::kGestureFlingStart);
  if (!ShouldForwardForBounceReduction(gesture_event))
    return false;

  QueueAndForwardIfNecessary(gesture_event);
  return true;
}

bool GestureEventQueue::FlingControllerFilterEvent(
    const GestureEventWithLatencyInfo& gesture_event) {
  TRACE_EVENT0("input", "GestureEventQueue::QueueEvent");
  if (fling_controller_.FilterGestureEvent(gesture_event))
    return true;

  // fling_controller_ is in charge of handling GFS events and the events are
  // not sent to the renderer, the controller processes the fling and generates
  // fling progress events (wheel events for touchpad and GSU events for
  // touchscreen and autoscroll) which are handled normally.
  if (gesture_event.event.GetType() == WebInputEvent::kGestureFlingStart) {
    fling_controller_.ProcessGestureFlingStart(gesture_event);
    fling_in_progress_ = true;
    return true;
  }

  // If the GestureFlingStart event is processed by the fling_controller_, the
  // GestureFlingCancel event should be the same.
  if (gesture_event.event.GetType() == WebInputEvent::kGestureFlingCancel) {
    fling_controller_.ProcessGestureFlingCancel(gesture_event);
    fling_in_progress_ = false;
    return true;
  }

  return false;
}

void GestureEventQueue::StopFling() {
  fling_in_progress_ = false;
  fling_controller_.StopFling();
}

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

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

bool GestureEventQueue::ShouldDiscardFlingCancelEvent(
    const GestureEventWithLatencyInfo& gesture_event) const {
  // When the GFS is processed by the fling_controller_ the controller handles
  // GFC filtering as well.
  DCHECK(!(fling_controller_.fling_in_progress()));

  if (coalesced_gesture_events_.empty() && fling_in_progress_)
    return false;
  GestureQueueWithAckState::const_reverse_iterator it =
      coalesced_gesture_events_.rbegin();
  while (it != coalesced_gesture_events_.rend()) {
    if (it->event.GetType() == WebInputEvent::kGestureFlingStart)
      return false;
    if (it->event.GetType() == WebInputEvent::kGestureFlingCancel)
      return true;
    it++;
  }
  return true;
}

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

  // 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 (gesture_event.event.GetType()) {
    case WebInputEvent::kGestureScrollUpdate:
      if (!scrolling_in_progress_) {
        debounce_deferring_timer_.Start(
            FROM_HERE,
            debounce_interval_,
            this,
            &GestureEventQueue::SendScrollEndingEventsNow);
      } else {
        // Extend the bounce interval.
        debounce_deferring_timer_.Reset();
      }
      scrolling_in_progress_ = true;
      debouncing_deferral_queue_.clear();
      return true;

    case WebInputEvent::kGestureFlingStart:
      // GestureFlingStart shouldn't be debounced because it is a discrete event
      // signalling the end of a scroll
      debounce_deferring_timer_.Stop();
      GestureEventQueue::SendScrollEndingEventsNow();
      return true;

    case WebInputEvent::kGesturePinchBegin:
    case WebInputEvent::kGesturePinchEnd:
    case WebInputEvent::kGesturePinchUpdate:
      // TODO(rjkroege): Debounce pinch (http://crbug.com/147647)
      return true;
    default:
      if (scrolling_in_progress_) {
        debouncing_deferral_queue_.push_back(gesture_event);
        return false;
      }
      return true;
  }
}

void GestureEventQueue::QueueAndForwardIfNecessary(
    const GestureEventWithLatencyInfo& gesture_event) {
  if (allow_multiple_inflight_events_) {
    // Event coalescing should be handled in compositor thread event queue.
    if (gesture_event.event.GetType() == WebInputEvent::kGestureFlingCancel)
      fling_in_progress_ = false;
    else if (gesture_event.event.GetType() == WebInputEvent::kGestureFlingStart)
      fling_in_progress_ = true;
    coalesced_gesture_events_.push_back(gesture_event);
    client_->SendGestureEventImmediately(gesture_event);
    return;
  }

  switch (gesture_event.event.GetType()) {
    case WebInputEvent::kGestureFlingCancel:
      fling_in_progress_ = false;
      break;
    case WebInputEvent::kGestureFlingStart:
      fling_in_progress_ = true;
      break;
    case WebInputEvent::kGesturePinchUpdate:
    case WebInputEvent::kGestureScrollUpdate:
      QueueScrollOrPinchAndForwardIfNecessary(gesture_event);
      return;
    case WebInputEvent::kGestureScrollBegin:
      if (OnScrollBegin(gesture_event))
        return;
      break;
    default:
      break;
  }

  coalesced_gesture_events_.push_back(gesture_event);
  if (coalesced_gesture_events_.size() == 1)
    client_->SendGestureEventImmediately(gesture_event);
}

bool GestureEventQueue::OnScrollBegin(
    const GestureEventWithLatencyInfo& gesture_event) {
  // If a synthetic scroll begin is encountered, it can cancel out a previous
  // synthetic scroll end. This allows a later gesture scroll update to coalesce
  // with the previous one. crbug.com/607340.
  bool synthetic = gesture_event.event.data.scroll_begin.synthetic;
  bool have_unsent_events =
      EventsInFlightCount() < coalesced_gesture_events_.size();
  if (synthetic && have_unsent_events) {
    GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back();
    if (last_event->event.GetType() == WebInputEvent::kGestureScrollEnd &&
        last_event->event.data.scroll_end.synthetic) {
      coalesced_gesture_events_.pop_back();
      return true;
    }
  }
  return false;
}

void GestureEventQueue::ProcessGestureAck(InputEventAckSource ack_source,
                                          InputEventAckState ack_result,
                                          WebInputEvent::Type type,
                                          const ui::LatencyInfo& latency) {
  TRACE_EVENT0("input", "GestureEventQueue::ProcessGestureAck");

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

  if (!allow_multiple_inflight_events_) {
    LegacyProcessGestureAck(ack_source, ack_result, type, latency);
    return;
  }

  // ACKs could come back out of order. We want to cache them to restore the
  // original order.
  for (auto& outstanding_event : coalesced_gesture_events_) {
    if (outstanding_event.ack_state() != INPUT_EVENT_ACK_STATE_UNKNOWN)
      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 (!coalesced_gesture_events_.empty()) {
    auto iter = coalesced_gesture_events_.begin();
    if (iter->ack_state() == INPUT_EVENT_ACK_STATE_UNKNOWN)
      break;
    GestureEventWithLatencyInfoAndAckState event = *iter;
    coalesced_gesture_events_.erase(iter);
    AckGestureEventToClient(event, event.ack_source(), event.ack_state());
  }
}

void GestureEventQueue::AckGestureEventToClient(
    const GestureEventWithLatencyInfo& event_with_latency,
    InputEventAckSource ack_source,
    InputEventAckState ack_result) {
  DCHECK(allow_multiple_inflight_events_);

  // Ack'ing an event may enqueue additional gesture events.  By ack'ing the
  // event before the forwarding of queued events below, such additional events
  // can be coalesced with existing queued events prior to dispatch.
  client_->OnGestureEventAck(event_with_latency, ack_source, ack_result);

  fling_controller_.OnGestureEventAck(event_with_latency, ack_result);
}

void GestureEventQueue::LegacyProcessGestureAck(
    InputEventAckSource ack_source,
    InputEventAckState ack_result,
    WebInputEvent::Type type,
    const ui::LatencyInfo& latency) {
  DCHECK(!allow_multiple_inflight_events_);

  // Events are forwarded one-by-one.
  // It's possible that the ack for the second event in an in-flight,
  // coalesced Gesture{Scroll,Pinch}Update pair is received prior to the first
  // event ack.
  size_t event_index = 0;
  if (ignore_next_ack_ && coalesced_gesture_events_.size() > 1 &&
      coalesced_gesture_events_[0].event.GetType() != type &&
      coalesced_gesture_events_[1].event.GetType() == type) {
    event_index = 1;
  }

  GestureEventWithLatencyInfo event_with_latency =
      coalesced_gesture_events_[event_index];
  DCHECK_EQ(event_with_latency.event.GetType(), type);
  event_with_latency.latency.AddNewLatencyFrom(latency);

  // Ack'ing an event may enqueue additional gesture events.  By ack'ing the
  // event before the forwarding of queued events below, such additional events
  // can be coalesced with existing queued events prior to dispatch.
  client_->OnGestureEventAck(event_with_latency, ack_source, ack_result);

  fling_controller_.OnGestureEventAck(event_with_latency, ack_result);

  DCHECK_LT(event_index, coalesced_gesture_events_.size());
  coalesced_gesture_events_.erase(coalesced_gesture_events_.begin() +
                                  event_index);

  if (ignore_next_ack_) {
    ignore_next_ack_ = false;
    return;
  }

  if (coalesced_gesture_events_.empty())
    return;

  const GestureEventWithLatencyInfo& first_gesture_event =
      coalesced_gesture_events_.front();

  // Check for the coupled GesturePinchUpdate before sending either event,
  // handling the case where the first GestureScrollUpdate ack is synchronous.
  GestureEventWithLatencyInfo second_gesture_event;
  if (first_gesture_event.event.GetType() ==
          WebInputEvent::kGestureScrollUpdate &&
      coalesced_gesture_events_.size() > 1 &&
      coalesced_gesture_events_[1].event.GetType() ==
          WebInputEvent::kGesturePinchUpdate) {
    second_gesture_event = coalesced_gesture_events_[1];
    ignore_next_ack_ = true;
  }

  client_->SendGestureEventImmediately(first_gesture_event);
  if (second_gesture_event.event.GetType() != WebInputEvent::kUndefined)
    client_->SendGestureEventImmediately(second_gesture_event);
}

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

void GestureEventQueue::FlingHasBeenHalted() {
  fling_in_progress_ = false;
}

void GestureEventQueue::ForwardGestureEvent(
    const GestureEventWithLatencyInfo& gesture_event) {
  QueueAndForwardIfNecessary(gesture_event);
}

void GestureEventQueue::SendScrollEndingEventsNow() {
  scrolling_in_progress_ = false;
  if (debouncing_deferral_queue_.empty())
    return;
  GestureQueue debouncing_deferral_queue;
  debouncing_deferral_queue.swap(debouncing_deferral_queue_);
  for (GestureQueue::const_iterator it = debouncing_deferral_queue.begin();
       it != debouncing_deferral_queue.end(); it++) {
    if (!fling_controller_.FilterGestureEvent(*it)) {
      QueueAndForwardIfNecessary(*it);
    }
  }
}

void GestureEventQueue::QueueScrollOrPinchAndForwardIfNecessary(
    const GestureEventWithLatencyInfo& gesture_event) {
  DCHECK_GE(coalesced_gesture_events_.size(), EventsInFlightCount());
  const size_t unsent_events_count =
      coalesced_gesture_events_.size() - EventsInFlightCount();
  if (!unsent_events_count) {
    coalesced_gesture_events_.push_back(gesture_event);
    if (coalesced_gesture_events_.size() == 1) {
      client_->SendGestureEventImmediately(gesture_event);
    } else if (coalesced_gesture_events_.size() == 2) {
      DCHECK(!ignore_next_ack_);
      // If there is an in-flight scroll, the new pinch can be forwarded
      // immediately, avoiding a potential frame delay between the two
      // (similarly for an in-flight pinch with a new scroll).
      const GestureEventWithLatencyInfo& first_event =
          coalesced_gesture_events_.front();
      if (gesture_event.event.GetType() != first_event.event.GetType() &&
          ui::IsCompatibleScrollorPinch(gesture_event.event,
                                        first_event.event)) {
        ignore_next_ack_ = true;
        client_->SendGestureEventImmediately(gesture_event);
      }
    }
    return;
  }

  GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back();
  if (last_event->CanCoalesceWith(gesture_event)) {
    last_event->CoalesceWith(gesture_event);
    return;
  }

  if (!ui::IsCompatibleScrollorPinch(gesture_event.event, last_event->event)) {
    coalesced_gesture_events_.push_back(gesture_event);
    return;
  }

  // Extract the last event in queue.
  blink::WebGestureEvent last_gesture_event =
      coalesced_gesture_events_.back().event;
  DCHECK_LE(coalesced_gesture_events_.back().latency.trace_id(),
            gesture_event.latency.trace_id());
  ui::LatencyInfo oldest_latency = coalesced_gesture_events_.back().latency;
  oldest_latency.set_coalesced();
  coalesced_gesture_events_.pop_back();

  // Extract the second last event in queue.
  ui::WebScopedInputEvent second_last_gesture_event = nullptr;
  if (unsent_events_count > 1 &&
      ui::IsCompatibleScrollorPinch(gesture_event.event,
                                    coalesced_gesture_events_.back().event)) {
    second_last_gesture_event =
        ui::WebInputEventTraits::Clone(coalesced_gesture_events_.back().event);
    DCHECK_LE(coalesced_gesture_events_.back().latency.trace_id(),
              oldest_latency.trace_id());
    oldest_latency = coalesced_gesture_events_.back().latency;
    oldest_latency.set_coalesced();
    coalesced_gesture_events_.pop_back();
  }

  std::pair<blink::WebGestureEvent, blink::WebGestureEvent> coalesced_events =
      ui::CoalesceScrollAndPinch(
          second_last_gesture_event
              ? &ui::ToWebGestureEvent(*second_last_gesture_event)
              : nullptr,
          last_gesture_event, gesture_event.event);

  GestureEventWithLatencyInfo scroll_event;
  scroll_event.event = coalesced_events.first;
  scroll_event.latency = oldest_latency;

  GestureEventWithLatencyInfo pinch_event;
  pinch_event.event = coalesced_events.second;
  pinch_event.latency = oldest_latency;

  coalesced_gesture_events_.push_back(scroll_event);
  coalesced_gesture_events_.push_back(pinch_event);
}

size_t GestureEventQueue::EventsInFlightCount() const {
  if (allow_multiple_inflight_events_) {
    // Currently unused, can be removed if compositor event queue was enabled by
    // default.
    NOTREACHED();
    return coalesced_gesture_events_.size();
  }

  if (coalesced_gesture_events_.empty())
    return 0;

  if (!ignore_next_ack_)
    return 1;

  DCHECK_GT(coalesced_gesture_events_.size(), 1U);
  return 2;
}

}  // namespace content
