// Copyright 2020 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/notifications/notification_platform_bridge_delegator.h"

#include <algorithm>
#include <utility>
#include <vector>

#include "base/barrier_closure.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/notifications/notification_display_service_impl.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"

#if BUILDFLAG(ENABLE_CHROME_NOTIFICATIONS)
#include "chrome/browser/notifications/notification_platform_bridge_message_center.h"
#endif

#if BUILDFLAG(IS_WIN)
#include "chrome/browser/notifications/notification_platform_bridge_win.h"
#endif

namespace {

// Returns if the current platform has system notifications enabled.
// Platforms behave as follows:
//
//   * Android, Chrome OS
//     Always uses system notifications.
//
//   * Windows before 10 RS4 (incl. Win8 & Win7)
//     Always uses message center.
//
//   * Mac OS X, Linux, Windows 10 RS4+
//     Uses system notifications by default, but can fall back to the message
//     center if features::kSystemNotifications is disabled or initialization
//     fails. Linux additionally checks if prefs::kAllowSystemNotifications is
//     disabled and falls back to the message center if so.
//
// Please try to keep this comment up to date when changing behaviour on one of
// the platforms supported by the browser.
bool SystemNotificationsEnabled(Profile* profile) {
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
  return true;
#elif BUILDFLAG(IS_WIN)
  return NotificationPlatformBridgeWin::SystemNotificationEnabled();
#else
#if BUILDFLAG(IS_LINUX)
  if (profile) {
    // Prefs take precedence over flags.
    PrefService* prefs = profile->GetPrefs();
    if (!prefs->GetBoolean(prefs::kAllowSystemNotifications)) {
      return false;
    }
  }
#endif  // BUILDFLAG(IS_LINUX)
  return base::FeatureList::IsEnabled(features::kNativeNotifications) &&
         base::FeatureList::IsEnabled(features::kSystemNotifications);
#endif  // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
}

NotificationPlatformBridge* GetSystemNotificationPlatformBridge(
    Profile* profile) {
  if (SystemNotificationsEnabled(profile))
    return g_browser_process->notification_platform_bridge();

  // The platform does not support, or has not enabled, system notifications.
  return nullptr;
}

// Returns the NotificationPlatformBridge to use for the message center. May be
// a nullptr for platforms where the message center is not available.
NotificationPlatformBridge* GetMessageCenterBridge() {
#if BUILDFLAG(ENABLE_CHROME_NOTIFICATIONS)
  return NotificationPlatformBridgeMessageCenter::Get();
#else
  return nullptr;
#endif
}

}  // namespace

NotificationPlatformBridgeDelegator::NotificationPlatformBridgeDelegator(
    Profile* profile,
    base::OnceClosure ready_callback)
    : profile_(profile),
      message_center_bridge_(GetMessageCenterBridge()),
      system_bridge_(GetSystemNotificationPlatformBridge(profile_)),
      ready_callback_(std::move(ready_callback)) {
  // Initialize the |system_bridge_| if system notifications are available,
  // otherwise signal that the bridge could not be initialized.
  if (system_bridge_) {
    system_bridge_->SetReadyCallback(
        base::BindOnce(&NotificationPlatformBridgeDelegator::
                           OnSystemNotificationPlatformBridgeReady,
                       weak_factory_.GetWeakPtr()));
  } else {
    OnSystemNotificationPlatformBridgeReady(/*success=*/false);
  }
}

NotificationPlatformBridgeDelegator::~NotificationPlatformBridgeDelegator() =
    default;

void NotificationPlatformBridgeDelegator::Display(
    NotificationHandler::Type notification_type,
    const message_center::Notification& notification,
    std::unique_ptr<NotificationCommon::Metadata> metadata) {
  NotificationPlatformBridge* bridge = GetBridgeForType(notification_type);
  DCHECK(bridge);
  bridge->Display(notification_type, profile_, notification,
                  std::move(metadata));
}

void NotificationPlatformBridgeDelegator::Close(
    NotificationHandler::Type notification_type,
    const std::string& notification_id) {
  NotificationPlatformBridge* bridge = GetBridgeForType(notification_type);
  DCHECK(bridge);
  bridge->Close(profile_, notification_id);
}

void NotificationPlatformBridgeDelegator::GetDisplayed(
    GetDisplayedNotificationsCallback callback) const {
  // TODO(crbug.com/40788519): We currently only query one of the bridges for
  // displayed notifications which may not return TRANSIENT style ones. Ideally
  // there would be only one bridge to query from.
  NotificationPlatformBridge* bridge =
      system_bridge_ ? system_bridge_.get() : message_center_bridge_.get();
  DCHECK(bridge);
  bridge->GetDisplayed(profile_, std::move(callback));
}

void NotificationPlatformBridgeDelegator::GetDisplayedForOrigin(
    const GURL& origin,
    GetDisplayedNotificationsCallback callback) const {
  // TODO(crbug.com/40788519): We currently only query one of the bridges for
  // displayed notifications which may not return TRANSIENT style ones. Ideally
  // there would be only one bridge to query from.
  NotificationPlatformBridge* bridge =
      system_bridge_ ? system_bridge_.get() : message_center_bridge_.get();
  DCHECK(bridge);
  bridge->GetDisplayedForOrigin(profile_, origin, std::move(callback));
}

void NotificationPlatformBridgeDelegator::DisplayServiceShutDown() {
  if (message_center_bridge_)
    message_center_bridge_->DisplayServiceShutDown(profile_);
  if (system_bridge_)
    system_bridge_->DisplayServiceShutDown(profile_);
}

NotificationPlatformBridge*
NotificationPlatformBridgeDelegator::GetBridgeForType(
    NotificationHandler::Type type) {
  // Prefer the system bridge if available and it can handle |type|.
  if (system_bridge_ && NotificationPlatformBridge::CanHandleType(type))
    return system_bridge_;
  return message_center_bridge_;
}

void NotificationPlatformBridgeDelegator::
    OnSystemNotificationPlatformBridgeReady(bool success) {
  if (!success) {
    // Fall back to the message center if initialization failed. Initialization
    // must always succeed on platforms where the message center is unavailable.
    DCHECK(message_center_bridge_);
    system_bridge_ = nullptr;
  }

  base::UmaHistogramBoolean("Notifications.UsingSystemNotificationCenter",
                            system_bridge_ != nullptr);

  if (ready_callback_)
    std::move(ready_callback_).Run();
}
