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

#include "ui/events/blink/fling_booster.h"

#include "base/trace_event/trace_event.h"

#if BUILDFLAG(ARKWEB_FLING)
#include "arkweb/chromium_ext/ui/events/blink/fling_booster_utils.h"
#endif  // BUILDFLAG(ARKWEB_FLING)
using blink::WebGestureEvent;
using blink::WebInputEvent;

namespace {
// Minimum fling velocity required for the active fling and new fling for the
// two to accumulate.
const double kMinBoostFlingSpeedSquare = 350. * 350.;

// Minimum velocity for the active touch scroll to preserve (boost) an active
// fling for which cancellation has been deferred.
const double kMinBoostTouchScrollSpeedSquare = 150 * 150.;

// Timeout window after which the active fling will be cancelled if no animation
// ticks, scrolls or flings of sufficient velocity relative to the current fling
// are received. The default value on Android native views is 40ms, but we use a
// slightly increased value to accomodate small IPC message delays.
constexpr base::TimeDelta kFlingBoostTimeoutDelay = base::Seconds(0.05);
}  // namespace

namespace ui {

gfx::Vector2dF FlingBooster::GetVelocityForFlingStart(
    const blink::WebGestureEvent& fling_start) {
  DCHECK_EQ(blink::WebInputEvent::Type::kGestureFlingStart,
            fling_start.GetType());
  gfx::Vector2dF velocity(fling_start.data.fling_start.velocity_x,
                          fling_start.data.fling_start.velocity_y);

#if BUILDFLAG(ARKWEB_FLING)
    ScaleVelocity(fling_start, velocity);
#endif  // BUILDFLAG(ARKWEB_FLING)
  TRACE_EVENT2("input", "FlingBooster::GetVelocityForFlingStart", "vx",
               velocity.x(), "vy", velocity.y());

  if (ShouldBoostFling(fling_start)) {
    velocity += previous_fling_starting_velocity_;
#if BUILDFLAG(ARKWEB_FLING)
    LimitVelocity(velocity, max_fling_velocity_);
#endif
    TRACE_EVENT_INSTANT2("input", "Boosted", TRACE_EVENT_SCOPE_THREAD, "vx",
                         velocity.x(), "vy", velocity.y());
  }

  Reset();

#if BUILDFLAG(ARKWEB_FLING)
  ShouldLimitFlingVelocity(velocity, max_fling_velocity_);
#endif

  previous_fling_starting_velocity_ = velocity;
  current_fling_velocity_ = velocity;
  source_device_ = fling_start.SourceDevice();
  modifiers_ = fling_start.GetModifiers();

  return previous_fling_starting_velocity_;
}

void FlingBooster::ObserveGestureEvent(const WebGestureEvent& gesture_event) {
  TRACE_EVENT1("input", "FlingBooster::ObserveGestureEvent", "type",
               WebInputEvent::GetName(gesture_event.GetType()));
  if (previous_fling_starting_velocity_.IsZero())
    return;

  // If a cutoff time is set (we're waiting for a boost) and we've now exceeded
  // it, reset the booster since we're not going to boost the current gesture.
  if (!cutoff_time_for_boost_.is_null() &&
      gesture_event.TimeStamp() > cutoff_time_for_boost_) {
    TRACE_EVENT_INSTANT0("input", "Timeout", TRACE_EVENT_SCOPE_THREAD);
    Reset();
    return;
  }

  // Gestures from a different source should prevent boosting.
  if (gesture_event.SourceDevice() != source_device_) {
    Reset();
    return;
  }

  switch (gesture_event.GetType()) {
    case WebInputEvent::Type::kGestureScrollBegin: {
      cutoff_time_for_boost_ =
          gesture_event.TimeStamp() + kFlingBoostTimeoutDelay;
      break;
    }
    case WebInputEvent::Type::kGestureScrollUpdate: {
      if (gesture_event.data.scroll_update.inertial_phase ==
          WebGestureEvent::InertialPhaseState::kMomentum) {
        return;
      }

      if (cutoff_time_for_boost_.is_null())
        return;

      // If the user scrolls in a direction counter to the current scroll, don't
      // boost.
      gfx::Vector2dF delta(gesture_event.data.scroll_update.delta_x,
                           gesture_event.data.scroll_update.delta_y);
      if (gfx::DotProduct(previous_fling_starting_velocity_, delta) <= 0) {
        TRACE_EVENT_INSTANT0("input", "Direction", TRACE_EVENT_SCOPE_THREAD);
        Reset();
        return;
      }

      // Scrolls must be of sufficient velocity to maintain the active fling.
      // Unfortunately we can't simply use the velocity_x|y fields on the
      // gesture event because they're not populated when converting from
      // Android's MotionEvents.
      if (!previous_boosting_scroll_timestamp_.is_null()) {
        const double time_since_last_boost_event =
            (gesture_event.TimeStamp() - previous_boosting_scroll_timestamp_)
                .InSecondsF();
        if (time_since_last_boost_event >= 0.001) {
          const gfx::Vector2dF scroll_velocity =
              gfx::ScaleVector2d(delta, 1. / time_since_last_boost_event);
          if (scroll_velocity.LengthSquared() <
              kMinBoostTouchScrollSpeedSquare) {
            TRACE_EVENT_INSTANT0("input", "Velocity", TRACE_EVENT_SCOPE_THREAD);
            Reset();
            return;
          }
        }
      }

      previous_boosting_scroll_timestamp_ = gesture_event.TimeStamp();
      cutoff_time_for_boost_ =
          gesture_event.TimeStamp() + kFlingBoostTimeoutDelay;
      break;
    }
    case WebInputEvent::Type::kGestureScrollEnd: {
      previous_boosting_scroll_timestamp_ = base::TimeTicks();
      break;
    }
    case WebInputEvent::Type::kGestureFlingCancel: {
      if (gesture_event.data.fling_cancel.prevent_boosting) {
        TRACE_EVENT_INSTANT0("input", "GFC PreventBoosting",
                             TRACE_EVENT_SCOPE_THREAD);
        Reset();
        return;
      }

      previous_boosting_scroll_timestamp_ = base::TimeTicks();
      cutoff_time_for_boost_ =
          gesture_event.TimeStamp() + kFlingBoostTimeoutDelay;
      break;
    }
    default:
      break;
  }
}

void FlingBooster::ObserveProgressFling(
    const gfx::Vector2dF& current_velocity) {
  TRACE_EVENT2("input", "FlingBooster::ObserveProgressFling", "vx",
               current_velocity.x(), "vy", current_velocity.y());
  if (previous_fling_starting_velocity_.IsZero())
    return;
  current_fling_velocity_ = current_velocity;
}

bool FlingBooster::ShouldBoostFling(const WebGestureEvent& fling_start_event) {
  DCHECK_EQ(WebInputEvent::Type::kGestureFlingStart,
            fling_start_event.GetType());
  if (previous_fling_starting_velocity_.IsZero()) {
    TRACE_EVENT_INSTANT0("input", "No Boost - NoActiveFling",
                         TRACE_EVENT_SCOPE_THREAD);
    return false;
  }

  if (source_device_ != fling_start_event.SourceDevice()) {
    TRACE_EVENT_INSTANT0("input", "No Boost - SourceDevice",
                         TRACE_EVENT_SCOPE_THREAD);
    return false;
  }

  if (modifiers_ != fling_start_event.GetModifiers()) {
    TRACE_EVENT_INSTANT0("input", "No Boost - Modifiers",
                         TRACE_EVENT_SCOPE_THREAD);
    return false;
  }

  if (cutoff_time_for_boost_.is_null()) {
    TRACE_EVENT_INSTANT0("input", "No Boost - CutoffTimeUnset",
                         TRACE_EVENT_SCOPE_THREAD);
    return false;
  }

  if (fling_start_event.TimeStamp() > cutoff_time_for_boost_) {
    TRACE_EVENT_INSTANT0("input", "No Boost - Timeout",
                         TRACE_EVENT_SCOPE_THREAD);
    return false;
  }

  gfx::Vector2dF new_fling_velocity(
      fling_start_event.data.fling_start.velocity_x,
      fling_start_event.data.fling_start.velocity_y);

  if (gfx::DotProduct(previous_fling_starting_velocity_, new_fling_velocity) <=
      0) {
    TRACE_EVENT_INSTANT0("input", "No Boost - Direction",
                         TRACE_EVENT_SCOPE_THREAD);
    return false;
  }

  if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare) {
    TRACE_EVENT_INSTANT0("input", "No Boost - CurrentVelocity",
                         TRACE_EVENT_SCOPE_THREAD);
    return false;
  }

  if (new_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare) {
    TRACE_EVENT_INSTANT0("input", "No Boost - NewVelocity",
                         TRACE_EVENT_SCOPE_THREAD);
    return false;
  }

  return true;
}

#if BUILDFLAG(ARKWEB_FLING)
void FlingBooster::UpdateFlingVelocityLimit(const gfx::Vector2dF& velocity) {
  if (velocity == max_fling_velocity_)
    return;
 
  LOG(INFO) << "[flingTracker] update max fling velocity:"
            << max_fling_velocity_.ToString() << " -> " << velocity.ToString();
  max_fling_velocity_ = velocity;
}
#endif

void FlingBooster::Reset() {
  TRACE_EVENT0("input", "FlingBooster::Reset");
  cutoff_time_for_boost_ = base::TimeTicks();
  previous_fling_starting_velocity_ = gfx::Vector2dF();
  current_fling_velocity_ = gfx::Vector2dF();
  source_device_ = blink::WebGestureDevice::kUninitialized;
  modifiers_ = 0;
  previous_boosting_scroll_timestamp_ = base::TimeTicks();
}

}  // namespace ui