// Copyright 2014 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/segregated_pref_store.h"

#include <string>
#include <utility>

#include "base/barrier_closure.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/notreached.h"
#include "base/observer_list.h"
#include "base/strings/string_piece.h"
#include "base/values.h"
#include "components/prefs/pref_name_set.h"

SegregatedPrefStore::UnderlyingPrefStoreObserver::UnderlyingPrefStoreObserver(
    SegregatedPrefStore* outer)
    : outer_(outer) {
  DCHECK(outer_);
}

void SegregatedPrefStore::UnderlyingPrefStoreObserver::OnPrefValueChanged(
    const std::string& key) {
  // Notify Observers only after all underlying PrefStores of the outer
  // SegregatedPrefStore are initialized.
  if (!outer_->IsInitializationComplete())
    return;

  for (auto& observer : outer_->observers_)
    observer.OnPrefValueChanged(key);
}

void SegregatedPrefStore::UnderlyingPrefStoreObserver::
    OnInitializationCompleted(bool succeeded) {
  initialization_succeeded_ = succeeded;

  // Notify Observers only after all underlying PrefStores of the outer
  // SegregatedPrefStore are initialized.
  if (!outer_->IsInitializationComplete())
    return;

  if (outer_->read_error_delegate_) {
    PersistentPrefStore::PrefReadError read_error = outer_->GetReadError();
    if (read_error != PersistentPrefStore::PREF_READ_ERROR_NONE)
      outer_->read_error_delegate_->OnError(read_error);
  }

  for (auto& observer : outer_->observers_)
    observer.OnInitializationCompleted(outer_->IsInitializationSuccessful());
}

SegregatedPrefStore::SegregatedPrefStore(
    scoped_refptr<PersistentPrefStore> default_pref_store,
    scoped_refptr<PersistentPrefStore> selected_pref_store,
    PrefNameSet selected_pref_names)
    : default_pref_store_(std::move(default_pref_store)),
      selected_pref_store_(std::move(selected_pref_store)),
      selected_preference_names_(std::move(selected_pref_names)),
      default_observer_(this),
      selected_observer_(this) {
  default_pref_store_->AddObserver(&default_observer_);
  selected_pref_store_->AddObserver(&selected_observer_);
}

void SegregatedPrefStore::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void SegregatedPrefStore::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

bool SegregatedPrefStore::HasObservers() const {
  return !observers_.empty();
}

bool SegregatedPrefStore::IsInitializationComplete() const {
  return default_pref_store_->IsInitializationComplete() &&
         selected_pref_store_->IsInitializationComplete();
}

bool SegregatedPrefStore::IsInitializationSuccessful() const {
  return default_observer_.initialization_succeeded() &&
         selected_observer_.initialization_succeeded();
}

bool SegregatedPrefStore::GetValue(base::StringPiece key,
                                   const base::Value** result) const {
  return StoreForKey(key)->GetValue(key, result);
}

base::Value::Dict SegregatedPrefStore::GetValues() const {
  base::Value::Dict values = default_pref_store_->GetValues();
  base::Value::Dict selected_pref_store_values =
      selected_pref_store_->GetValues();
  for (const auto& key : selected_preference_names_) {
    if (base::Value* value = selected_pref_store_values.FindByDottedPath(key)) {
      values.SetByDottedPath(key, std::move(*value));
    } else {
      values.Remove(key);
    }
  }
  return values;
}

void SegregatedPrefStore::SetValue(const std::string& key,
                                   base::Value value,
                                   uint32_t flags) {
  StoreForKey(key)->SetValue(key, std::move(value), flags);
}

void SegregatedPrefStore::RemoveValue(const std::string& key, uint32_t flags) {
  StoreForKey(key)->RemoveValue(key, flags);
}

void SegregatedPrefStore::RemoveValuesByPrefixSilently(
    const std::string& prefix) {
  // Since we can't guarantee to have all the prefs in one the pref stores, we
  // have to push the removal command down to both of them.
  default_pref_store_->RemoveValuesByPrefixSilently(prefix);
  selected_pref_store_->RemoveValuesByPrefixSilently(prefix);
}

bool SegregatedPrefStore::GetMutableValue(const std::string& key,
                                          base::Value** result) {
  return StoreForKey(key)->GetMutableValue(key, result);
}

void SegregatedPrefStore::ReportValueChanged(const std::string& key,
                                             uint32_t flags) {
  StoreForKey(key)->ReportValueChanged(key, flags);
}

void SegregatedPrefStore::SetValueSilently(const std::string& key,
                                           base::Value value,
                                           uint32_t flags) {
  StoreForKey(key)->SetValueSilently(key, std::move(value), flags);
}

bool SegregatedPrefStore::ReadOnly() const {
  return selected_pref_store_->ReadOnly() || default_pref_store_->ReadOnly();
}

PersistentPrefStore::PrefReadError SegregatedPrefStore::GetReadError() const {
  PersistentPrefStore::PrefReadError read_error =
      default_pref_store_->GetReadError();
  if (read_error == PersistentPrefStore::PREF_READ_ERROR_NONE) {
    read_error = selected_pref_store_->GetReadError();
    // Ignore NO_FILE from selected_pref_store_.
    if (read_error == PersistentPrefStore::PREF_READ_ERROR_NO_FILE)
      read_error = PersistentPrefStore::PREF_READ_ERROR_NONE;
  }
  return read_error;
}

PersistentPrefStore::PrefReadError SegregatedPrefStore::ReadPrefs() {
  // Note: Both of these stores own PrefFilters which makes ReadPrefs
  // asynchronous. This is okay in this case as only the first call will be
  // truly asynchronous, the second call will then unblock the migration in
  // TrackedPreferencesMigrator and complete synchronously.
  default_pref_store_->ReadPrefs();
  PersistentPrefStore::PrefReadError selected_store_read_error =
      selected_pref_store_->ReadPrefs();
  DCHECK_NE(PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE,
            selected_store_read_error);

  return GetReadError();
}

void SegregatedPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
  read_error_delegate_.reset(error_delegate);
  default_pref_store_->ReadPrefsAsync(NULL);
  selected_pref_store_->ReadPrefsAsync(NULL);
}

void SegregatedPrefStore::CommitPendingWrite(
    base::OnceClosure reply_callback,
    base::OnceClosure synchronous_done_callback) {
  // A BarrierClosure will run its callback wherever the last instance of the
  // returned wrapper is invoked. As such it is guaranteed to respect the reply
  // vs synchronous semantics assuming |default_pref_store_| and
  // |selected_pref_store_| honor it.

  base::RepeatingClosure reply_callback_wrapper =
      reply_callback ? base::BarrierClosure(2, std::move(reply_callback))
                     : base::RepeatingClosure();

  base::RepeatingClosure synchronous_callback_wrapper =
      synchronous_done_callback
          ? base::BarrierClosure(2, std::move(synchronous_done_callback))
          : base::RepeatingClosure();

  default_pref_store_->CommitPendingWrite(reply_callback_wrapper,
                                          synchronous_callback_wrapper);
  selected_pref_store_->CommitPendingWrite(reply_callback_wrapper,
                                           synchronous_callback_wrapper);
}

void SegregatedPrefStore::SchedulePendingLossyWrites() {
  default_pref_store_->SchedulePendingLossyWrites();
  selected_pref_store_->SchedulePendingLossyWrites();
}

void SegregatedPrefStore::OnStoreDeletionFromDisk() {
  default_pref_store_->OnStoreDeletionFromDisk();
  selected_pref_store_->OnStoreDeletionFromDisk();
}

SegregatedPrefStore::~SegregatedPrefStore() {
  default_pref_store_->RemoveObserver(&default_observer_);
  selected_pref_store_->RemoveObserver(&selected_observer_);
}

PersistentPrefStore* SegregatedPrefStore::StoreForKey(base::StringPiece key) {
  return (base::Contains(selected_preference_names_, key) ? selected_pref_store_
                                                          : default_pref_store_)
      .get();
}

const PersistentPrefStore* SegregatedPrefStore::StoreForKey(
    base::StringPiece key) const {
  return (base::Contains(selected_preference_names_, key) ? selected_pref_store_
                                                          : default_pref_store_)
      .get();
}
