// Copyright 2015 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 "ui/compositor/callback_layer_animation_observer.h"

#include <memory>

#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/test/layer_animation_observer_test_api.h"

namespace ui {
namespace test {

// Simple class that tracks whether callbacks were invoked and when.
class TestCallbacks {
 public:
  TestCallbacks();
  virtual ~TestCallbacks();

  void ResetCallbackObservations();

  void set_should_delete_observer_on_animations_ended(
      bool should_delete_observer_on_animations_ended) {
    should_delete_observer_on_animations_ended_ =
        should_delete_observer_on_animations_ended;
  }

  bool animations_started() const { return animations_started_; }

  bool animations_ended() const { return animations_ended_; }

  virtual void AnimationsStarted(const CallbackLayerAnimationObserver&);

  virtual bool AnimationsEnded(const CallbackLayerAnimationObserver&);

  testing::AssertionResult StartedEpochIsBeforeEndedEpoch();

 private:
  // Monotonic counter that tracks the next time snapshot.
  int next_epoch_ = 0;

  // Is true when AnimationsStarted() has been called.
  bool animations_started_ = false;

  // Relative time snapshot of when AnimationsStarted() was last called.
  int animations_started_epoch_ = -1;

  // Is true when AnimationsEnded() has been called.
  bool animations_ended_ = false;

  // Relative time snapshot of when AnimationsEnded() was last called.
  int animations_ended_epoch_ = -1;

  // The return value for AnimationsEnded().
  bool should_delete_observer_on_animations_ended_ = false;

  DISALLOW_COPY_AND_ASSIGN(TestCallbacks);
};

TestCallbacks::TestCallbacks() {}

TestCallbacks::~TestCallbacks() {}

void TestCallbacks::ResetCallbackObservations() {
  next_epoch_ = 0;
  animations_started_ = false;
  animations_started_epoch_ = -1;
  animations_ended_ = false;
  animations_ended_epoch_ = -1;
  should_delete_observer_on_animations_ended_ = false;
}

void TestCallbacks::AnimationsStarted(const CallbackLayerAnimationObserver&) {
  animations_started_ = true;
  animations_started_epoch_ = next_epoch_++;
}

bool TestCallbacks::AnimationsEnded(const CallbackLayerAnimationObserver&) {
  animations_ended_ = true;
  animations_ended_epoch_ = next_epoch_++;
  return should_delete_observer_on_animations_ended_;
}

testing::AssertionResult TestCallbacks::StartedEpochIsBeforeEndedEpoch() {
  if (animations_started_epoch_ < animations_ended_epoch_) {
    return testing::AssertionSuccess();
  } else {
    return testing::AssertionFailure()
           << "The started epoch=" << animations_started_epoch_
           << " is NOT before the ended epoch=" << animations_ended_epoch_;
  }
}

// A child of TestCallbacks that can explicitly delete a
// CallbackLayerAnimationObserver in the AnimationsStarted() or
// AnimationsEnded() callback.
class TestCallbacksThatExplicitlyDeletesObserver : public TestCallbacks {
 public:
  TestCallbacksThatExplicitlyDeletesObserver();

  void set_observer_to_delete_in_animation_started(
      CallbackLayerAnimationObserver* observer) {
    observer_to_delete_in_animation_started_ = observer;
  }

  void set_observer_to_delete_in_animation_ended(
      CallbackLayerAnimationObserver* observer) {
    observer_to_delete_in_animation_ended_ = observer;
  }

  // TestCallbacks:
  void AnimationsStarted(
      const CallbackLayerAnimationObserver& observer) override;
  bool AnimationsEnded(const CallbackLayerAnimationObserver& observer) override;

 private:
  // The observer to delete, if non-NULL, in AnimationsStarted().
  CallbackLayerAnimationObserver* observer_to_delete_in_animation_started_ =
      nullptr;

  // The observer to delete, if non-NULL, in AnimationsEnded().
  CallbackLayerAnimationObserver* observer_to_delete_in_animation_ended_ =
      nullptr;

  DISALLOW_COPY_AND_ASSIGN(TestCallbacksThatExplicitlyDeletesObserver);
};

TestCallbacksThatExplicitlyDeletesObserver::
    TestCallbacksThatExplicitlyDeletesObserver() {}

void TestCallbacksThatExplicitlyDeletesObserver::AnimationsStarted(
    const CallbackLayerAnimationObserver& observer) {
  if (observer_to_delete_in_animation_started_)
    delete observer_to_delete_in_animation_started_;
  TestCallbacks::AnimationsStarted(observer);
}

bool TestCallbacksThatExplicitlyDeletesObserver::AnimationsEnded(
    const CallbackLayerAnimationObserver& observer) {
  if (observer_to_delete_in_animation_ended_)
    delete observer_to_delete_in_animation_ended_;
  return TestCallbacks::AnimationsEnded(observer);
}

// A test specific CallbackLayerAnimationObserver that will set a bool when
// destroyed.
class TestCallbackLayerAnimationObserver
    : public CallbackLayerAnimationObserver {
 public:
  TestCallbackLayerAnimationObserver(
      AnimationStartedCallback animation_started_callback,
      AnimationEndedCallback animation_ended_callback,
      bool* destroyed);

  TestCallbackLayerAnimationObserver(
      AnimationStartedCallback animation_started_callback,
      bool should_delete_observer,
      bool* destroyed);

  TestCallbackLayerAnimationObserver(
      AnimationEndedCallback animation_ended_callback,
      bool* destroyed);

  ~TestCallbackLayerAnimationObserver() override;

 private:
  bool* destroyed_;

  DISALLOW_COPY_AND_ASSIGN(TestCallbackLayerAnimationObserver);
};

TestCallbackLayerAnimationObserver::TestCallbackLayerAnimationObserver(
    AnimationStartedCallback animation_started_callback,
    AnimationEndedCallback animation_ended_callback,
    bool* destroyed)
    : CallbackLayerAnimationObserver(animation_started_callback,
                                     animation_ended_callback),
      destroyed_(destroyed) {
  if (destroyed_)
    (*destroyed_) = false;
}

TestCallbackLayerAnimationObserver::TestCallbackLayerAnimationObserver(
    AnimationStartedCallback animation_started_callback,
    bool should_delete_observer,
    bool* destroyed)
    : CallbackLayerAnimationObserver(animation_started_callback,
                                     should_delete_observer),
      destroyed_(destroyed) {
  if (destroyed_)
    (*destroyed_) = false;
}

TestCallbackLayerAnimationObserver::TestCallbackLayerAnimationObserver(
    AnimationEndedCallback animation_ended_callback,
    bool* destroyed)
    : CallbackLayerAnimationObserver(animation_ended_callback),
      destroyed_(destroyed) {
  if (destroyed_)
    (*destroyed_) = false;
}

TestCallbackLayerAnimationObserver::~TestCallbackLayerAnimationObserver() {
  if (destroyed_)
    (*destroyed_) = true;
}

class CallbackLayerAnimationObserverTest : public testing::Test {
 public:
  CallbackLayerAnimationObserverTest();
  ~CallbackLayerAnimationObserverTest() override;

 protected:
  // Creates a LayerAnimationSequence.  The lifetime of the sequence will be
  // managed by this.
  LayerAnimationSequence* CreateLayerAnimationSequence();

  std::unique_ptr<TestCallbacks> callbacks_;

  std::unique_ptr<CallbackLayerAnimationObserver> observer_;

  std::unique_ptr<LayerAnimationObserverTestApi> observer_test_api_;

  // List of managaged sequences created by CreateLayerAnimationSequence() that
  // need to be destroyed.
  std::vector<std::unique_ptr<LayerAnimationSequence>> sequences_;

 private:
  DISALLOW_COPY_AND_ASSIGN(CallbackLayerAnimationObserverTest);
};

CallbackLayerAnimationObserverTest::CallbackLayerAnimationObserverTest()
    : callbacks_(new TestCallbacks()),
      observer_(new CallbackLayerAnimationObserver(
          base::Bind(&TestCallbacks::AnimationsStarted,
                     base::Unretained(callbacks_.get())),
          base::Bind(&TestCallbacks::AnimationsEnded,
                     base::Unretained(callbacks_.get())))),
      observer_test_api_(new LayerAnimationObserverTestApi(observer_.get())) {}

CallbackLayerAnimationObserverTest::~CallbackLayerAnimationObserverTest() {
  observer_test_api_.reset();
  // The |observer_| will detach from all attached sequences upon destruction so
  // we need to explicitly delete the |observer_| before the |sequences_| and
  // |callbacks_|.
  observer_.reset();
}

LayerAnimationSequence*
CallbackLayerAnimationObserverTest::CreateLayerAnimationSequence() {
  sequences_.emplace_back(new LayerAnimationSequence);
  return sequences_.back().get();
}

TEST(CallbackLayerAnimationObserverDestructionTest, VerifyFalseAutoDelete) {
  TestCallbacks callbacks;
  callbacks.set_should_delete_observer_on_animations_ended(false);

  bool is_destroyed = false;

  TestCallbackLayerAnimationObserver* observer =
      new TestCallbackLayerAnimationObserver(
          base::Bind(&TestCallbacks::AnimationsStarted,
                     base::Unretained(&callbacks)),
          false, &is_destroyed);
  observer->SetActive();

  EXPECT_FALSE(is_destroyed);
  delete observer;
}

TEST(CallbackLayerAnimationObserverDestructionTest, VerifyTrueAutoDelete) {
  TestCallbacks callbacks;
  callbacks.set_should_delete_observer_on_animations_ended(false);

  bool is_destroyed = false;

  TestCallbackLayerAnimationObserver* observer =
      new TestCallbackLayerAnimationObserver(
          base::Bind(&TestCallbacks::AnimationsStarted,
                     base::Unretained(&callbacks)),
          true, &is_destroyed);
  observer->SetActive();

  EXPECT_TRUE(is_destroyed);
}

TEST(CallbackLayerAnimationObserverDestructionTest,
     AnimationEndedReturnsFalse) {
  TestCallbacks callbacks;
  callbacks.set_should_delete_observer_on_animations_ended(false);

  bool is_destroyed = false;

  TestCallbackLayerAnimationObserver* observer =
      new TestCallbackLayerAnimationObserver(
          base::Bind(&TestCallbacks::AnimationsStarted,
                     base::Unretained(&callbacks)),
          base::Bind(&TestCallbacks::AnimationsEnded,
                     base::Unretained(&callbacks)),
          &is_destroyed);
  observer->SetActive();

  EXPECT_FALSE(is_destroyed);
  delete observer;
}

TEST(CallbackLayerAnimationObserverDestructionTest, AnimationEndedReturnsTrue) {
  TestCallbacks callbacks;
  callbacks.set_should_delete_observer_on_animations_ended(true);

  bool is_destroyed = false;

  TestCallbackLayerAnimationObserver* observer =
      new TestCallbackLayerAnimationObserver(
          base::Bind(&TestCallbacks::AnimationsStarted,
                     base::Unretained(&callbacks)),
          base::Bind(&TestCallbacks::AnimationsEnded,
                     base::Unretained(&callbacks)),
          &is_destroyed);
  observer->SetActive();

  EXPECT_TRUE(is_destroyed);
}

TEST_F(CallbackLayerAnimationObserverTest, VerifyInitialState) {
  EXPECT_FALSE(observer_->active());
  EXPECT_EQ(0, observer_->aborted_count());
  EXPECT_EQ(0, observer_->successful_count());

  EXPECT_FALSE(callbacks_->animations_started());
  EXPECT_FALSE(callbacks_->animations_ended());
}

// Verifies that the CallbackLayerAnimationObserver is robust to explicit
// deletes caused as a side effect of calling the AnimationsStartedCallback()
// when there are no animation sequences attached. This test also guards against
// heap-use-after-free errors.
TEST_F(
    CallbackLayerAnimationObserverTest,
    ExplicitlyDeleteObserverInAnimationStartedCallbackWithNoSequencesAttached) {
  TestCallbacksThatExplicitlyDeletesObserver callbacks;
  callbacks.set_should_delete_observer_on_animations_ended(true);

  bool is_destroyed = false;

  TestCallbackLayerAnimationObserver* observer =
      new TestCallbackLayerAnimationObserver(
          base::Bind(&TestCallbacks::AnimationsStarted,
                     base::Unretained(&callbacks)),
          base::Bind(&TestCallbacks::AnimationsEnded,
                     base::Unretained(&callbacks)),
          &is_destroyed);

  callbacks.set_observer_to_delete_in_animation_started(observer);

  observer->SetActive();

  EXPECT_TRUE(is_destroyed);
}

// Verifies that the CallbackLayerAnimationObserver is robust to explicit
// deletes caused as a side effect of calling the AnimationsStartedCallback()
// when there are some animation sequences attached. This test also guards
// against heap-use-after-free errors.
TEST_F(
    CallbackLayerAnimationObserverTest,
    ExplicitlyDeleteObserverInAnimationStartedCallbackWithSomeSequencesAttached) {
  LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
  LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

  TestCallbacksThatExplicitlyDeletesObserver callbacks;
  callbacks.set_should_delete_observer_on_animations_ended(true);

  bool is_destroyed = false;

  TestCallbackLayerAnimationObserver* observer =
      new TestCallbackLayerAnimationObserver(
          base::Bind(&TestCallbacks::AnimationsStarted,
                     base::Unretained(&callbacks)),
          base::Bind(&TestCallbacks::AnimationsEnded,
                     base::Unretained(&callbacks)),
          &is_destroyed);

  observer_test_api_->AttachedToSequence(sequence_1);
  observer_test_api_->AttachedToSequence(sequence_2);
  observer_->OnLayerAnimationStarted(sequence_1);
  observer_->OnLayerAnimationStarted(sequence_2);

  callbacks.set_observer_to_delete_in_animation_started(observer);

  observer->SetActive();

  EXPECT_TRUE(is_destroyed);
}

// Verifies that a 'true' return value for AnimationEndedCallback is ignored if
// the CallbackLayerAnimationObserver is explicitly deleted as a side effect of
// calling the AnimationEndedCallback. This test also guards against
// heap-use-after-free errors.
TEST_F(CallbackLayerAnimationObserverTest,
       IgnoreTrueReturnValueForAnimationEndedCallbackIfExplicitlyDeleted) {
  TestCallbacksThatExplicitlyDeletesObserver callbacks;
  callbacks.set_should_delete_observer_on_animations_ended(true);

  bool is_destroyed = false;

  TestCallbackLayerAnimationObserver* observer =
      new TestCallbackLayerAnimationObserver(
          base::Bind(&TestCallbacks::AnimationsStarted,
                     base::Unretained(&callbacks)),
          base::Bind(&TestCallbacks::AnimationsEnded,
                     base::Unretained(&callbacks)),
          &is_destroyed);

  callbacks.set_observer_to_delete_in_animation_ended(observer);

  observer->SetActive();

  EXPECT_TRUE(is_destroyed);
}

TEST_F(CallbackLayerAnimationObserverTest,
       SetActiveWhenNoSequencesWereAttached) {
  observer_->SetActive();

  EXPECT_FALSE(observer_->active());
  EXPECT_TRUE(callbacks_->animations_started());
  EXPECT_TRUE(callbacks_->animations_ended());
  EXPECT_TRUE(callbacks_->StartedEpochIsBeforeEndedEpoch());
}

TEST_F(CallbackLayerAnimationObserverTest,
       SetActiveWhenAllSequencesAreAttachedButNoneWereStarted) {
  LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
  LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

  observer_test_api_->AttachedToSequence(sequence_1);
  observer_test_api_->AttachedToSequence(sequence_2);

  observer_->SetActive();

  EXPECT_TRUE(observer_->active());
  EXPECT_FALSE(callbacks_->animations_started());
  EXPECT_FALSE(callbacks_->animations_ended());
}

TEST_F(CallbackLayerAnimationObserverTest,
       SetActiveWhenAllSequencesAreAttachedAndOnlySomeWereStarted) {
  LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
  LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

  observer_test_api_->AttachedToSequence(sequence_1);
  observer_test_api_->AttachedToSequence(sequence_2);
  observer_->OnLayerAnimationStarted(sequence_1);

  observer_->SetActive();

  EXPECT_TRUE(observer_->active());
  EXPECT_FALSE(callbacks_->animations_started());
  EXPECT_FALSE(callbacks_->animations_ended());
}

TEST_F(CallbackLayerAnimationObserverTest,
       SetActiveWhenAllSequencesAreAttachedAndOnlySomeWereCompleted) {
  LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
  LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

  observer_test_api_->AttachedToSequence(sequence_1);
  observer_test_api_->AttachedToSequence(sequence_2);
  observer_->OnLayerAnimationStarted(sequence_1);
  observer_->OnLayerAnimationEnded(sequence_1);

  observer_->SetActive();

  EXPECT_TRUE(observer_->active());
  EXPECT_FALSE(callbacks_->animations_started());
  EXPECT_FALSE(callbacks_->animations_ended());
}

TEST_F(CallbackLayerAnimationObserverTest,
       SetActiveAfterAllSequencesWereStartedButNoneWereCompleted) {
  LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
  LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

  observer_test_api_->AttachedToSequence(sequence_1);
  observer_test_api_->AttachedToSequence(sequence_2);
  observer_->OnLayerAnimationStarted(sequence_1);
  observer_->OnLayerAnimationStarted(sequence_2);

  observer_->SetActive();

  EXPECT_TRUE(observer_->active());
  EXPECT_TRUE(callbacks_->animations_started());
  EXPECT_FALSE(callbacks_->animations_ended());
}

TEST_F(CallbackLayerAnimationObserverTest,
       SetActiveWhenAllSequencesAreStartedAndOnlySomeWereCompleted) {
  LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
  LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

  observer_test_api_->AttachedToSequence(sequence_1);
  observer_test_api_->AttachedToSequence(sequence_2);
  observer_->OnLayerAnimationStarted(sequence_1);
  observer_->OnLayerAnimationStarted(sequence_2);
  observer_->OnLayerAnimationEnded(sequence_1);

  observer_->SetActive();

  EXPECT_TRUE(observer_->active());
  EXPECT_TRUE(callbacks_->animations_started());
  EXPECT_FALSE(callbacks_->animations_ended());
}

TEST_F(CallbackLayerAnimationObserverTest,
       SetActiveWhenAllSequencesWereCompleted) {
  LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
  LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

  observer_test_api_->AttachedToSequence(sequence_1);
  observer_test_api_->AttachedToSequence(sequence_2);
  observer_->OnLayerAnimationStarted(sequence_1);
  observer_->OnLayerAnimationStarted(sequence_2);
  observer_->OnLayerAnimationEnded(sequence_1);
  observer_->OnLayerAnimationEnded(sequence_2);

  observer_->SetActive();

  EXPECT_FALSE(observer_->active());
  EXPECT_TRUE(callbacks_->animations_started());
  EXPECT_TRUE(callbacks_->animations_ended());
}

TEST_F(CallbackLayerAnimationObserverTest,
       SetActiveAgainAfterAllSequencesWereCompleted) {
  LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
  LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();
  LayerAnimationSequence* sequence_3 = CreateLayerAnimationSequence();
  LayerAnimationSequence* sequence_4 = CreateLayerAnimationSequence();

  observer_test_api_->AttachedToSequence(sequence_1);
  observer_test_api_->AttachedToSequence(sequence_2);
  observer_->OnLayerAnimationStarted(sequence_1);
  observer_->OnLayerAnimationStarted(sequence_2);
  observer_->OnLayerAnimationEnded(sequence_1);
  observer_->OnLayerAnimationEnded(sequence_2);

  observer_->SetActive();

  EXPECT_FALSE(observer_->active());

  observer_test_api_->AttachedToSequence(sequence_3);
  observer_test_api_->AttachedToSequence(sequence_4);

  callbacks_->ResetCallbackObservations();

  observer_->SetActive();

  EXPECT_TRUE(observer_->active());
  EXPECT_FALSE(callbacks_->animations_started());
  EXPECT_FALSE(callbacks_->animations_ended());
  EXPECT_EQ(2, observer_->successful_count());

  observer_->OnLayerAnimationStarted(sequence_3);
  observer_->OnLayerAnimationStarted(sequence_4);

  EXPECT_TRUE(observer_->active());
  EXPECT_TRUE(callbacks_->animations_started());
  EXPECT_FALSE(callbacks_->animations_ended());
  EXPECT_EQ(2, observer_->successful_count());

  observer_->OnLayerAnimationEnded(sequence_3);
  observer_->OnLayerAnimationEnded(sequence_4);

  EXPECT_FALSE(observer_->active());
  EXPECT_TRUE(callbacks_->animations_started());
  EXPECT_TRUE(callbacks_->animations_ended());
  EXPECT_EQ(4, observer_->successful_count());
}

}  // namespace test
}  // namespace ui
