// 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 "base/auto_reset.h"
#include "base/command_line.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/mock_plugin_exceptions_table_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/testing_pref_service.h"
#include "chrome/test/testing_profile.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/models/table_model_observer.h"
#include "webkit/plugins/npapi/plugin_group.h"
#include "webkit/plugins/npapi/webplugininfo.h"

// Can't be an internal namespace because PluginExceptionsTableModel declares
// as a friend.
namespace plugin_test_internal {

using ::testing::_;
using ::testing::Invoke;

class MockTableModelObserver : public ui::TableModelObserver {
 public:
  explicit MockTableModelObserver(ui::TableModel* model)
      : model_(model) {
    ON_CALL(*this, OnItemsRemoved(_, _))
        .WillByDefault(
            Invoke(this, &MockTableModelObserver::CheckOnItemsRemoved));
  }

  MOCK_METHOD0(OnModelChanged, void());
  MOCK_METHOD2(OnItemsChanged, void(int start, int length));
  MOCK_METHOD2(OnItemsAdded, void(int start, int length));
  MOCK_METHOD2(OnItemsRemoved, void(int start, int length));

 private:
  void CheckOnItemsRemoved(int start, int length) {
    if (!model_)
      return;
    // This method is called *after* the items have been removed, so we check if
    // the first removed item was still inside the correct range.
    EXPECT_LT(start, model_->RowCount() + 1);
  }

  ui::TableModel* model_;
};

}  // namespace plugin_test_internal

using ::testing::InSequence;

class PluginExceptionsTableModelTest : public testing::Test {
 public:
  PluginExceptionsTableModelTest()
      : ui_thread_(BrowserThread::UI, &message_loop_),
        command_line_(CommandLine::ForCurrentProcess(),
                      *CommandLine::ForCurrentProcess()) {}

  virtual void SetUp() {
    CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kEnableResourceContentSettings);

    profile_.reset(new TestingProfile());

    HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();

    ContentSettingsPattern example_com("[*.]example.com");
    ContentSettingsPattern moose_org("[*.]moose.org");
    map->SetContentSetting(example_com,
                           CONTENT_SETTINGS_TYPE_PLUGINS,
                           "a-foo",
                           CONTENT_SETTING_ALLOW);
    map->SetContentSetting(moose_org,
                           CONTENT_SETTINGS_TYPE_PLUGINS,
                           "b-bar",
                           CONTENT_SETTING_BLOCK);
    map->SetContentSetting(example_com,
                           CONTENT_SETTINGS_TYPE_PLUGINS,
                           "b-bar",
                           CONTENT_SETTING_ALLOW);

    table_model_.reset(new MockPluginExceptionsTableModel(map, NULL));

    std::vector<webkit::npapi::PluginGroup> plugins;
    webkit::npapi::WebPluginInfo foo_plugin;
    foo_plugin.path = FilePath(FILE_PATH_LITERAL("a-foo"));
    foo_plugin.name = ASCIIToUTF16("FooPlugin");
    foo_plugin.enabled =
        webkit::npapi::WebPluginInfo::USER_ENABLED_POLICY_UNMANAGED;
    scoped_ptr<webkit::npapi::PluginGroup> foo_group(
        webkit::npapi::PluginGroup::FromWebPluginInfo(foo_plugin));
    plugins.push_back(*foo_group);

    webkit::npapi::WebPluginInfo bar_plugin;
    bar_plugin.path = FilePath(FILE_PATH_LITERAL("b-bar"));
    bar_plugin.name = ASCIIToUTF16("BarPlugin");
    bar_plugin.enabled =
        webkit::npapi::WebPluginInfo::USER_ENABLED_POLICY_UNMANAGED;
    scoped_ptr<webkit::npapi::PluginGroup> bar_group(
        webkit::npapi::PluginGroup::FromWebPluginInfo(bar_plugin));
    plugins.push_back(*bar_group);

    table_model_->set_plugins(plugins);
    table_model_->ReloadSettings();
  }

 protected:
  void CheckInvariants() const {
    typedef std::vector<PluginExceptionsTableModel::SettingsEntry> Entries;
    const Entries& settings = table_model_->settings_;
    const std::vector<int>& row_counts = table_model_->row_counts_;
    const std::vector<std::string>& resources = table_model_->resources_;
    const ui::TableModel::Groups& groups = table_model_->groups_;

    EXPECT_EQ(groups.size(), row_counts.size());
    EXPECT_EQ(groups.size(), resources.size());

    int last_plugin = 0;
    int count = 0;
    for (Entries::const_iterator it = settings.begin();
         it != settings.end(); ++it) {
      // Plugin IDs are indices into |groups|.
      EXPECT_GE(it->plugin_id, 0);
      EXPECT_LT(it->plugin_id, static_cast<int>(groups.size()));
      if (it->plugin_id == last_plugin) {
        count++;
      } else {
        // Plugin IDs are ascending one by one.
        EXPECT_EQ(last_plugin+1, it->plugin_id);

        // Consecutive runs of plugins with id |x| are |row_counts[x]| long.
        EXPECT_EQ(count, row_counts[last_plugin]);
        count = 1;
        last_plugin = it->plugin_id;
      }
    }
    if (!row_counts.empty())
      EXPECT_EQ(count, row_counts[last_plugin]);
  }

 protected:
  MessageLoop message_loop_;
  BrowserThread ui_thread_;

  scoped_ptr<TestingProfile> profile_;
  scoped_ptr<MockPluginExceptionsTableModel> table_model_;

 private:
  AutoReset<CommandLine> command_line_;
};

TEST_F(PluginExceptionsTableModelTest, Basic) {
  EXPECT_EQ(3, table_model_->RowCount());
  CheckInvariants();
}

TEST_F(PluginExceptionsTableModelTest, RemoveOneRow) {
  plugin_test_internal::MockTableModelObserver observer(table_model_.get());
  table_model_->SetObserver(&observer);

  EXPECT_CALL(observer, OnItemsRemoved(1, 1));
  RemoveRowsTableModel::Rows rows;
  rows.insert(1);
  table_model_->RemoveRows(rows);
  EXPECT_EQ(2, table_model_->RowCount());
  EXPECT_EQ(2, static_cast<int>(table_model_->GetGroups().size()));
  CheckInvariants();
  table_model_->SetObserver(NULL);
}

TEST_F(PluginExceptionsTableModelTest, RemoveLastRowInGroup) {
  plugin_test_internal::MockTableModelObserver observer(table_model_.get());
  table_model_->SetObserver(&observer);

  EXPECT_CALL(observer, OnModelChanged());
  RemoveRowsTableModel::Rows rows;
  rows.insert(0);
  table_model_->RemoveRows(rows);
  EXPECT_EQ(2, table_model_->RowCount());
  EXPECT_EQ(1, static_cast<int>(table_model_->GetGroups().size()));
  CheckInvariants();

  HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
  EXPECT_CALL(observer, OnModelChanged());
  map->SetContentSetting(ContentSettingsPattern("[*.]blurp.net"),
                         CONTENT_SETTINGS_TYPE_PLUGINS,
                         "b-bar",
                         CONTENT_SETTING_BLOCK);
  EXPECT_EQ(3, table_model_->RowCount());

  InSequence s;
  EXPECT_CALL(observer, OnItemsRemoved(2, 1));
  EXPECT_CALL(observer, OnItemsRemoved(0, 1));
  rows.clear();
  rows.insert(0);
  rows.insert(2);
  table_model_->RemoveRows(rows);
  EXPECT_EQ(1, table_model_->RowCount());
  EXPECT_EQ(1, static_cast<int>(table_model_->GetGroups().size()));
  CheckInvariants();

  table_model_->SetObserver(NULL);
}

TEST_F(PluginExceptionsTableModelTest, RemoveAllRows) {
  plugin_test_internal::MockTableModelObserver observer(table_model_.get());
  table_model_->SetObserver(&observer);

  EXPECT_CALL(observer, OnModelChanged());
  table_model_->RemoveAll();
  EXPECT_EQ(0, table_model_->RowCount());
  EXPECT_EQ(0, static_cast<int>(table_model_->GetGroups().size()));
  CheckInvariants();
  table_model_->SetObserver(NULL);
}