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

#include "chrome/browser/web_applications/test/web_app_test_observers.h"

#include <sstream>

#include "base/check.h"
#include "base/dcheck_is_on.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/strings/to_string.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_provider.h"

namespace web_app {

namespace {

#if DCHECK_IS_ON()
bool IsAnyIdEmpty(const std::set<webapps::AppId>& app_ids) {
  for (const webapps::AppId& id : app_ids) {
    if (id.empty())
      return true;
  }
  return false;
}
#endif

template <typename Container>
std::string ContainerToString(const Container& container) {
  std::ostringstream ss;
  ss << "[";
  bool first_item = true;
  for (const auto& item : container) {
    if (!first_item) {
      ss << ", ";
    }
    ss << base::ToString(item);
    first_item = false;
  }
  ss << "]";
  return ss.str();
}

}  // namespace

WebAppInstallManagerObserverAdapter::WebAppInstallManagerObserverAdapter(
    WebAppInstallManager* install_manager) {
  observation_.Observe(install_manager);
}

WebAppInstallManagerObserverAdapter::WebAppInstallManagerObserverAdapter(
    Profile* profile)
    : WebAppInstallManagerObserverAdapter(
          &WebAppProvider::GetForWebApps(profile)->install_manager()) {}

WebAppInstallManagerObserverAdapter::~WebAppInstallManagerObserverAdapter() =
    default;

void WebAppInstallManagerObserverAdapter::SetWebAppInstalledDelegate(
    WebAppInstalledDelegate delegate) {
  app_installed_delegate_ = std::move(delegate);
}

void WebAppInstallManagerObserverAdapter::SetWebAppInstalledWithOsHooksDelegate(
    WebAppInstalledWithOsHooksDelegate delegate) {
  app_installed_with_os_hooks_delegate_ = std::move(delegate);
}

void WebAppInstallManagerObserverAdapter::SetWebAppWillBeUninstalledDelegate(
    WebAppWillBeUninstalledDelegate delegate) {
  app_will_be_uninstalled_delegate_ = std::move(delegate);
}

void WebAppInstallManagerObserverAdapter::SetWebAppUninstalledDelegate(
    WebAppUninstalledDelegate delegate) {
  app_uninstalled_delegate_ = std::move(delegate);
}

void WebAppInstallManagerObserverAdapter::SetWebAppManifestUpdateDelegate(
    WebAppManifestUpdateDelegate delegate) {
  app_manifest_updated_delegate_ = std::move(delegate);
}

void WebAppInstallManagerObserverAdapter::SetWebAppSourceRemovedDelegate(
    WebAppSourceRemovedDelegate delegate) {
  app_source_removed_delegate_ = std::move(delegate);
}

void WebAppInstallManagerObserverAdapter::SetWebAppMigratedDelegate(
    WebAppMigratedDelegate delegate) {
  app_migrated_delegate_ = std::move(delegate);
}

void WebAppInstallManagerObserverAdapter::OnWebAppInstalled(
    const webapps::AppId& app_id) {
  if (app_installed_delegate_)
    app_installed_delegate_.Run(app_id);
}

void WebAppInstallManagerObserverAdapter::OnWebAppInstalledWithOsHooks(
    const webapps::AppId& app_id) {
  if (app_installed_with_os_hooks_delegate_)
    app_installed_with_os_hooks_delegate_.Run(app_id);
}

void WebAppInstallManagerObserverAdapter::OnWebAppManifestUpdated(
    const webapps::AppId& app_id) {
  if (app_manifest_updated_delegate_)
    app_manifest_updated_delegate_.Run(app_id);
}

void WebAppInstallManagerObserverAdapter::OnWebAppWillBeUninstalled(
    const webapps::AppId& app_id) {
  if (app_will_be_uninstalled_delegate_)
    app_will_be_uninstalled_delegate_.Run(app_id);
}

void WebAppInstallManagerObserverAdapter::OnWebAppUninstalled(
    const webapps::AppId& app_id,
    webapps::WebappUninstallSource uninstall_source) {
  if (app_uninstalled_delegate_)
    app_uninstalled_delegate_.Run(app_id);
}

void WebAppInstallManagerObserverAdapter::OnWebAppInstallManagerDestroyed() {
  observation_.Reset();
}

void WebAppInstallManagerObserverAdapter::OnWebAppSourceRemoved(
    const webapps::AppId& app_id) {
  if (app_source_removed_delegate_) {
    app_source_removed_delegate_.Run(app_id);
  }
}

void WebAppInstallManagerObserverAdapter::OnWebAppMigrated(
    const webapps::AppId& source_app_id,
    const webapps::AppId& target_app_id) {
  if (app_migrated_delegate_) {
    app_migrated_delegate_.Run(source_app_id, target_app_id);
  }
}

void WebAppInstallManagerObserverAdapter::SignalRunLoopAndStoreAppId(
    const webapps::AppId& app_id) {
  if (!is_listening_)
    return;
  optional_app_ids_.erase(app_id);
  if (!optional_app_ids_.empty())
    return;
  last_app_id_ = app_id;
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, wait_loop_.QuitClosure());
  is_listening_ = false;
}

WebAppTestRegistryObserverAdapter::WebAppTestRegistryObserverAdapter(
    WebAppRegistrar* registrar) {
  observation_.Observe(registrar);
}

WebAppTestRegistryObserverAdapter::WebAppTestRegistryObserverAdapter(
    Profile* profile)
    : WebAppTestRegistryObserverAdapter(
          &WebAppProvider::GetForTest(profile)->registrar_unsafe()) {}

WebAppTestRegistryObserverAdapter::~WebAppTestRegistryObserverAdapter() =
    default;

void WebAppTestRegistryObserverAdapter::SetWebAppWillBeUpdatedFromSyncDelegate(
    WebAppWillBeUpdatedFromSyncDelegate delegate) {
  app_will_be_updated_from_sync_delegate_ = std::move(delegate);
}

void WebAppTestRegistryObserverAdapter::SetWebAppEffectiveScopeChangedDelegate(
    WebAppEffectiveScopeChangedDelegate delegate) {
  app_effective_scope_changed_delegate_ = std::move(delegate);
}

void WebAppTestRegistryObserverAdapter::SetWebAppLastBadgingTimeChangedDelegate(
    WebAppLastBadgingTimeChangedDelegate delegate) {
  app_last_badging_time_changed_delegate_ = std::move(delegate);
}

void WebAppTestRegistryObserverAdapter::SetWebAppPendingUpdateChangedDelegate(
    WebAppPendingUpdateChangedDelegate delegate) {
  app_pending_update_changed_delegate_ = std::move(delegate);
}

void WebAppTestRegistryObserverAdapter::
    SetWebAppProtocolSettingsChangedDelegate(
        WebAppProtocolSettingsChangedDelegate delegate) {
  app_protocol_settings_changed_delegate_ = std::move(delegate);
}

void WebAppTestRegistryObserverAdapter::OnWebAppEffectiveScopeChanged(
    const webapps::AppId& app_id,
    const WebAppScope& new_scope) {
  if (app_effective_scope_changed_delegate_) {
    app_effective_scope_changed_delegate_.Run(app_id, new_scope);
  }
}

void WebAppTestRegistryObserverAdapter::OnWebAppsWillBeUpdatedFromSync(
    base::span<const WebApp* const> new_apps_state) {
  if (app_will_be_updated_from_sync_delegate_)
    app_will_be_updated_from_sync_delegate_.Run(new_apps_state);
}

void WebAppTestRegistryObserverAdapter::OnWebAppLastBadgingTimeChanged(
    const webapps::AppId& app_id,
    const base::Time& time) {
  if (app_last_badging_time_changed_delegate_)
    app_last_badging_time_changed_delegate_.Run(app_id, time);
}

void WebAppTestRegistryObserverAdapter::OnWebAppPendingUpdateChanged(
    const webapps::AppId& app_id,
    bool has_pending_update) {
  if (app_pending_update_changed_delegate_) {
    app_pending_update_changed_delegate_.Run(app_id, has_pending_update);
  }
}

void WebAppTestRegistryObserverAdapter::OnWebAppProtocolSettingsChanged(
    const webapps::AppId& app_id) {
  if (app_protocol_settings_changed_delegate_)
    app_protocol_settings_changed_delegate_.Run();
}

void WebAppTestRegistryObserverAdapter::OnAppRegistrarDestroyed() {
  observation_.Reset();
}

void WebAppTestRegistryObserverAdapter::SignalRunLoopAndStoreAppId(
    const webapps::AppId& app_id) {
  if (!is_listening_)
    return;
  optional_app_ids_.erase(app_id);
  if (!optional_app_ids_.empty())
    return;
  last_app_id_ = app_id;
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, wait_loop_.QuitClosure());
  is_listening_ = false;
}

WebAppTestInstallObserver::WebAppTestInstallObserver(Profile* profile)
    : WebAppInstallManagerObserverAdapter(profile) {}
WebAppTestInstallObserver::~WebAppTestInstallObserver() = default;

void WebAppTestInstallObserver::BeginListening(
    const std::set<webapps::AppId>& optional_app_ids) {
  optional_app_ids_ = optional_app_ids;
#if DCHECK_IS_ON()
  DCHECK(!IsAnyIdEmpty(optional_app_ids_)) << "Cannot listen for empty ids.";
#endif
  is_listening_ = true;
  app_installed_delegate_ = base::BindRepeating(
      &WebAppTestInstallObserver::SignalRunLoopAndStoreAppId,
      weak_factory_.GetWeakPtr());
}

webapps::AppId WebAppTestInstallObserver::Wait() {
  wait_loop_.Run();
  if (last_app_id_.empty()) {
    LOG(ERROR) << "Could not find any of "
               << ContainerToString(optional_app_ids_);
  }
  return last_app_id_;
}

webapps::AppId WebAppTestInstallObserver::BeginListeningAndWait(
    const std::set<webapps::AppId>& optional_app_ids) {
  BeginListening(optional_app_ids);
  webapps::AppId id = Wait();
  return id;
}

WebAppTestInstallWithOsHooksObserver::WebAppTestInstallWithOsHooksObserver(
    Profile* profile)
    : WebAppInstallManagerObserverAdapter(profile) {}
WebAppTestInstallWithOsHooksObserver::~WebAppTestInstallWithOsHooksObserver() =
    default;

void WebAppTestInstallWithOsHooksObserver::BeginListening(
    const std::set<webapps::AppId>& optional_app_ids) {
  optional_app_ids_ = optional_app_ids;
#if DCHECK_IS_ON()
  DCHECK(!IsAnyIdEmpty(optional_app_ids_)) << "Cannot listen for empty ids.";
#endif
  is_listening_ = true;
  app_installed_with_os_hooks_delegate_ = base::BindRepeating(
      &WebAppTestInstallWithOsHooksObserver::SignalRunLoopAndStoreAppId,
      weak_factory_.GetWeakPtr());
}

webapps::AppId WebAppTestInstallWithOsHooksObserver::Wait() {
  wait_loop_.Run();
  if (last_app_id_.empty()) {
    LOG(ERROR) << "Could not find any of "
               << ContainerToString(optional_app_ids_);
  }
  return last_app_id_;
}

webapps::AppId WebAppTestInstallWithOsHooksObserver::BeginListeningAndWait(
    const std::set<webapps::AppId>& optional_app_ids) {
  BeginListening(optional_app_ids);
  webapps::AppId id = Wait();
  return id;
}

WebAppTestManifestUpdatedObserver::WebAppTestManifestUpdatedObserver(
    WebAppInstallManager* install_manager)
    : WebAppInstallManagerObserverAdapter(install_manager) {}
WebAppTestManifestUpdatedObserver::~WebAppTestManifestUpdatedObserver() =
    default;

void WebAppTestManifestUpdatedObserver::BeginListening(
    const std::set<webapps::AppId>& optional_app_ids) {
  optional_app_ids_ = optional_app_ids;
#if DCHECK_IS_ON()
  DCHECK(!IsAnyIdEmpty(optional_app_ids_)) << "Cannot listen for empty ids.";
#endif
  is_listening_ = true;
  app_manifest_updated_delegate_ = base::BindRepeating(
      &WebAppTestManifestUpdatedObserver::SignalRunLoopAndStoreAppId,
      weak_factory_.GetWeakPtr());
}

webapps::AppId WebAppTestManifestUpdatedObserver::Wait() {
  wait_loop_.Run();
  if (last_app_id_.empty()) {
    LOG(ERROR) << "Could not find any of "
               << ContainerToString(optional_app_ids_);
  }
  return last_app_id_;
}

webapps::AppId WebAppTestManifestUpdatedObserver::BeginListeningAndWait(
    const std::set<webapps::AppId>& optional_app_ids) {
  BeginListening(optional_app_ids);
  webapps::AppId id = Wait();
  return id;
}

WebAppTestUninstallObserver::WebAppTestUninstallObserver(Profile* profile)
    : WebAppInstallManagerObserverAdapter(profile) {}

WebAppTestUninstallObserver::~WebAppTestUninstallObserver() = default;

void WebAppTestUninstallObserver::BeginListening(
    const std::set<webapps::AppId>& optional_app_ids) {
  optional_app_ids_ = optional_app_ids;
#if DCHECK_IS_ON()
  DCHECK(!IsAnyIdEmpty(optional_app_ids_)) << "Cannot listen for empty ids.";
#endif
  is_listening_ = true;
  app_uninstalled_delegate_ = base::BindRepeating(
      &WebAppTestUninstallObserver::SignalRunLoopAndStoreAppId,
      weak_factory_.GetWeakPtr());
}

webapps::AppId WebAppTestUninstallObserver::Wait() {
  wait_loop_.Run();
  if (last_app_id_.empty()) {
    LOG(ERROR) << "Could not find any of "
               << ContainerToString(optional_app_ids_);
  }
  return last_app_id_;
}

webapps::AppId WebAppTestUninstallObserver::BeginListeningAndWait(
    const std::set<webapps::AppId>& optional_app_ids) {
  BeginListening(optional_app_ids);
  webapps::AppId id = Wait();
  return id;
}

}  // namespace web_app
