// Copyright (c) 2011 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 <algorithm>
#include <vector>

#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_temp_dir.h"
#include "base/path_service.h"
#include "chrome/browser/history/history_database.h"
#include "chrome/browser/history/history_unittest_base.h"
#include "chrome/browser/history/thumbnail_database.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/thumbnail_score.h"
#include "chrome/test/testing_profile.h"
#include "chrome/tools/profiles/thumbnail-inl.h"
#include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/jpeg_codec.h"

using base::Time;
using base::TimeDelta;

namespace history {

namespace {

// data we'll put into the thumbnail database
static const unsigned char blob1[] =
    "12346102356120394751634516591348710478123649165419234519234512349134";
static const unsigned char blob2[] =
    "goiwuegrqrcomizqyzkjalitbahxfjytrqvpqeroicxmnlkhlzunacxaneviawrtxcywhgef";
static const unsigned char blob3[] =
    "3716871354098370776510470746794707624107647054607467847164027";
const double kBoringness = 0.25;
const double kWorseBoringness = 0.50;
const double kBetterBoringness = 0.10;
const double kTotallyBoring = 1.0;

const int64 kPage1 = 1234;

}  // namespace

class ThumbnailDatabaseTest : public testing::Test {
 public:
  ThumbnailDatabaseTest() {
  }
  ~ThumbnailDatabaseTest() {
  }

 protected:
  virtual void SetUp() {
    // Get a temporary directory for the test DB files.
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());

    file_name_ = temp_dir_.path().AppendASCII("TestThumbnails.db");
    new_file_name_ = temp_dir_.path().AppendASCII("TestFavicons.db");
    history_db_name_ = temp_dir_.path().AppendASCII("TestHistory.db");
    google_bitmap_.reset(
        gfx::JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail)));
  }

  scoped_ptr<SkBitmap> google_bitmap_;

  ScopedTempDir temp_dir_;
  FilePath file_name_;
  FilePath new_file_name_;
  FilePath history_db_name_;
};

class IconMappingMigrationTest : public HistoryUnitTestBase {
 public:
  IconMappingMigrationTest() {
  }
  ~IconMappingMigrationTest() {
  }

 protected:
  virtual void SetUp() {
    profile_.reset(new TestingProfile);

    FilePath data_path;
    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
    data_path = data_path.AppendASCII("History");

    history_db_name_ = profile_->GetPath().Append(chrome::kHistoryFilename);
    // Set up history and thumbnails as they would be before migration.
    ASSERT_NO_FATAL_FAILURE(
        ExecuteSQLScript(data_path.AppendASCII("history.20.sql"),
                         history_db_name_));
    thumbnail_db_name_ =
        profile_->GetPath().Append(chrome::kThumbnailsFilename);
    ASSERT_NO_FATAL_FAILURE(
        ExecuteSQLScript(data_path.AppendASCII("thumbnails.3.sql"),
                         thumbnail_db_name_));
  }

 protected:
  FilePath history_db_name_;
  FilePath thumbnail_db_name_;

 private:
  scoped_ptr<TestingProfile> profile_;
};

TEST_F(ThumbnailDatabaseTest, GetFaviconAfterMigrationToTopSites) {
  ThumbnailDatabase db;
  ASSERT_EQ(sql::INIT_OK, db.Init(file_name_, NULL, NULL));
  db.BeginTransaction();

  std::vector<unsigned char> data(blob1, blob1 + sizeof(blob1));
  scoped_refptr<RefCountedBytes> favicon(new RefCountedBytes(data));

  GURL url("http://google.com");
  FaviconID id = db.AddFavicon(url, FAVICON);
  base::Time time = base::Time::Now();
  db.SetFavicon(id, favicon, time);
  EXPECT_TRUE(db.RenameAndDropThumbnails(file_name_, new_file_name_));

  base::Time time_out;
  std::vector<unsigned char> favicon_out;
  GURL url_out;
  EXPECT_TRUE(db.GetFavicon(id, &time_out, &favicon_out, &url_out));
  EXPECT_EQ(url, url_out);
  EXPECT_EQ(time.ToTimeT(), time_out.ToTimeT());
  ASSERT_EQ(data.size(), favicon_out.size());
  EXPECT_TRUE(std::equal(data.begin(),
                         data.end(),
                         favicon_out.begin()));
}

TEST_F(ThumbnailDatabaseTest, AddIconMapping) {
  ThumbnailDatabase db;
  ASSERT_EQ(sql::INIT_OK, db.Init(file_name_, NULL, NULL));
  db.BeginTransaction();

  std::vector<unsigned char> data(blob1, blob1 + sizeof(blob1));
  scoped_refptr<RefCountedBytes> favicon(new RefCountedBytes(data));

  GURL url("http://google.com");
  FaviconID id = db.AddFavicon(url, TOUCH_ICON);
  EXPECT_NE(0, id);
  base::Time time = base::Time::Now();
  db.SetFavicon(id, favicon, time);

  EXPECT_NE(0, db.AddIconMapping(url, id));
  std::vector<IconMapping> icon_mapping;
  EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping));
  EXPECT_EQ(1u, icon_mapping.size());
  EXPECT_EQ(url, icon_mapping.front().page_url);
  EXPECT_EQ(id, icon_mapping.front().icon_id);
}

TEST_F(ThumbnailDatabaseTest, UpdateIconMapping) {
  ThumbnailDatabase db;
  ASSERT_EQ(sql::INIT_OK, db.Init(file_name_, NULL, NULL));
  db.BeginTransaction();

  std::vector<unsigned char> data(blob1, blob1 + sizeof(blob1));
  scoped_refptr<RefCountedBytes> favicon(new RefCountedBytes(data));

  GURL url("http://google.com");
  FaviconID id = db.AddFavicon(url, TOUCH_ICON);
  base::Time time = base::Time::Now();
  db.SetFavicon(id, favicon, time);

  EXPECT_TRUE(0 < db.AddIconMapping(url, id));
  std::vector<IconMapping> icon_mapping;
  EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping));
  ASSERT_EQ(1u, icon_mapping.size());
  EXPECT_EQ(url, icon_mapping.front().page_url);
  EXPECT_EQ(id, icon_mapping.front().icon_id);

  GURL url1("http://www.google.com/");
  FaviconID new_id = db.AddFavicon(url1, TOUCH_ICON);
  EXPECT_TRUE(db.UpdateIconMapping(icon_mapping.front().mapping_id, new_id));

  icon_mapping.clear();
  EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping));
  ASSERT_EQ(1u, icon_mapping.size());
  EXPECT_EQ(url, icon_mapping.front().page_url);
  EXPECT_EQ(new_id, icon_mapping.front().icon_id);
  EXPECT_NE(id, icon_mapping.front().icon_id);
}

TEST_F(ThumbnailDatabaseTest, DeleteIconMappings) {
  ThumbnailDatabase db;
  ASSERT_EQ(sql::INIT_OK, db.Init(file_name_, NULL, NULL));
  db.BeginTransaction();

  std::vector<unsigned char> data(blob1, blob1 + sizeof(blob1));
  scoped_refptr<RefCountedBytes> favicon(new RefCountedBytes(data));

  GURL url("http://google.com");
  FaviconID id = db.AddFavicon(url, TOUCH_ICON);
  base::Time time = base::Time::Now();
  db.SetFavicon(id, favicon, time);
  EXPECT_TRUE(0 < db.AddIconMapping(url, id));

  FaviconID id2 = db.AddFavicon(url, FAVICON);
  db.SetFavicon(id2, favicon, time);
  EXPECT_TRUE(0 < db.AddIconMapping(url, id2));
  ASSERT_NE(id, id2);

  std::vector<IconMapping> icon_mapping;
  EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping));
  ASSERT_EQ(2u, icon_mapping.size());
  EXPECT_EQ(icon_mapping.front().icon_type, TOUCH_ICON);
  EXPECT_TRUE(db.GetIconMappingForPageURL(url, FAVICON, NULL));

  db.DeleteIconMappings(url);

  EXPECT_FALSE(db.GetIconMappingsForPageURL(url, NULL));
  EXPECT_FALSE(db.GetIconMappingForPageURL(url, FAVICON, NULL));
}

TEST_F(ThumbnailDatabaseTest, GetIconMappingsForPageURL) {
  ThumbnailDatabase db;
  ASSERT_EQ(sql::INIT_OK, db.Init(file_name_, NULL, NULL));
  db.BeginTransaction();

  std::vector<unsigned char> data(blob1, blob1 + sizeof(blob1));
  scoped_refptr<RefCountedBytes> favicon(new RefCountedBytes(data));

  GURL url("http://google.com");

  FaviconID id1 = db.AddFavicon(url, TOUCH_ICON);
  base::Time time = base::Time::Now();
  db.SetFavicon(id1, favicon, time);
  EXPECT_TRUE(0 < db.AddIconMapping(url, id1));

  FaviconID id2 = db.AddFavicon(url, FAVICON);
  EXPECT_NE(id1, id2);
  db.SetFavicon(id2, favicon, time);
  EXPECT_TRUE(0 < db.AddIconMapping(url, id2));

  std::vector<IconMapping> icon_mapping;
  EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping));
  ASSERT_EQ(2u, icon_mapping.size());
  EXPECT_NE(icon_mapping[0].icon_id, icon_mapping[1].icon_id);
  EXPECT_TRUE(icon_mapping[0].icon_id == id1 && icon_mapping[1].icon_id == id2);
}

TEST_F(ThumbnailDatabaseTest, UpgradeToVersion4) {
  ThumbnailDatabase db;
  ASSERT_EQ(sql::INIT_OK, db.Init(file_name_, NULL, NULL));
  db.BeginTransaction();

  const char* name = "favicons";
  std::string sql;
  sql.append("DROP TABLE IF EXISTS ");
  sql.append(name);
  EXPECT_TRUE(db.db_.Execute(sql.c_str()));

  sql.resize(0);
  sql.append("CREATE TABLE ");
  sql.append(name);
  sql.append("("
             "id INTEGER PRIMARY KEY,"
             "url LONGVARCHAR NOT NULL,"
             "last_updated INTEGER DEFAULT 0,"
             "image_data BLOB)");
  EXPECT_TRUE(db.db_.Execute(sql.c_str()));

  EXPECT_TRUE(db.UpgradeToVersion4());

  std::vector<unsigned char> data(blob1, blob1 + sizeof(blob1));
  scoped_refptr<RefCountedBytes> favicon(new RefCountedBytes(data));

  GURL url("http://google.com");
  FaviconID id = db.AddFavicon(url, TOUCH_ICON);
  base::Time time = base::Time::Now();
  db.SetFavicon(id, favicon, time);

  EXPECT_TRUE(0 < db.AddIconMapping(url, id));
  IconMapping icon_mapping;
  EXPECT_TRUE(db.GetIconMappingForPageURL(url, TOUCH_ICON, &icon_mapping));
  EXPECT_EQ(url, icon_mapping.page_url);
  EXPECT_EQ(id, icon_mapping.icon_id);
}

TEST_F(ThumbnailDatabaseTest, TemporayIconMapping) {
  ThumbnailDatabase db;

  ASSERT_EQ(sql::INIT_OK, db.Init(file_name_, NULL, NULL));

  db.BeginTransaction();

  EXPECT_TRUE(db.InitTemporaryIconMappingTable());

  std::vector<unsigned char> data(blob1, blob1 + sizeof(blob1));
  scoped_refptr<RefCountedBytes> favicon(new RefCountedBytes(data));

  GURL url("http://google.com");
  FaviconID id = db.AddFavicon(url, FAVICON);
  base::Time time = base::Time::Now();
  db.SetFavicon(id, favicon, time);

  db.AddToTemporaryIconMappingTable(url, id);
  db.CommitTemporaryIconMappingTable();
  IconMapping icon_mapping;
  EXPECT_TRUE(db.GetIconMappingForPageURL(url, FAVICON, &icon_mapping));
  EXPECT_EQ(id, icon_mapping.icon_id);
  EXPECT_EQ(url, icon_mapping.page_url);
}

TEST_F(ThumbnailDatabaseTest, GetIconMappingsForPageURLForReturnOrder) {
  ThumbnailDatabase db;
  ASSERT_EQ(sql::INIT_OK, db.Init(file_name_, NULL, NULL));
  db.BeginTransaction();

  // Add a favicon
  std::vector<unsigned char> data(blob1, blob1 + sizeof(blob1));
  scoped_refptr<RefCountedBytes> favicon(new RefCountedBytes(data));

  GURL url("http://google.com");
  FaviconID id = db.AddFavicon(url, FAVICON);
  base::Time time = base::Time::Now();
  db.SetFavicon(id, favicon, time);

  EXPECT_NE(0, db.AddIconMapping(url, id));
  std::vector<IconMapping> icon_mapping;
  EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping));

  EXPECT_EQ(url, icon_mapping.front().page_url);
  EXPECT_EQ(id, icon_mapping.front().icon_id);
  EXPECT_EQ(FAVICON, icon_mapping.front().icon_type);

  // Add a touch icon
  std::vector<unsigned char> data2(blob2, blob2 + sizeof(blob2));
  scoped_refptr<RefCountedBytes> favicon2(new RefCountedBytes(data));

  FaviconID id2 = db.AddFavicon(url, TOUCH_ICON);
  db.SetFavicon(id2, favicon2, time);
  EXPECT_NE(0, db.AddIconMapping(url, id2));

  icon_mapping.clear();
  EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping));

  EXPECT_EQ(url, icon_mapping.front().page_url);
  EXPECT_EQ(id2, icon_mapping.front().icon_id);
  EXPECT_EQ(TOUCH_ICON, icon_mapping.front().icon_type);

  // Add a touch precomposed icon
  scoped_refptr<RefCountedBytes> favicon3(new RefCountedBytes(data2));

  FaviconID id3 = db.AddFavicon(url, TOUCH_PRECOMPOSED_ICON);
  db.SetFavicon(id3, favicon3, time);
  EXPECT_NE(0, db.AddIconMapping(url, id3));

  icon_mapping.clear();
  EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping));

  EXPECT_EQ(url, icon_mapping.front().page_url);
  EXPECT_EQ(id3, icon_mapping.front().icon_id);
  EXPECT_EQ(TOUCH_PRECOMPOSED_ICON, icon_mapping.front().icon_type);
}

TEST_F(ThumbnailDatabaseTest, HasMappingFor) {
  ThumbnailDatabase db;
  ASSERT_EQ(sql::INIT_OK, db.Init(file_name_, NULL, NULL));
  db.BeginTransaction();

  std::vector<unsigned char> data(blob1, blob1 + sizeof(blob1));
  scoped_refptr<RefCountedBytes> favicon(new RefCountedBytes(data));

  // Add a favicon which will have icon_mappings
  FaviconID id1 = db.AddFavicon(GURL("http://google.com"), FAVICON);
  EXPECT_NE(id1, 0);
  base::Time time = base::Time::Now();
  db.SetFavicon(id1, favicon, time);

  // Add another type of favicon
  FaviconID id2 = db.AddFavicon(GURL("http://www.google.com/icon"), TOUCH_ICON);
  EXPECT_NE(id2, 0);
  time = base::Time::Now();
  db.SetFavicon(id2, favicon, time);

  // Add 3rd favicon
  FaviconID id3 = db.AddFavicon(GURL("http://www.google.com/icon"), TOUCH_ICON);
  EXPECT_NE(id3, 0);
  time = base::Time::Now();
  db.SetFavicon(id3, favicon, time);

  // Add 2 icon mapping
  GURL page_url("http://www.google.com");
  EXPECT_TRUE(db.AddIconMapping(page_url, id1));
  EXPECT_TRUE(db.AddIconMapping(page_url, id2));

  EXPECT_TRUE(db.HasMappingFor(id1));
  EXPECT_TRUE(db.HasMappingFor(id2));
  EXPECT_FALSE(db.HasMappingFor(id3));

  // Remove all mappings
  db.DeleteIconMappings(page_url);
  EXPECT_FALSE(db.HasMappingFor(id1));
  EXPECT_FALSE(db.HasMappingFor(id2));
  EXPECT_FALSE(db.HasMappingFor(id3));
}

TEST_F(IconMappingMigrationTest, TestIconMappingMigration) {
  HistoryDatabase history_db;
  ASSERT_TRUE(history_db.db_.Open(history_db_name_));
  history_db.BeginTransaction();

  const GURL icon1 = GURL("http://www.google.com/favicon.ico");
  const GURL icon2 = GURL("http://www.yahoo.com/favicon.ico");

  ThumbnailDatabase db;
  ASSERT_EQ(sql::INIT_OK, db.Init(thumbnail_db_name_, NULL, &history_db));
  db.BeginTransaction();

  // Migration should be done.
  // Test one icon_mapping.
  GURL page_url1 = GURL("http://google.com/");
  std::vector<IconMapping> icon_mappings;
  EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url1, &icon_mappings));
  ASSERT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(FAVICON, icon_mappings[0].icon_type);
  EXPECT_EQ(page_url1, icon_mappings[0].page_url);
  EXPECT_EQ(1, icon_mappings[0].icon_id);
  base::Time time;
  std::vector<unsigned char> out_data;
  GURL out_icon_url;
  ASSERT_TRUE(db.GetFavicon(
      icon_mappings[0].icon_id, &time, &out_data, &out_icon_url));
  EXPECT_EQ(icon1, out_icon_url);

  // Test a page which has the same icon.
  GURL page_url3 = GURL("http://www.google.com/");
  icon_mappings.clear();
  EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url3, &icon_mappings));
  ASSERT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(FAVICON, icon_mappings[0].icon_type);
  EXPECT_EQ(page_url3, icon_mappings[0].page_url);
  EXPECT_EQ(1, icon_mappings[0].icon_id);

  // Test a icon_mapping with different IconID.
  GURL page_url2 = GURL("http://yahoo.com/");
  icon_mappings.clear();
  EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url2, &icon_mappings));
  ASSERT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(FAVICON, icon_mappings[0].icon_type);
  EXPECT_EQ(page_url2, icon_mappings[0].page_url);
  EXPECT_EQ(2, icon_mappings[0].icon_id);
  ASSERT_TRUE(db.GetFavicon(
      icon_mappings[0].icon_id, &time, &out_data, &out_icon_url));
  EXPECT_EQ(icon2, out_icon_url);

  // Test a page without icon
  GURL page_url4 = GURL("http://www.google.com/blank.html");
  EXPECT_FALSE(db.GetIconMappingsForPageURL(page_url4, NULL));
}

}  // namespace history