// Copyright (c) 2012 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 "components/prefs/overlay_user_pref_store.h"

#include <memory>
#include <utility>

#include "base/memory/ptr_util.h"
#include "base/values.h"
#include "components/prefs/in_memory_pref_store.h"

// Allows us to monitor two pref stores and tell updates from them apart. It
// essentially mimics a Callback for the Observer interface (e.g. it allows
// binding additional arguments).
class OverlayUserPrefStore::ObserverAdapter : public PrefStore::Observer {
 public:
  ObserverAdapter(bool ephemeral, OverlayUserPrefStore* parent)
      : ephemeral_user_pref_store_(ephemeral), parent_(parent) {}

  // Methods of PrefStore::Observer.
  void OnPrefValueChanged(const std::string& key) override {
    parent_->OnPrefValueChanged(ephemeral_user_pref_store_, key);
  }
  void OnInitializationCompleted(bool succeeded) override {
    parent_->OnInitializationCompleted(ephemeral_user_pref_store_, succeeded);
  }

 private:
  // Is the update for the ephemeral?
  const bool ephemeral_user_pref_store_;
  OverlayUserPrefStore* const parent_;
};

OverlayUserPrefStore::OverlayUserPrefStore(PersistentPrefStore* persistent)
    : OverlayUserPrefStore(new InMemoryPrefStore(), persistent) {}

OverlayUserPrefStore::OverlayUserPrefStore(PersistentPrefStore* ephemeral,
                                           PersistentPrefStore* persistent)
    : ephemeral_pref_store_observer_(
          std::make_unique<OverlayUserPrefStore::ObserverAdapter>(true, this)),
      persistent_pref_store_observer_(
          std::make_unique<OverlayUserPrefStore::ObserverAdapter>(false, this)),
      ephemeral_user_pref_store_(ephemeral),
      persistent_user_pref_store_(persistent) {
  DCHECK(ephemeral->IsInitializationComplete());
  ephemeral_user_pref_store_->AddObserver(ephemeral_pref_store_observer_.get());
  persistent_user_pref_store_->AddObserver(
      persistent_pref_store_observer_.get());
}

bool OverlayUserPrefStore::IsSetInOverlay(const std::string& key) const {
  return ephemeral_user_pref_store_->GetValue(key, nullptr);
}

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

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

bool OverlayUserPrefStore::HasObservers() const {
  return observers_.might_have_observers();
}

bool OverlayUserPrefStore::IsInitializationComplete() const {
  return persistent_user_pref_store_->IsInitializationComplete() &&
         ephemeral_user_pref_store_->IsInitializationComplete();
}

bool OverlayUserPrefStore::GetValue(const std::string& key,
                                    const base::Value** result) const {
  // If the |key| shall NOT be stored in the ephemeral store, there must not
  // be an entry.
  DCHECK(!ShallBeStoredInPersistent(key) ||
         !ephemeral_user_pref_store_->GetValue(key, nullptr));

  if (ephemeral_user_pref_store_->GetValue(key, result))
    return true;
  return persistent_user_pref_store_->GetValue(key, result);
}

std::unique_ptr<base::DictionaryValue> OverlayUserPrefStore::GetValues() const {
  auto values = ephemeral_user_pref_store_->GetValues();
  auto persistent_values = persistent_user_pref_store_->GetValues();

  // Output |values| are read from |ephemeral_user_pref_store_| (in-memory
  // store). Then the values of preferences in |persistent_names_set_| are
  // overwritten by the content of |persistent_user_pref_store_| (the persistent
  // store).
  for (const auto& key : persistent_names_set_) {
    std::unique_ptr<base::Value> out_value;
    persistent_values->Remove(key, &out_value);
    if (out_value) {
      values->Set(key, std::move(out_value));
    }
  }
  return values;
}

bool OverlayUserPrefStore::GetMutableValue(const std::string& key,
                                           base::Value** result) {
  if (ShallBeStoredInPersistent(key))
    return persistent_user_pref_store_->GetMutableValue(key, result);

  written_ephemeral_names_.insert(key);
  if (ephemeral_user_pref_store_->GetMutableValue(key, result))
    return true;

  // Try to create copy of persistent if the ephemeral does not contain a value.
  base::Value* persistent_value = nullptr;
  if (!persistent_user_pref_store_->GetMutableValue(key, &persistent_value))
    return false;

  *result = persistent_value->DeepCopy();
  ephemeral_user_pref_store_->SetValue(
      key, base::WrapUnique(*result),
      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  return true;
}

void OverlayUserPrefStore::SetValue(const std::string& key,
                                    std::unique_ptr<base::Value> value,
                                    uint32_t flags) {
  if (ShallBeStoredInPersistent(key)) {
    persistent_user_pref_store_->SetValue(key, std::move(value), flags);
    return;
  }

  // TODO(https://crbug.com/861722): If we always store in in-memory storage
  // and conditionally also stored in persistent one, we wouldn't have to do a
  // complex merge in GetValues().
  written_ephemeral_names_.insert(key);
  ephemeral_user_pref_store_->SetValue(key, std::move(value), flags);
}

void OverlayUserPrefStore::SetValueSilently(const std::string& key,
                                            std::unique_ptr<base::Value> value,
                                            uint32_t flags) {
  if (ShallBeStoredInPersistent(key)) {
    persistent_user_pref_store_->SetValueSilently(key, std::move(value), flags);
    return;
  }

  written_ephemeral_names_.insert(key);
  ephemeral_user_pref_store_->SetValueSilently(key, std::move(value), flags);
}

void OverlayUserPrefStore::RemoveValue(const std::string& key, uint32_t flags) {
  if (ShallBeStoredInPersistent(key)) {
    persistent_user_pref_store_->RemoveValue(key, flags);
    return;
  }

  written_ephemeral_names_.insert(key);
  ephemeral_user_pref_store_->RemoveValue(key, flags);
}

bool OverlayUserPrefStore::ReadOnly() const {
  return false;
}

PersistentPrefStore::PrefReadError OverlayUserPrefStore::GetReadError() const {
  return PersistentPrefStore::PREF_READ_ERROR_NONE;
}

PersistentPrefStore::PrefReadError OverlayUserPrefStore::ReadPrefs() {
  // We do not read intentionally.
  OnInitializationCompleted(/* ephemeral */ false, true);
  return PersistentPrefStore::PREF_READ_ERROR_NONE;
}

void OverlayUserPrefStore::ReadPrefsAsync(
    ReadErrorDelegate* error_delegate_raw) {
  std::unique_ptr<ReadErrorDelegate> error_delegate(error_delegate_raw);
  // We do not read intentionally.
  OnInitializationCompleted(/* ephemeral */ false, true);
}

void OverlayUserPrefStore::CommitPendingWrite(base::OnceClosure done_callback) {
  persistent_user_pref_store_->CommitPendingWrite(std::move(done_callback));
  // We do not write our content intentionally.
}

void OverlayUserPrefStore::SchedulePendingLossyWrites() {
  persistent_user_pref_store_->SchedulePendingLossyWrites();
}

void OverlayUserPrefStore::ReportValueChanged(const std::string& key,
                                              uint32_t flags) {
  for (PrefStore::Observer& observer : observers_)
    observer.OnPrefValueChanged(key);
}

void OverlayUserPrefStore::RegisterPersistentPref(const std::string& key) {
  DCHECK(!key.empty()) << "Key is empty";
  DCHECK(persistent_names_set_.find(key) == persistent_names_set_.end())
      << "Key already registered: " << key;
  persistent_names_set_.insert(key);
}

void OverlayUserPrefStore::ClearMutableValues() {
  for (const auto& key : written_ephemeral_names_) {
    ephemeral_user_pref_store_->RemoveValue(
        key, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  }
}

void OverlayUserPrefStore::OnStoreDeletionFromDisk() {
  persistent_user_pref_store_->OnStoreDeletionFromDisk();
}

OverlayUserPrefStore::~OverlayUserPrefStore() {
  ephemeral_user_pref_store_->RemoveObserver(
      ephemeral_pref_store_observer_.get());
  persistent_user_pref_store_->RemoveObserver(
      persistent_pref_store_observer_.get());
}

void OverlayUserPrefStore::OnPrefValueChanged(bool ephemeral,
                                              const std::string& key) {
  if (ephemeral) {
    ReportValueChanged(key, DEFAULT_PREF_WRITE_FLAGS);
  } else {
    if (!ephemeral_user_pref_store_->GetValue(key, nullptr))
      ReportValueChanged(key, DEFAULT_PREF_WRITE_FLAGS);
  }
}

void OverlayUserPrefStore::OnInitializationCompleted(bool ephemeral,
                                                     bool succeeded) {
  if (!IsInitializationComplete())
    return;
  for (PrefStore::Observer& observer : observers_)
    observer.OnInitializationCompleted(succeeded);
}

bool OverlayUserPrefStore::ShallBeStoredInPersistent(
    const std::string& key) const {
  return persistent_names_set_.find(key) != persistent_names_set_.end();
}
