// 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/logging.h"
#include "base/value_conversions.h"
#include "chrome/browser/prefs/pref_service.h"
#include "content/common/notification_type.h"

namespace subtle {

PrefMemberBase::PrefMemberBase()
    : observer_(NULL),
      prefs_(NULL),
      setting_value_(false) {
}

PrefMemberBase::~PrefMemberBase() {
  Destroy();
}


void PrefMemberBase::Init(const char* pref_name, PrefService* prefs,
                          NotificationObserver* observer) {
  DCHECK(pref_name);
  DCHECK(prefs);
  DCHECK(pref_name_.empty());  // Check that Init is only called once.
  observer_ = observer;
  prefs_ = prefs;
  pref_name_ = pref_name;
  DCHECK(!pref_name_.empty());

  // Add ourselves as a pref observer so we can keep our local value in sync.
  prefs_->AddPrefObserver(pref_name, this);
}

void PrefMemberBase::Destroy() {
  if (prefs_ && !pref_name_.empty()) {
    prefs_->RemovePrefObserver(pref_name_.c_str(), this);
    prefs_ = NULL;
  }
}

void PrefMemberBase::MoveToThread(BrowserThread::ID thread_id) {
  VerifyValuePrefName();
  // Load the value from preferences if it hasn't been loaded so far.
  if (!internal())
    UpdateValueFromPref();
  internal()->MoveToThread(thread_id);
}

void PrefMemberBase::Observe(NotificationType type,
                             const NotificationSource& source,
                             const NotificationDetails& details) {
  VerifyValuePrefName();
  DCHECK(NotificationType::PREF_CHANGED == type);
  UpdateValueFromPref();
  if (!setting_value_ && observer_)
    observer_->Observe(type, source, details);
}

void PrefMemberBase::UpdateValueFromPref() const {
  VerifyValuePrefName();
  const PrefService::Preference* pref =
      prefs_->FindPreference(pref_name_.c_str());
  DCHECK(pref);
  if (!internal())
    CreateInternal();
  internal()->UpdateValue(pref->GetValue()->DeepCopy(), pref->IsManaged());
}

void PrefMemberBase::VerifyPref() const {
  VerifyValuePrefName();
  if (!internal())
    UpdateValueFromPref();
}

PrefMemberBase::Internal::Internal() : thread_id_(BrowserThread::UI) { }
PrefMemberBase::Internal::~Internal() { }

bool PrefMemberBase::Internal::IsOnCorrectThread() const {
  // In unit tests, there may not be a UI thread.
  return (BrowserThread::CurrentlyOn(thread_id_) ||
          (thread_id_ == BrowserThread::UI &&
           !BrowserThread::IsMessageLoopValid(BrowserThread::UI)));
}

void PrefMemberBase::Internal::UpdateValue(Value* v, bool is_managed) const {
  scoped_ptr<Value> value(v);
  if (IsOnCorrectThread()) {
    bool rv = UpdateValueInternal(*value);
    DCHECK(rv);
    is_managed_ = is_managed;
  } else {
    bool rv = BrowserThread::PostTask(
        thread_id_, FROM_HERE,
        NewRunnableMethod(this,
                          &PrefMemberBase::Internal::UpdateValue,
                          value.release(), is_managed));
    DCHECK(rv);
  }
}

void PrefMemberBase::Internal::MoveToThread(BrowserThread::ID thread_id) {
  CheckOnCorrectThread();
  thread_id_ = thread_id;
}

}  // namespace subtle

template <>
void PrefMember<bool>::UpdatePref(const bool& value) {
  prefs()->SetBoolean(pref_name().c_str(), value);
}

template <>
bool PrefMember<bool>::Internal::UpdateValueInternal(const Value& value) const {
  return value.GetAsBoolean(&value_);
}

template <>
void PrefMember<int>::UpdatePref(const int& value) {
  prefs()->SetInteger(pref_name().c_str(), value);
}

template <>
bool PrefMember<int>::Internal::UpdateValueInternal(const Value& value) const {
  return value.GetAsInteger(&value_);
}

template <>
void PrefMember<double>::UpdatePref(const double& value) {
  prefs()->SetDouble(pref_name().c_str(), value);
}

template <>
bool PrefMember<double>::Internal::UpdateValueInternal(const Value& value)
    const {
  return value.GetAsDouble(&value_);
}

template <>
void PrefMember<std::string>::UpdatePref(const std::string& value) {
  prefs()->SetString(pref_name().c_str(), value);
}

template <>
bool PrefMember<std::string>::Internal::UpdateValueInternal(const Value& value)
    const {
  return value.GetAsString(&value_);
}

template <>
void PrefMember<FilePath>::UpdatePref(const FilePath& value) {
  prefs()->SetFilePath(pref_name().c_str(), value);
}

template <>
bool PrefMember<FilePath>::Internal::UpdateValueInternal(const Value& value)
    const {
  return base::GetValueAsFilePath(value, &value_);
}

template <>
void PrefMember<ListValue*>::UpdatePref(ListValue*const& value) {
  // prefs takes ownership of the value passed, so make a copy.
  prefs()->SetList(pref_name().c_str(), value->DeepCopy());
}

template <>
bool PrefMember<ListValue*>::Internal::UpdateValueInternal(const Value& value)
    const {
  // Verify the type before doing the DeepCopy to avoid leaking the copy.
  if (value.GetType() != Value::TYPE_LIST)
    return false;

  // ListPrefMember keeps a copy of the ListValue and of its contents.
  // GetAsList() assigns its |this| (the DeepCopy) to |value_|.
  delete value_;
  return value.DeepCopy()->GetAsList(&value_);
}

template <>
PrefMember<ListValue*>::Internal::~Internal() {
  delete value_;
}