// 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 "chrome/browser/prefs/pref_member.h"

#include "base/message_loop.h"
#include "chrome/browser/prefs/pref_value_store.h"
#include "chrome/test/testing_pref_service.h"
#include "content/browser/browser_thread.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"
#include "content/common/notification_type.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

const char kBoolPref[] = "bool";
const char kIntPref[] = "int";
const char kDoublePref[] = "double";
const char kStringPref[] = "string";
const char kListPref[] = "list";

void RegisterTestPrefs(PrefService* prefs) {
  prefs->RegisterBooleanPref(kBoolPref, false);
  prefs->RegisterIntegerPref(kIntPref, 0);
  prefs->RegisterDoublePref(kDoublePref, 0.0);
  prefs->RegisterStringPref(kStringPref, "default");
  prefs->RegisterListPref(kListPref);
}

class GetPrefValueCallback
    : public base::RefCountedThreadSafe<GetPrefValueCallback> {
 public:
  GetPrefValueCallback() : value_(false) {}

  void Init(const char* pref_name, PrefService* prefs) {
    pref_.Init(pref_name, prefs, NULL);
    pref_.MoveToThread(BrowserThread::IO);
  }

  bool FetchValue() {
    if (!BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        NewRunnableMethod(this,
                          &GetPrefValueCallback::GetPrefValueOnIOThread))) {
      return false;
    }
    MessageLoop::current()->Run();
    return true;
  }

  bool value() { return value_; }

 private:
  friend class base::RefCountedThreadSafe<GetPrefValueCallback>;
  ~GetPrefValueCallback() {}

  void GetPrefValueOnIOThread() {
    value_ = pref_.GetValue();
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            new MessageLoop::QuitTask());
  }

  BooleanPrefMember pref_;
  bool value_;
};

class PrefMemberTestClass : public NotificationObserver {
 public:
  explicit PrefMemberTestClass(PrefService* prefs)
      : observe_cnt_(0), prefs_(prefs) {
    str_.Init(kStringPref, prefs, this);
  }

  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details) {
    DCHECK(NotificationType::PREF_CHANGED == type);
    PrefService* prefs_in = Source<PrefService>(source).ptr();
    EXPECT_EQ(prefs_in, prefs_);
    std::string* pref_name_in = Details<std::string>(details).ptr();
    EXPECT_EQ(*pref_name_in, kStringPref);
    EXPECT_EQ(str_.GetValue(), prefs_->GetString(kStringPref));
    ++observe_cnt_;
  }

  StringPrefMember str_;
  int observe_cnt_;

 private:
  PrefService* prefs_;
};

}  // anonymous namespace

TEST(PrefMemberTest, BasicGetAndSet) {
  TestingPrefService prefs;
  RegisterTestPrefs(&prefs);

  // Test bool
  BooleanPrefMember boolean;
  boolean.Init(kBoolPref, &prefs, NULL);

  // Check the defaults
  EXPECT_FALSE(prefs.GetBoolean(kBoolPref));
  EXPECT_FALSE(boolean.GetValue());
  EXPECT_FALSE(*boolean);

  // Try changing through the member variable.
  boolean.SetValue(true);
  EXPECT_TRUE(boolean.GetValue());
  EXPECT_TRUE(prefs.GetBoolean(kBoolPref));
  EXPECT_TRUE(*boolean);

  // Try changing back through the pref.
  prefs.SetBoolean(kBoolPref, false);
  EXPECT_FALSE(prefs.GetBoolean(kBoolPref));
  EXPECT_FALSE(boolean.GetValue());
  EXPECT_FALSE(*boolean);

  // Test int
  IntegerPrefMember integer;
  integer.Init(kIntPref, &prefs, NULL);

  // Check the defaults
  EXPECT_EQ(0, prefs.GetInteger(kIntPref));
  EXPECT_EQ(0, integer.GetValue());
  EXPECT_EQ(0, *integer);

  // Try changing through the member variable.
  integer.SetValue(5);
  EXPECT_EQ(5, integer.GetValue());
  EXPECT_EQ(5, prefs.GetInteger(kIntPref));
  EXPECT_EQ(5, *integer);

  // Try changing back through the pref.
  prefs.SetInteger(kIntPref, 2);
  EXPECT_EQ(2, prefs.GetInteger(kIntPref));
  EXPECT_EQ(2, integer.GetValue());
  EXPECT_EQ(2, *integer);

  // Test double
  DoublePrefMember double_member;
  double_member.Init(kDoublePref, &prefs, NULL);

  // Check the defaults
  EXPECT_EQ(0.0, prefs.GetDouble(kDoublePref));
  EXPECT_EQ(0.0, double_member.GetValue());
  EXPECT_EQ(0.0, *double_member);

  // Try changing through the member variable.
  double_member.SetValue(1.0);
  EXPECT_EQ(1.0, double_member.GetValue());
  EXPECT_EQ(1.0, prefs.GetDouble(kDoublePref));
  EXPECT_EQ(1.0, *double_member);

  // Try changing back through the pref.
  prefs.SetDouble(kDoublePref, 3.0);
  EXPECT_EQ(3.0, prefs.GetDouble(kDoublePref));
  EXPECT_EQ(3.0, double_member.GetValue());
  EXPECT_EQ(3.0, *double_member);

  // Test string
  StringPrefMember string;
  string.Init(kStringPref, &prefs, NULL);

  // Check the defaults
  EXPECT_EQ("default", prefs.GetString(kStringPref));
  EXPECT_EQ("default", string.GetValue());
  EXPECT_EQ("default", *string);

  // Try changing through the member variable.
  string.SetValue("foo");
  EXPECT_EQ("foo", string.GetValue());
  EXPECT_EQ("foo", prefs.GetString(kStringPref));
  EXPECT_EQ("foo", *string);

  // Try changing back through the pref.
  prefs.SetString(kStringPref, "bar");
  EXPECT_EQ("bar", prefs.GetString(kStringPref));
  EXPECT_EQ("bar", string.GetValue());
  EXPECT_EQ("bar", *string);

  // Test list
  ListPrefMember list;
  list.Init(kListPref, &prefs, NULL);

  // Check the defaults
  const ListValue* list_value = prefs.GetList(kListPref);
  ASSERT_TRUE(list_value != NULL);
  EXPECT_EQ(0u, list_value->GetSize());
  EXPECT_TRUE(list_value->empty());
  ASSERT_TRUE(list.GetValue() != NULL);
  EXPECT_EQ(0u, list.GetValue()->GetSize());
  EXPECT_TRUE(list.GetValue()->empty());
  ASSERT_TRUE(*list != NULL);
  EXPECT_EQ(0u, (*list)->GetSize());
  EXPECT_TRUE((*list)->empty());

  // Try changing through the member variable.
  scoped_ptr<ListValue> list_value_numbers(new ListValue());
  list_value_numbers->Append(new StringValue("one"));
  list_value_numbers->Append(new StringValue("two"));
  list_value_numbers->Append(new StringValue("three"));
  list.SetValue(list_value_numbers.get());
  EXPECT_TRUE(list_value_numbers->Equals(list.GetValue()));
  EXPECT_TRUE(list_value_numbers->Equals(prefs.GetList(kListPref)));
  EXPECT_TRUE(list_value_numbers->Equals(*list));

  // Try changing back through the pref.
  ListValue* list_value_ints = new ListValue();
  list_value_ints->Append(new FundamentalValue(1));
  list_value_ints->Append(new FundamentalValue(2));
  list_value_ints->Append(new FundamentalValue(3));
  prefs.SetList(kListPref, list_value_ints); // takes ownership
  EXPECT_TRUE(list_value_ints->Equals(list.GetValue()));
  EXPECT_TRUE(list_value_ints->Equals(prefs.GetList(kListPref)));
  EXPECT_TRUE(list_value_ints->Equals(*list));
}

TEST(PrefMemberTest, TwoPrefs) {
  // Make sure two DoublePrefMembers stay in sync.
  TestingPrefService prefs;
  RegisterTestPrefs(&prefs);

  DoublePrefMember pref1;
  pref1.Init(kDoublePref, &prefs, NULL);
  DoublePrefMember pref2;
  pref2.Init(kDoublePref, &prefs, NULL);

  pref1.SetValue(2.3);
  EXPECT_EQ(2.3, *pref2);

  pref2.SetValue(3.5);
  EXPECT_EQ(3.5, *pref1);

  prefs.SetDouble(kDoublePref, 4.2);
  EXPECT_EQ(4.2, *pref1);
  EXPECT_EQ(4.2, *pref2);
}

TEST(PrefMemberTest, Observer) {
  TestingPrefService prefs;
  RegisterTestPrefs(&prefs);

  PrefMemberTestClass test_obj(&prefs);
  EXPECT_EQ("default", *test_obj.str_);

  // Calling SetValue should not fire the observer.
  test_obj.str_.SetValue("hello");
  EXPECT_EQ(0, test_obj.observe_cnt_);
  EXPECT_EQ("hello", prefs.GetString(kStringPref));

  // Changing the pref does fire the observer.
  prefs.SetString(kStringPref, "world");
  EXPECT_EQ(1, test_obj.observe_cnt_);
  EXPECT_EQ("world", *(test_obj.str_));

  // Not changing the value should not fire the observer.
  prefs.SetString(kStringPref, "world");
  EXPECT_EQ(1, test_obj.observe_cnt_);
  EXPECT_EQ("world", *(test_obj.str_));

  prefs.SetString(kStringPref, "hello");
  EXPECT_EQ(2, test_obj.observe_cnt_);
  EXPECT_EQ("hello", prefs.GetString(kStringPref));
}

TEST(PrefMemberTest, NoInit) {
  // Make sure not calling Init on a PrefMember doesn't cause problems.
  IntegerPrefMember pref;
}

TEST(PrefMemberTest, MoveToThread) {
  TestingPrefService prefs;
  scoped_refptr<GetPrefValueCallback> callback =
      make_scoped_refptr(new GetPrefValueCallback());
  MessageLoop message_loop;
  BrowserThread ui_thread(BrowserThread::UI, &message_loop);
  BrowserThread io_thread(BrowserThread::IO);
  ASSERT_TRUE(io_thread.Start());
  RegisterTestPrefs(&prefs);
  callback->Init(kBoolPref, &prefs);

  ASSERT_TRUE(callback->FetchValue());
  EXPECT_FALSE(callback->value());

  prefs.SetBoolean(kBoolPref, true);

  ASSERT_TRUE(callback->FetchValue());
  EXPECT_TRUE(callback->value());
}