// 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 <limits.h>

#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/android/scroller.h"

namespace ui {
namespace {

const float kDefaultStartX = 7.f;
const float kDefaultStartY = 25.f;
const float kDefaultDeltaX = -20.f;
const float kDefaultDeltaY = 73.f;
const float kDefaultVelocityX = -350.f;
const float kDefaultVelocityY = 220.f;
const float kEpsilon = 1e-3f;

Scroller::Config DefaultConfig() {
  return Scroller::Config();
}

}  // namespace

class ScrollerTest : public testing::Test {};

TEST_F(ScrollerTest, Scroll) {
  Scroller scroller(DefaultConfig());
  base::TimeTicks start_time = base::TimeTicks::Now();

  // Start a scroll and verify initialized values.
  scroller.StartScroll(kDefaultStartX,
                       kDefaultStartY,
                       kDefaultDeltaX,
                       kDefaultDeltaY,
                       start_time);

  EXPECT_EQ(kDefaultStartX, scroller.GetStartX());
  EXPECT_EQ(kDefaultStartY, scroller.GetStartY());
  EXPECT_EQ(kDefaultStartX, scroller.GetCurrX());
  EXPECT_EQ(kDefaultStartY, scroller.GetCurrY());
  EXPECT_EQ(kDefaultStartX + kDefaultDeltaX, scroller.GetFinalX());
  EXPECT_EQ(kDefaultStartY + kDefaultDeltaY, scroller.GetFinalY());
  EXPECT_FALSE(scroller.IsFinished());
  EXPECT_EQ(base::TimeDelta(), scroller.GetTimePassed());

  // Advance halfway through the scroll.
  const base::TimeDelta scroll_duration = scroller.GetDuration();
  gfx::Vector2dF offset, velocity;
  EXPECT_TRUE(scroller.ComputeScrollOffset(
      start_time + scroll_duration / 2, &offset, &velocity));

  // Ensure we've moved in the direction of the delta, but have yet to reach
  // the target.
  EXPECT_GT(kDefaultStartX, offset.x());
  EXPECT_LT(kDefaultStartY, offset.y());
  EXPECT_LT(scroller.GetFinalX(), offset.x());
  EXPECT_GT(scroller.GetFinalY(), offset.y());
  EXPECT_FALSE(scroller.IsFinished());

  // Ensure our velocity is non-zero and in the same direction as the delta.
  EXPECT_GT(0.f, velocity.x() * kDefaultDeltaX);
  EXPECT_GT(0.f, velocity.y() * kDefaultDeltaY);
  EXPECT_TRUE(scroller.IsScrollingInDirection(kDefaultDeltaX, kDefaultDeltaY));

  // Repeated offset computations at the same timestamp should yield identical
  // results.
  float curr_x = offset.x();
  float curr_y = offset.y();
  float curr_velocity_x = velocity.x();
  float curr_velocity_y = velocity.y();
  EXPECT_TRUE(scroller.ComputeScrollOffset(
      start_time + scroll_duration / 2, &offset, &velocity));
  EXPECT_EQ(curr_x, offset.x());
  EXPECT_EQ(curr_y, offset.y());
  EXPECT_EQ(curr_velocity_x, velocity.x());
  EXPECT_EQ(curr_velocity_y, velocity.y());

  // Advance to the end.
  EXPECT_FALSE(scroller.ComputeScrollOffset(
      start_time + scroll_duration, &offset, &velocity));
  EXPECT_EQ(scroller.GetFinalX(), offset.x());
  EXPECT_EQ(scroller.GetFinalY(), offset.y());
  EXPECT_TRUE(scroller.IsFinished());
  EXPECT_EQ(scroll_duration, scroller.GetTimePassed());
  EXPECT_NEAR(0.f, velocity.x(), kEpsilon);
  EXPECT_NEAR(0.f, velocity.y(), kEpsilon);

  // Try to advance further; nothing should change.
  EXPECT_FALSE(scroller.ComputeScrollOffset(
      start_time + scroll_duration * 2, &offset, &velocity));
  EXPECT_EQ(scroller.GetFinalX(), offset.x());
  EXPECT_EQ(scroller.GetFinalY(), offset.y());
  EXPECT_TRUE(scroller.IsFinished());
  EXPECT_EQ(scroll_duration, scroller.GetTimePassed());
}

TEST_F(ScrollerTest, Fling) {
  Scroller scroller(DefaultConfig());
  base::TimeTicks start_time = base::TimeTicks::Now();

  // Start a fling and verify initialized values.
  scroller.Fling(kDefaultStartX,
                 kDefaultStartY,
                 kDefaultVelocityX,
                 kDefaultVelocityY,
                 INT_MIN,
                 INT_MAX,
                 INT_MIN,
                 INT_MAX,
                 start_time);

  EXPECT_EQ(kDefaultStartX, scroller.GetStartX());
  EXPECT_EQ(kDefaultStartY, scroller.GetStartY());
  EXPECT_EQ(kDefaultStartX, scroller.GetCurrX());
  EXPECT_EQ(kDefaultStartY, scroller.GetCurrY());
  EXPECT_GT(kDefaultStartX, scroller.GetFinalX());
  EXPECT_LT(kDefaultStartY, scroller.GetFinalY());
  EXPECT_FALSE(scroller.IsFinished());
  EXPECT_EQ(base::TimeDelta(), scroller.GetTimePassed());

  // Advance halfway through the fling.
  const base::TimeDelta scroll_duration = scroller.GetDuration();
  gfx::Vector2dF offset, velocity;
  scroller.ComputeScrollOffset(
      start_time + scroll_duration / 2, &offset, &velocity);

  // Ensure we've moved in the direction of the velocity, but have yet to reach
  // the target.
  EXPECT_GT(kDefaultStartX, offset.x());
  EXPECT_LT(kDefaultStartY, offset.y());
  EXPECT_LT(scroller.GetFinalX(), offset.x());
  EXPECT_GT(scroller.GetFinalY(), offset.y());
  EXPECT_FALSE(scroller.IsFinished());

  // Ensure our velocity is non-zero and in the same direction as the original
  // velocity.
  EXPECT_LT(0.f, velocity.x() * kDefaultVelocityX);
  EXPECT_LT(0.f, velocity.y() * kDefaultVelocityY);
  EXPECT_TRUE(
      scroller.IsScrollingInDirection(kDefaultVelocityX, kDefaultVelocityY));

  // Repeated offset computations at the same timestamp should yield identical
  // results.
  float curr_x = offset.x();
  float curr_y = offset.y();
  float curr_velocity_x = velocity.x();
  float curr_velocity_y = velocity.y();
  EXPECT_TRUE(scroller.ComputeScrollOffset(
      start_time + scroll_duration / 2, &offset, &velocity));
  EXPECT_EQ(curr_x, offset.x());
  EXPECT_EQ(curr_y, offset.y());
  EXPECT_EQ(curr_velocity_x, velocity.x());
  EXPECT_EQ(curr_velocity_y, velocity.y());

  // Advance to the end.
  EXPECT_FALSE(scroller.ComputeScrollOffset(
      start_time + scroll_duration, &offset, &velocity));
  EXPECT_EQ(scroller.GetFinalX(), offset.x());
  EXPECT_EQ(scroller.GetFinalY(), offset.y());
  EXPECT_TRUE(scroller.IsFinished());
  EXPECT_EQ(scroll_duration, scroller.GetTimePassed());
  EXPECT_NEAR(0.f, velocity.x(), kEpsilon);
  EXPECT_NEAR(0.f, velocity.y(), kEpsilon);

  // Try to advance further; nothing should change.
  EXPECT_FALSE(scroller.ComputeScrollOffset(
      start_time + scroll_duration * 2, &offset, &velocity));
  EXPECT_EQ(scroller.GetFinalX(), offset.x());
  EXPECT_EQ(scroller.GetFinalY(), offset.y());
  EXPECT_TRUE(scroller.IsFinished());
  EXPECT_EQ(scroll_duration, scroller.GetTimePassed());
}

}  // namespace ui
