// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/prefs/pref_value_store.h"

#include <stddef.h>

#include <string>
#include <string_view>

#include "base/logging.h"
#include "components/prefs/pref_notifier.h"

PrefValueStore::PrefStoreKeeper::PrefStoreKeeper()
    : pref_value_store_(nullptr), type_(PrefValueStore::INVALID_STORE) {}

PrefValueStore::PrefStoreKeeper::~PrefStoreKeeper() {
  if (pref_store_) {
    pref_store_->RemoveObserver(this);
    pref_store_ = nullptr;
  }
  pref_value_store_ = nullptr;
}

void PrefValueStore::PrefStoreKeeper::Initialize(
    PrefValueStore* store,
    scoped_refptr<PrefStore> pref_store,
    PrefValueStore::PrefStoreType type) {
  if (pref_store_) {
    pref_store_->RemoveObserver(this);
    DCHECK(!pref_store_->HasObservers());
  }
  type_ = type;
  pref_value_store_ = store;
  pref_store_ = pref_store;
  if (pref_store_)
    pref_store_->AddObserver(this);
}

void PrefValueStore::PrefStoreKeeper::OnPrefValueChanged(std::string_view key) {
  pref_value_store_->OnPrefValueChanged(type_, key);
}

void PrefValueStore::PrefStoreKeeper::OnInitializationCompleted(
    bool succeeded) {
  pref_value_store_->OnInitializationCompleted(type_, succeeded);
}

PrefValueStore::PrefValueStore(scoped_refptr<PrefStore> managed_prefs,
                               scoped_refptr<PrefStore> supervised_user_prefs,
                               scoped_refptr<PrefStore> extension_prefs,
                               scoped_refptr<PrefStore> command_line_prefs,
                               scoped_refptr<PrefStore> user_prefs,
                               scoped_refptr<PrefStore> recommended_prefs,
                               scoped_refptr<PrefStore> default_prefs,
                               PrefNotifier* pref_notifier)
    : pref_notifier_(pref_notifier), initialization_failed_(false) {
  InitPrefStore(MANAGED_STORE, std::move(managed_prefs));
  InitPrefStore(SUPERVISED_USER_STORE, std::move(supervised_user_prefs));
  InitPrefStore(EXTENSION_STORE, std::move(extension_prefs));
  InitPrefStore(COMMAND_LINE_STORE, std::move(command_line_prefs));
  InitPrefStore(USER_STORE, std::move(user_prefs));
  InitPrefStore(RECOMMENDED_STORE, std::move(recommended_prefs));
  InitPrefStore(DEFAULT_STORE, std::move(default_prefs));

  CheckInitializationCompleted();
}

PrefValueStore::~PrefValueStore() = default;

std::unique_ptr<PrefValueStore> PrefValueStore::CloneAndSpecialize(
    scoped_refptr<PrefStore> managed_prefs,
    scoped_refptr<PrefStore> supervised_user_prefs,
    scoped_refptr<PrefStore> extension_prefs,
    scoped_refptr<PrefStore> command_line_prefs,
    scoped_refptr<PrefStore> user_prefs,
    scoped_refptr<PrefStore> recommended_prefs,
    scoped_refptr<PrefStore> default_prefs,
    PrefNotifier* pref_notifier) {
  DCHECK(pref_notifier);
  if (!managed_prefs)
    managed_prefs = GetPrefStoreRef(MANAGED_STORE);
  if (!supervised_user_prefs)
    supervised_user_prefs = GetPrefStoreRef(SUPERVISED_USER_STORE);
  if (!extension_prefs)
    extension_prefs = GetPrefStoreRef(EXTENSION_STORE);
  if (!command_line_prefs)
    command_line_prefs = GetPrefStoreRef(COMMAND_LINE_STORE);
  if (!user_prefs)
    user_prefs = GetPrefStoreRef(USER_STORE);
  if (!recommended_prefs)
    recommended_prefs = GetPrefStoreRef(RECOMMENDED_STORE);
  if (!default_prefs)
    default_prefs = GetPrefStoreRef(DEFAULT_STORE);

  return std::make_unique<PrefValueStore>(
      std::move(managed_prefs), std::move(supervised_user_prefs),
      std::move(extension_prefs), std::move(command_line_prefs),
      std::move(user_prefs), std::move(recommended_prefs),
      std::move(default_prefs), pref_notifier);
}

PrefValueStore::PrefStoreType PrefValueStore::ControllingPrefStoreForPref(
    const std::string& name) const {
  for (size_t i = 0; i <= PREF_STORE_TYPE_MAX; ++i) {
    if (PrefValueInStore(name, static_cast<PrefStoreType>(i))) {
      return static_cast<PrefStoreType>(i);
    }
  }
  return INVALID_STORE;
}

bool PrefValueStore::GetValue(std::string_view name,
                              base::Value::Type type,
                              const base::Value** out_value) const {
  // Check the |PrefStore|s in order of their priority from highest to lowest,
  // looking for the first preference value with the given |name| and |type|.
  for (size_t i = 0; i <= PREF_STORE_TYPE_MAX; ++i) {
    if (GetValueFromStoreWithType(name, type, static_cast<PrefStoreType>(i),
                                  out_value))
      return true;
  }
  return false;
}

bool PrefValueStore::GetRecommendedValue(const std::string& name,
                                         base::Value::Type type,
                                         const base::Value** out_value) const {
  return GetValueFromStoreWithType(name, type, RECOMMENDED_STORE, out_value);
}

void PrefValueStore::NotifyPrefChanged(
    std::string_view path,
    PrefValueStore::PrefStoreType new_store) {
  DCHECK(new_store != INVALID_STORE);
  // A notification is sent when the pref value in any store changes. If this
  // store is currently being overridden by a higher-priority store, the
  // effective value of the pref will not have changed.
  pref_notifier_->OnPreferenceChanged(path);
}

bool PrefValueStore::PrefValueInManagedStore(const std::string& name) const {
  return PrefValueInStore(name, MANAGED_STORE);
}

bool PrefValueStore::PrefValueInSupervisedStore(const std::string& name) const {
  return PrefValueInStore(name, SUPERVISED_USER_STORE);
}

bool PrefValueStore::PrefValueInExtensionStore(const std::string& name) const {
  return PrefValueInStore(name, EXTENSION_STORE);
}

bool PrefValueStore::PrefValueInUserStore(const std::string& name) const {
  return PrefValueInStore(name, USER_STORE);
}

bool PrefValueStore::PrefValueFromExtensionStore(
    const std::string& name) const {
  return ControllingPrefStoreForPref(name) == EXTENSION_STORE;
}

bool PrefValueStore::PrefValueFromUserStore(const std::string& name) const {
  return ControllingPrefStoreForPref(name) == USER_STORE;
}

bool PrefValueStore::PrefValueFromRecommendedStore(
    const std::string& name) const {
  return ControllingPrefStoreForPref(name) == RECOMMENDED_STORE;
}

bool PrefValueStore::PrefValueFromDefaultStore(const std::string& name) const {
  return ControllingPrefStoreForPref(name) == DEFAULT_STORE;
}

bool PrefValueStore::PrefValueUserModifiable(const std::string& name) const {
  PrefStoreType effective_store = ControllingPrefStoreForPref(name);
  return effective_store >= USER_STORE ||
         effective_store == INVALID_STORE;
}

bool PrefValueStore::PrefValueExtensionModifiable(
    const std::string& name) const {
  PrefStoreType effective_store = ControllingPrefStoreForPref(name);
  return effective_store >= EXTENSION_STORE ||
         effective_store == INVALID_STORE;
}

void PrefValueStore::UpdateCommandLinePrefStore(
    scoped_refptr<PrefStore> command_line_prefs) {
  InitPrefStore(COMMAND_LINE_STORE, std::move(command_line_prefs));
}

void PrefValueStore::UpdateExtensionPrefStore(
    scoped_refptr<PrefStore> extension_prefs) {
  InitPrefStore(EXTENSION_STORE, std::move(extension_prefs));
}

bool PrefValueStore::IsInitializationComplete() const {
  for (size_t i = 0; i <= PREF_STORE_TYPE_MAX; ++i) {
    const PrefStore* pref_store = GetPrefStore(static_cast<PrefStoreType>(i));
    if (pref_store && !pref_store->IsInitializationComplete()) {
      return false;
    }
  }
  return true;
}

bool PrefValueStore::PrefValueInStore(
    const std::string& name,
    PrefValueStore::PrefStoreType store) const {
  // Declare a temp Value* and call GetValueFromStore,
  // ignoring the output value.
  const base::Value* tmp_value = nullptr;
  return GetValueFromStore(name, store, &tmp_value);
}

bool PrefValueStore::PrefValueInStoreRange(
    const std::string& name,
    PrefValueStore::PrefStoreType first_checked_store,
    PrefValueStore::PrefStoreType last_checked_store) const {
  if (first_checked_store > last_checked_store) {
    NOTREACHED();
  }

  for (size_t i = first_checked_store;
       i <= static_cast<size_t>(last_checked_store); ++i) {
    if (PrefValueInStore(name, static_cast<PrefStoreType>(i)))
      return true;
  }
  return false;
}

bool PrefValueStore::GetValueFromStore(std::string_view name,
                                       PrefValueStore::PrefStoreType store_type,
                                       const base::Value** out_value) const {
  // Only return true if we find a value and it is the correct type, so stale
  // values with the incorrect type will be ignored.
  const PrefStore* store = GetPrefStore(static_cast<PrefStoreType>(store_type));
  if (store && store->GetValue(name, out_value))
    return true;

  // No valid value found for the given preference name: set the return value
  // to false.
  *out_value = nullptr;
  return false;
}

bool PrefValueStore::GetValueFromStoreWithType(
    std::string_view name,
    base::Value::Type type,
    PrefStoreType store,
    const base::Value** out_value) const {
  if (GetValueFromStore(name, store, out_value)) {
    if ((*out_value)->type() == type)
      return true;

    LOG(WARNING) << "Expected type for " << name << " is " << type
                 << " but got " << (*out_value)->type() << " in store "
                 << store;
  }

  *out_value = nullptr;
  return false;
}

void PrefValueStore::OnPrefValueChanged(PrefValueStore::PrefStoreType type,
                                        std::string_view key) {
  NotifyPrefChanged(key, type);
}

void PrefValueStore::OnInitializationCompleted(
    PrefValueStore::PrefStoreType type, bool succeeded) {
  if (initialization_failed_)
    return;
  if (!succeeded) {
    initialization_failed_ = true;
    pref_notifier_->OnInitializationCompleted(false);
    return;
  }
  CheckInitializationCompleted();
}

void PrefValueStore::InitPrefStore(PrefValueStore::PrefStoreType type,
                                   scoped_refptr<PrefStore> pref_store) {
  pref_stores_[type].Initialize(this, std::move(pref_store), type);
}

void PrefValueStore::CheckInitializationCompleted() {
  if (initialization_failed_)
    return;
  for (size_t i = 0; i <= PREF_STORE_TYPE_MAX; ++i) {
    scoped_refptr<PrefStore> store =
        GetPrefStore(static_cast<PrefStoreType>(i));
    if (store.get() && !store->IsInitializationComplete())
      return;
  }
  pref_notifier_->OnInitializationCompleted(true);
}
