// Copyright (c) 2012 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 "ash/wm/video_detector.h"

#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "base/time.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/aura/client/window_types.h"
#include "ui/aura/root_window.h"
#include "ui/aura/test/test_windows.h"
#include "ui/aura/window.h"
#include "ui/gfx/rect.h"

namespace ash {
namespace test {

// Implementation that just counts the number of times we've been told that a
// video is playing.
class TestVideoDetectorObserver : public VideoDetectorObserver {
 public:
  TestVideoDetectorObserver() : num_invocations_(0) {}

  int num_invocations() const { return num_invocations_; }
  void reset_stats() { num_invocations_ = 0; }

  // VideoDetectorObserver implementation.
  virtual void OnVideoDetected() OVERRIDE { num_invocations_++; }

 private:
  // Number of times that OnVideoDetected() has been called.
  int num_invocations_;

  DISALLOW_COPY_AND_ASSIGN(TestVideoDetectorObserver);
};

class VideoDetectorTest : public AshTestBase {
 public:
  VideoDetectorTest() {}
  virtual ~VideoDetectorTest() {}

  virtual void SetUp() OVERRIDE {
    AshTestBase::SetUp();
    observer_.reset(new TestVideoDetectorObserver);
    detector_ = Shell::GetInstance()->video_detector();
    detector_->AddObserver(observer_.get());

    now_ = base::TimeTicks::Now();
    detector_->set_now_for_test(now_);
  }

 protected:
  // Move |detector_|'s idea of the current time forward by |delta|.
  void AdvanceTime(base::TimeDelta delta) {
    now_ += delta;
    detector_->set_now_for_test(now_);
  }

  VideoDetector* detector_;  // not owned

  scoped_ptr<TestVideoDetectorObserver> observer_;

  base::TimeTicks now_;

 private:
  DISALLOW_COPY_AND_ASSIGN(VideoDetectorTest);
};

TEST_F(VideoDetectorTest, Basic) {
  gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
  scoped_ptr<aura::Window> window(
      aura::test::CreateTestWindow(SK_ColorRED, 12345, window_bounds, NULL));

  // Send enough updates, but make them be too small to trigger detection.
  gfx::Rect update_region(
      gfx::Point(),
      gfx::Size(VideoDetector::kMinUpdateWidth - 1,
                VideoDetector::kMinUpdateHeight));
  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
    detector_->OnWindowPaintScheduled(window.get(), update_region);
  EXPECT_EQ(0, observer_->num_invocations());

  // Send not-quite-enough adaquately-sized updates.
  observer_->reset_stats();
  AdvanceTime(base::TimeDelta::FromSeconds(2));
  update_region.set_size(
      gfx::Size(VideoDetector::kMinUpdateWidth,
                VideoDetector::kMinUpdateHeight));
  for (int i = 0; i < VideoDetector::kMinFramesPerSecond - 1; ++i)
    detector_->OnWindowPaintScheduled(window.get(), update_region);
  EXPECT_EQ(0, observer_->num_invocations());

  // We should get notified after the next update, but not in response to
  // additional updates.
  detector_->OnWindowPaintScheduled(window.get(), update_region);
  EXPECT_EQ(1, observer_->num_invocations());
  detector_->OnWindowPaintScheduled(window.get(), update_region);
  EXPECT_EQ(1, observer_->num_invocations());

  // Spread out the frames over two seconds; we shouldn't detect video.
  observer_->reset_stats();
  AdvanceTime(base::TimeDelta::FromSeconds(2));
  for (int i = 0; i < VideoDetector::kMinFramesPerSecond - 1; ++i)
    detector_->OnWindowPaintScheduled(window.get(), update_region);
  AdvanceTime(base::TimeDelta::FromSeconds(1));
  for (int i = 0; i < VideoDetector::kMinFramesPerSecond - 1; ++i)
    detector_->OnWindowPaintScheduled(window.get(), update_region);
  EXPECT_EQ(0, observer_->num_invocations());
}

TEST_F(VideoDetectorTest, WindowNotVisible) {
  gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
  scoped_ptr<aura::Window> window(
      aura::test::CreateTestWindow(SK_ColorRED, 12345, window_bounds, NULL));

  // Reparent the window to the root to make sure that visibility changes aren't
  // animated.
  Shell::GetRootWindow()->AddChild(window.get());

  // We shouldn't report video that's played in a hidden window.
  window->Hide();
  gfx::Rect update_region(
      gfx::Point(),
      gfx::Size(VideoDetector::kMinUpdateWidth,
                VideoDetector::kMinUpdateHeight));
  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
    detector_->OnWindowPaintScheduled(window.get(), update_region);
  EXPECT_EQ(0, observer_->num_invocations());

  // Make the window visible and send more updates.
  observer_->reset_stats();
  AdvanceTime(base::TimeDelta::FromSeconds(2));
  window->Show();
  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
    detector_->OnWindowPaintScheduled(window.get(), update_region);
  EXPECT_EQ(1, observer_->num_invocations());

  // We also shouldn't report video in a window that's fully offscreen.
  observer_->reset_stats();
  AdvanceTime(base::TimeDelta::FromSeconds(2));
  gfx::Rect offscreen_bounds(
      gfx::Point(Shell::GetRootWindow()->bounds().width(), 0),
      window_bounds.size());
  window->SetBounds(offscreen_bounds);
  ASSERT_EQ(offscreen_bounds, window->bounds());
  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
    detector_->OnWindowPaintScheduled(window.get(), update_region);
  EXPECT_EQ(0, observer_->num_invocations());
}

TEST_F(VideoDetectorTest, MultipleWindows) {
  // Create two windows.
  gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
  scoped_ptr<aura::Window> window1(
      aura::test::CreateTestWindow(SK_ColorRED, 12345, window_bounds, NULL));
  scoped_ptr<aura::Window> window2(
      aura::test::CreateTestWindow(SK_ColorBLUE, 23456, window_bounds, NULL));

  // Even if there's video playing in both, the observer should only receive a
  // single notification.
  gfx::Rect update_region(
      gfx::Point(),
      gfx::Size(VideoDetector::kMinUpdateWidth,
                VideoDetector::kMinUpdateHeight));
  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
    detector_->OnWindowPaintScheduled(window1.get(), update_region);
  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
    detector_->OnWindowPaintScheduled(window2.get(), update_region);
  EXPECT_EQ(1, observer_->num_invocations());
}

// Test that the observer receives repeated notifications.
TEST_F(VideoDetectorTest, RepeatedNotifications) {
  gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
  scoped_ptr<aura::Window> window(
      aura::test::CreateTestWindow(SK_ColorRED, 12345, window_bounds, NULL));

  gfx::Rect update_region(
      gfx::Point(),
      gfx::Size(VideoDetector::kMinUpdateWidth,
                VideoDetector::kMinUpdateHeight));
  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
    detector_->OnWindowPaintScheduled(window.get(), update_region);
  EXPECT_EQ(1, observer_->num_invocations());

  // Let enough time pass that a second notification should be sent.
  observer_->reset_stats();
  AdvanceTime(base::TimeDelta::FromSeconds(
      static_cast<int64>(VideoDetector::kNotifyIntervalSec + 1)));
  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
    detector_->OnWindowPaintScheduled(window.get(), update_region);
  EXPECT_EQ(1, observer_->num_invocations());
}

}  // namespace test
}  // namespace ash