// Copyright (c) 2006-2008 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 <string>

#include "base/message_loop.h"
#include "chrome/browser/renderer_host/web_cache_manager.h"
#include "content/browser/browser_thread.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::Time;
using base::TimeDelta;
using WebKit::WebCache;

class WebCacheManagerTest : public testing::Test {
 protected:
  typedef WebCacheManager::StatsMap StatsMap;
  typedef WebCacheManager::Allocation Allocation;
  typedef WebCacheManager::AllocationStrategy AllocationStrategy;

  static const int kRendererID;
  static const int kRendererID2;
  static const WebCache::UsageStats kStats;
  static const WebCache::UsageStats kStats2;

  WebCacheManagerTest()
      : ui_thread_(BrowserThread::UI, &message_loop_) {
  }

  // Thunks to access protected members of WebCacheManager
  static std::map<int, WebCacheManager::RendererInfo>& stats(
        WebCacheManager* h) {
    return h->stats_;
  }

  static void SimulateInactivity(WebCacheManager* h, int renderer_id) {
    stats(h)[renderer_id].access = Time::Now() - TimeDelta::FromMinutes(
        WebCacheManager::kRendererInactiveThresholdMinutes);
    h->FindInactiveRenderers();
  }

  static std::set<int>& active_renderers(WebCacheManager* h) {
    return h->active_renderers_;
  }
  static std::set<int>& inactive_renderers(WebCacheManager* h) {
    return h->inactive_renderers_;
  }
  static void GatherStats(WebCacheManager* h,
                          std::set<int> renderers,
                          WebCache::UsageStats* stats) {
    h->GatherStats(renderers, stats);
  }
  static size_t GetSize(int tactic,
                        const WebCache::UsageStats& stats) {
    return WebCacheManager::GetSize(
        static_cast<WebCacheManager::AllocationTactic>(tactic), stats);
  }
  static bool AttemptTactic(WebCacheManager* h,
                            int active_tactic,
                            const WebCache::UsageStats& active_stats,
                            int inactive_tactic,
                            const WebCache::UsageStats& inactive_stats,
                            std::list< std::pair<int,size_t> >* strategy) {
    return h->AttemptTactic(
        static_cast<WebCacheManager::AllocationTactic>(active_tactic),
        active_stats,
        static_cast<WebCacheManager::AllocationTactic>(inactive_tactic),
        inactive_stats,
        strategy);
  }
  static void AddToStrategy(WebCacheManager* h,
                            std::set<int> renderers,
                            int tactic,
                            size_t extra_bytes_to_allocate,
                            std::list< std::pair<int,size_t> >* strategy) {
    h->AddToStrategy(renderers,
                     static_cast<WebCacheManager::AllocationTactic>(tactic),
                     extra_bytes_to_allocate,
                     strategy);
  }

  enum {
    DIVIDE_EVENLY = WebCacheManager::DIVIDE_EVENLY,
    KEEP_CURRENT_WITH_HEADROOM = WebCacheManager::KEEP_CURRENT_WITH_HEADROOM,
    KEEP_CURRENT = WebCacheManager::KEEP_CURRENT,
    KEEP_LIVE_WITH_HEADROOM = WebCacheManager::KEEP_LIVE_WITH_HEADROOM,
    KEEP_LIVE = WebCacheManager::KEEP_LIVE,
  };

  WebCacheManager* manager() { return &manager_; }

 private:
  WebCacheManager manager_;
  MessageLoop message_loop_;
  BrowserThread ui_thread_;
};

// static
const int WebCacheManagerTest::kRendererID = 146;

// static
const int WebCacheManagerTest::kRendererID2 = 245;

// static
const WebCache::UsageStats WebCacheManagerTest::kStats = {
    0,
    1024 * 1024,
    1024 * 1024,
    256 * 1024,
    512,
  };

// static
const WebCache::UsageStats WebCacheManagerTest::kStats2 = {
    0,
    2 * 1024 * 1024,
    2 * 1024 * 1024,
    2 * 256 * 1024,
    2 * 512,
  };

static bool operator==(const WebCache::UsageStats& lhs,
                       const WebCache::UsageStats& rhs) {
  return !::memcmp(&lhs, &rhs, sizeof(WebCache::UsageStats));
}

TEST_F(WebCacheManagerTest, AddRemoveRendererTest) {
  EXPECT_EQ(0U, active_renderers(manager()).size());
  EXPECT_EQ(0U, inactive_renderers(manager()).size());

  manager()->Add(kRendererID);
  EXPECT_EQ(1U, active_renderers(manager()).count(kRendererID));
  EXPECT_EQ(0U, inactive_renderers(manager()).count(kRendererID));

  manager()->Remove(kRendererID);
  EXPECT_EQ(0U, active_renderers(manager()).size());
  EXPECT_EQ(0U, inactive_renderers(manager()).size());
}

TEST_F(WebCacheManagerTest, ActiveInactiveTest) {
  manager()->Add(kRendererID);

  manager()->ObserveActivity(kRendererID);
  EXPECT_EQ(1U, active_renderers(manager()).count(kRendererID));
  EXPECT_EQ(0U, inactive_renderers(manager()).count(kRendererID));

  SimulateInactivity(manager(), kRendererID);
  EXPECT_EQ(0U, active_renderers(manager()).count(kRendererID));
  EXPECT_EQ(1U, inactive_renderers(manager()).count(kRendererID));

  manager()->ObserveActivity(kRendererID);
  EXPECT_EQ(1U, active_renderers(manager()).count(kRendererID));
  EXPECT_EQ(0U, inactive_renderers(manager()).count(kRendererID));

  manager()->Remove(kRendererID);
}

TEST_F(WebCacheManagerTest, ObserveStatsTest) {
  manager()->Add(kRendererID);

  EXPECT_EQ(1U, stats(manager()).size());

  manager()->ObserveStats(kRendererID, kStats);

  EXPECT_EQ(1U, stats(manager()).size());
  EXPECT_TRUE(kStats == stats(manager())[kRendererID]);

  manager()->Remove(kRendererID);
}

TEST_F(WebCacheManagerTest, SetGlobalSizeLimitTest) {
  size_t limit = manager()->GetDefaultGlobalSizeLimit();
  manager()->SetGlobalSizeLimit(limit);
  EXPECT_EQ(limit, manager()->global_size_limit());

  manager()->SetGlobalSizeLimit(0);
  EXPECT_EQ(0U, manager()->global_size_limit());
}

TEST_F(WebCacheManagerTest, GatherStatsTest) {
  manager()->Add(kRendererID);
  manager()->Add(kRendererID2);

  manager()->ObserveStats(kRendererID, kStats);
  manager()->ObserveStats(kRendererID2, kStats2);

  std::set<int> renderer_set;
  renderer_set.insert(kRendererID);

  WebCache::UsageStats stats;
  GatherStats(manager(), renderer_set, &stats);

  EXPECT_TRUE(kStats == stats);

  renderer_set.insert(kRendererID2);
  GatherStats(manager(), renderer_set, &stats);

  WebCache::UsageStats expected_stats = kStats;
  expected_stats.minDeadCapacity += kStats2.minDeadCapacity;
  expected_stats.maxDeadCapacity += kStats2.maxDeadCapacity;
  expected_stats.capacity += kStats2.capacity;
  expected_stats.liveSize += kStats2.liveSize;
  expected_stats.deadSize += kStats2.deadSize;

  EXPECT_TRUE(expected_stats == stats);

  manager()->Remove(kRendererID);
  manager()->Remove(kRendererID2);
}

TEST_F(WebCacheManagerTest, GetSizeTest) {
  EXPECT_EQ(0U, GetSize(DIVIDE_EVENLY, kStats));
  EXPECT_LT(256 * 1024u + 512, GetSize(KEEP_CURRENT_WITH_HEADROOM, kStats));
  EXPECT_EQ(256 * 1024u + 512, GetSize(KEEP_CURRENT, kStats));
  EXPECT_LT(256 * 1024u, GetSize(KEEP_LIVE_WITH_HEADROOM, kStats));
  EXPECT_EQ(256 * 1024u, GetSize(KEEP_LIVE, kStats));
}

TEST_F(WebCacheManagerTest, AttemptTacticTest) {
  manager()->Add(kRendererID);
  manager()->Add(kRendererID2);

  manager()->ObserveActivity(kRendererID);
  SimulateInactivity(manager(), kRendererID2);

  manager()->ObserveStats(kRendererID, kStats);
  manager()->ObserveStats(kRendererID2, kStats2);

  manager()->SetGlobalSizeLimit(kStats.liveSize + kStats.deadSize +
                        kStats2.liveSize + kStats2.deadSize/2);

  AllocationStrategy strategy;

  EXPECT_FALSE(AttemptTactic(manager(),
                             KEEP_CURRENT,
                             kStats,
                             KEEP_CURRENT,
                             kStats2,
                             &strategy));
  EXPECT_TRUE(strategy.empty());

  EXPECT_TRUE(AttemptTactic(manager(),
                            KEEP_CURRENT,
                            kStats,
                            KEEP_LIVE,
                            kStats2,
                            &strategy));
  EXPECT_EQ(2U, strategy.size());

  AllocationStrategy::iterator iter = strategy.begin();
  while (iter != strategy.end()) {
    if (iter->first == kRendererID)
      EXPECT_LE(kStats.liveSize + kStats.deadSize, iter->second);
    else if (iter->first == kRendererID2)
      EXPECT_LE(kStats2.liveSize, iter->second);
    else
      EXPECT_FALSE("Unexpected entry in strategy");
    ++iter;
  }

  manager()->Remove(kRendererID);
  manager()->Remove(kRendererID2);
}

TEST_F(WebCacheManagerTest, AddToStrategyTest) {
  manager()->Add(kRendererID);
  manager()->Add(kRendererID2);

  std::set<int> renderer_set;
  renderer_set.insert(kRendererID);
  renderer_set.insert(kRendererID2);

  manager()->ObserveStats(kRendererID, kStats);
  manager()->ObserveStats(kRendererID2, kStats2);

  const size_t kExtraBytesToAllocate = 10 * 1024;

  AllocationStrategy strategy;
  AddToStrategy(manager(),
                renderer_set,
                KEEP_CURRENT,
                kExtraBytesToAllocate,
                &strategy);

  EXPECT_EQ(2U, strategy.size());

  size_t total_bytes = 0;
  AllocationStrategy::iterator iter = strategy.begin();
  while (iter != strategy.end()) {
    total_bytes += iter->second;

    if (iter->first == kRendererID)
      EXPECT_LE(kStats.liveSize + kStats.deadSize, iter->second);
    else if (iter->first == kRendererID2)
      EXPECT_LE(kStats2.liveSize + kStats2.deadSize, iter->second);
    else
      EXPECT_FALSE("Unexpected entry in strategy");
    ++iter;
  }

  size_t expected_total_bytes = kExtraBytesToAllocate +
                                kStats.liveSize + kStats.deadSize +
                                kStats2.liveSize + kStats2.deadSize;

  EXPECT_GE(expected_total_bytes, total_bytes);

  manager()->Remove(kRendererID);
  manager()->Remove(kRendererID2);
}