// 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 "components/system_media_controls/mac/system_media_controls_mac.h"

#include "base/check_is_test.h"
#include "base/notimplemented.h"
#include "components/remote_cocoa/browser/application_host.h"
#include "components/system_media_controls/mac/remote_cocoa/system_media_controls_bridge.h"
#include "components/system_media_controls/system_media_controls_observer.h"
#include "third_party/skia/include/core/SkBitmap.h"

namespace {
system_media_controls::mojom::PlaybackStatus ConvertPlaybackStatus(
    system_media_controls::SystemMediaControls::PlaybackStatus status) {
  switch (status) {
    case system_media_controls::SystemMediaControls::PlaybackStatus::kPlaying:
      return system_media_controls::mojom::PlaybackStatus::kPlaying;
    case system_media_controls::SystemMediaControls::PlaybackStatus::kPaused:
      return system_media_controls::mojom::PlaybackStatus::kPaused;
    case system_media_controls::SystemMediaControls::PlaybackStatus::kStopped:
      return system_media_controls::mojom::PlaybackStatus::kStopped;
  }
}
}  // namespace

namespace system_media_controls {

// For testing only.
base::RepeatingCallback<void(bool)>*
    g_on_visibility_changed_for_testing_callback = nullptr;

// static
std::unique_ptr<SystemMediaControls> SystemMediaControls::Create(
    remote_cocoa::ApplicationHost* application_host) {
  return std::make_unique<internal::SystemMediaControlsMac>(application_host);
}
// static
void SystemMediaControls::SetVisibilityChangedCallbackForTesting(
    base::RepeatingCallback<void(bool)>* callback) {
  CHECK_IS_TEST();
  g_on_visibility_changed_for_testing_callback = callback;
}

namespace internal {

SystemMediaControlsMac::SystemMediaControlsMac(
    remote_cocoa::ApplicationHost* application_host)
    : application_host_(application_host) {
  if (application_host) {
    // ApplicationHost only has a value for PWAs. Use it to make an
    // out-of-process SystemMediaControlsBridge in the correct app shim process.
    remote_cocoa::mojom::Application* application_bridge =
        application_host->GetApplication();
    CHECK(application_bridge);

    application_bridge->CreateSystemMediaControlsBridge(
        bridge_remote_.BindNewPipeAndPassReceiver(),
        bridge_receiver_.BindNewPipeAndPassRemote());

    DCHECK(bridge_remote_.is_bound());
    DCHECK(bridge_receiver_.is_bound());
    DCHECK(bridge_remote_.is_connected());

    // Additionally, if we're making an out of process bridge, we should
    // observe `application_host` so we can get notified when the app shim
    // is going away.
    application_host->AddObserver(this);
  } else {
    // This SMC is for the browser, make an in-process bridge.
    in_proc_bridge_ = std::make_unique<SystemMediaControlsBridge>(
        bridge_remote_.BindNewPipeAndPassReceiver(),
        bridge_receiver_.BindNewPipeAndPassRemote());
  }
}

SystemMediaControlsMac::~SystemMediaControlsMac() {
  if (application_host_) {
    application_host_->RemoveObserver(this);
    application_host_ = nullptr;
  }
}

void SystemMediaControlsMac::AddObserver(
    system_media_controls::SystemMediaControlsObserver* observer) {
  MaybeRebindToBridge();
  observers_.AddObserver(observer);
}

void SystemMediaControlsMac::RemoveObserver(
    system_media_controls::SystemMediaControlsObserver* observer) {
  MaybeRebindToBridge();
  observers_.RemoveObserver(observer);
}

void SystemMediaControlsMac::SetIsNextEnabled(bool value) {
  MaybeRebindToBridge();
  bridge_remote_->SetIsNextEnabled(value);
}

void SystemMediaControlsMac::SetIsPreviousEnabled(bool value) {
  MaybeRebindToBridge();
  bridge_remote_->SetIsPreviousEnabled(value);
}

void SystemMediaControlsMac::SetIsPlayPauseEnabled(bool value) {
  MaybeRebindToBridge();
  bridge_remote_->SetIsPlayPauseEnabled(value);
}

void SystemMediaControlsMac::SetIsStopEnabled(bool value) {
  MaybeRebindToBridge();
  bridge_remote_->SetIsStopEnabled(value);
}

void SystemMediaControlsMac::SetIsSeekToEnabled(bool value) {
  MaybeRebindToBridge();
  bridge_remote_->SetIsSeekToEnabled(value);
}

void SystemMediaControlsMac::SetPlaybackStatus(PlaybackStatus status) {
  MaybeRebindToBridge();
  bridge_remote_->SetPlaybackStatus(ConvertPlaybackStatus(status));
}

void SystemMediaControlsMac::SetTitle(const std::u16string& title) {
  MaybeRebindToBridge();
  bridge_remote_->SetTitle(title);
}

void SystemMediaControlsMac::SetArtist(const std::u16string& artist) {
  MaybeRebindToBridge();
  bridge_remote_->SetArtist(artist);
}

void SystemMediaControlsMac::SetAlbum(const std::u16string& album) {
  MaybeRebindToBridge();
  bridge_remote_->SetAlbum(album);
}

void SystemMediaControlsMac::SetThumbnail(const SkBitmap& bitmap) {
  MaybeRebindToBridge();
  bridge_remote_->SetThumbnail(bitmap);
}

void SystemMediaControlsMac::SetPosition(
    const media_session::MediaPosition& position) {
  MaybeRebindToBridge();
  bridge_remote_->SetPosition(position);
}

void SystemMediaControlsMac::ClearPosition() {
  MaybeRebindToBridge();
  bridge_remote_->ClearPosition();
}

void SystemMediaControlsMac::ClearMetadata() {
  MaybeRebindToBridge();
  bridge_remote_->ClearMetadata();
}

bool SystemMediaControlsMac::GetVisibilityForTesting() const {
  NOTIMPLEMENTED();
  return false;
}

void SystemMediaControlsMac::SetOnBridgeCreatedCallbackForTesting(
    base::RepeatingCallback<void()> callback) {
  on_bridge_created_callback_for_testing_ = callback;
}

// These need to go tell the browser that Mac did something.
// These used to live in RemoteCommandCenterDelegate, but now move here since
// the observers list moved here.
void SystemMediaControlsMac::OnNext() {
  for (auto& observer : observers_) {
    observer.OnNext(this);
  }
}

void SystemMediaControlsMac::OnPrevious() {
  for (auto& observer : observers_) {
    observer.OnPrevious(this);
  }
}

void SystemMediaControlsMac::OnPause() {
  for (auto& observer : observers_) {
    observer.OnPause(this);
  }
}

void SystemMediaControlsMac::OnPlayPause() {
  for (auto& observer : observers_) {
    observer.OnPlayPause(this);
  }
}

void SystemMediaControlsMac::OnStop() {
  for (auto& observer : observers_) {
    observer.OnStop(this);
  }
}

void SystemMediaControlsMac::OnPlay() {
  for (auto& observer : observers_) {
    observer.OnPlay(this);
  }
}

void SystemMediaControlsMac::OnSeekTo(base::TimeDelta seek_time) {
  for (auto& observer : observers_) {
    observer.OnSeekTo(this, seek_time);
  }
}

void SystemMediaControlsMac::OnBridgeCreatedForTesting() {
  // The app shim has just told us that the SMCBridge was created. Update any
  // tests that are listening.
  if (on_bridge_created_callback_for_testing_) {
    std::move(on_bridge_created_callback_for_testing_).Run();
  }
}

void SystemMediaControlsMac::OnMetadataClearedForTesting() {
  if (g_on_visibility_changed_for_testing_callback) {
    // The mojo test API told us that the metadata has been cleared.
    // We are using this as a best-approximate signal that the visibility of the
    // controls has changed, so run the callback with false, as clearing
    // metadata implies that the controls should be hidden soon.
    CHECK_IS_TEST();
    g_on_visibility_changed_for_testing_callback->Run(false);
  }
}

void SystemMediaControlsMac::OnApplicationHostDestroying(
    remote_cocoa::ApplicationHost* host) {
  // If we get here, the user has likely Cmd+Q quit the app.
  application_host_->RemoveObserver(this);
  application_host_ = nullptr;
}

void SystemMediaControlsMac::MaybeRebindToBridge() {
  if (bridge_remote_ && bridge_remote_.is_connected()) {
    return;
  }

  // Don't try to rebind if we've received OnApplicationHostDestroying, as
  // ApplicationBridge will have gone away.
  if (application_host_) {
    // Before we reset our connections, clear the existing metadata to ensure
    // we don't mix data between the mojo connections.
    bridge_remote_->ClearMetadata();

    bridge_remote_.reset();
    bridge_receiver_.reset();

    remote_cocoa::mojom::Application* application_bridge =
        application_host_->GetApplication();
    DCHECK(application_bridge);

    // We need to reconnect.
    application_bridge->CreateSystemMediaControlsBridge(
        bridge_remote_.BindNewPipeAndPassReceiver(),
        bridge_receiver_.BindNewPipeAndPassRemote());
  }
}

}  // namespace internal
}  // namespace system_media_controls
