// 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.

#ifndef COMPONENTS_SYSTEM_MEDIA_CONTROLS_LINUX_SYSTEM_MEDIA_CONTROLS_LINUX_H_
#define COMPONENTS_SYSTEM_MEDIA_CONTROLS_LINUX_SYSTEM_MEDIA_CONTROLS_LINUX_H_

#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/component_export.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_file.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/threading/sequence_bound.h"
#include "base/timer/timer.h"
#include "components/dbus/properties/dbus_properties.h"
#include "components/dbus/utils/signature.h"
#include "components/system_media_controls/system_media_controls.h"
#include "dbus/bus.h"
#include "dbus/exported_object.h"

class DbusProperties;

namespace base {
class SequencedTaskRunner;
}

namespace dbus {
class MethodCall;
}  // namespace dbus

namespace system_media_controls {

class SystemMediaControlsObserver;

namespace internal {

COMPONENT_EXPORT(SYSTEM_MEDIA_CONTROLS)
extern const char kMprisAPIServiceNameFormatString[];
COMPONENT_EXPORT(SYSTEM_MEDIA_CONTROLS) extern const char kMprisAPIObjectPath[];
COMPONENT_EXPORT(SYSTEM_MEDIA_CONTROLS)
extern const char kMprisAPIInterfaceName[];
COMPONENT_EXPORT(SYSTEM_MEDIA_CONTROLS)
extern const char kMprisAPIPlayerInterfaceName[];
COMPONENT_EXPORT(SYSTEM_MEDIA_CONTROLS)
extern const char kMprisAPISignalSeeked[];

// A D-Bus service conforming to the MPRIS spec:
// https://specifications.freedesktop.org/mpris-spec/latest/
class COMPONENT_EXPORT(SYSTEM_MEDIA_CONTROLS) SystemMediaControlsLinux
    : public SystemMediaControls {
 public:
  explicit SystemMediaControlsLinux(const std::string& product_name);

  SystemMediaControlsLinux(const SystemMediaControlsLinux&) = delete;
  SystemMediaControlsLinux& operator=(const SystemMediaControlsLinux&) = delete;

  ~SystemMediaControlsLinux() override;

  // Starts the DBus service.
  void StartService();

  // SystemMediaControls implementation.
  void AddObserver(SystemMediaControlsObserver* observer) override;
  void RemoveObserver(SystemMediaControlsObserver* observer) override;
  void SetEnabled(bool enabled) override {}
  void SetIsNextEnabled(bool value) override;
  void SetIsPreviousEnabled(bool value) override;
  void SetIsPlayPauseEnabled(bool value) override;
  void SetIsStopEnabled(bool value) override {}
  void SetIsSeekToEnabled(bool value) override;
  void SetPlaybackStatus(PlaybackStatus value) override;
  void SetID(const std::string* value) override;
  void SetTitle(const std::u16string& value) override;
  void SetArtist(const std::u16string& value) override;
  void SetAlbum(const std::u16string& value) override;
  void SetThumbnail(const SkBitmap& bitmap) override;
  void SetPosition(const media_session::MediaPosition& position) override;
  void ClearPosition() override;
  void ClearThumbnail() override {}
  void ClearMetadata() override;
  void UpdateDisplay() override {}
  bool GetVisibilityForTesting() const override;

  // Returns the generated service name.
  std::string GetServiceName() const;

  // Used for testing with a mock DBus Bus.
  void SetBusForTesting(scoped_refptr<dbus::Bus> bus) { bus_ = bus; }

 private:
  template <dbus_utils::SignatureLiteral Signature>
  void SetProperty(const std::string& property_name,
                   dbus_utils::internal::ParseDBusSignature<Signature> value) {
    properties_->SetProperty<Signature>(kMprisAPIInterfaceName, property_name,
                                        std::move(value), false);
  }

  template <dbus_utils::SignatureLiteral Signature>
  void SetPlayerProperty(
      const std::string& property_name,
      dbus_utils::internal::ParseDBusSignature<Signature> value) {
    properties_->SetProperty<Signature>(kMprisAPIPlayerInterfaceName,
                                        property_name, std::move(value), false);
  }

  void InitializeProperties();
  void InitializeDbusInterface();
  void OnExported(const std::string& interface_name,
                  const std::string& method_name,
                  bool success);
  void OnInitialized(bool success);
  void OnOwnership(const std::string& service_name, bool success);

  // org.mpris.MediaPlayer2.Player interface.
  void Next(dbus::MethodCall* method_call,
            dbus::ExportedObject::ResponseSender response_sender);
  void Previous(dbus::MethodCall* method_call,
                dbus::ExportedObject::ResponseSender response_sender);
  void Pause(dbus::MethodCall* method_call,
             dbus::ExportedObject::ResponseSender response_sender);
  void PlayPause(dbus::MethodCall* method_call,
                 dbus::ExportedObject::ResponseSender response_sender);
  void Stop(dbus::MethodCall* method_call,
            dbus::ExportedObject::ResponseSender response_sender);
  void Play(dbus::MethodCall* method_call,
            dbus::ExportedObject::ResponseSender response_sender);
  void Seek(dbus::MethodCall* method_call,
            dbus::ExportedObject::ResponseSender response_sender);
  void SetPositionMpris(dbus::MethodCall* method_call,
                        dbus::ExportedObject::ResponseSender response_sender);

  // Used for API methods we don't support.
  void DoNothing(dbus::MethodCall* method_call,
                 dbus::ExportedObject::ResponseSender response_sender);

  void UpdateMetadata();

  void ClearTrackId();

  // Updates MPRIS with our current position.
  void UpdatePosition(bool emit_signal);

  void StartPositionUpdateTimer();
  void StopPositionUpdateTimer();

  void OnThumbnailFileWritten(
      std::pair<base::FilePath, base::SequenceBound<base::ScopedTempFile>>
          thumbnail);

  std::optional<media_session::MediaPosition> position_;
  base::RepeatingTimer position_update_timer_;
  bool playing_ = false;

  // Values for the MPRIS metadata dictionary property.
  std::optional<std::string> track_id_;
  std::optional<std::string> title_;
  std::optional<std::vector<std::string>> artist_;
  std::optional<std::string> album_;
  std::optional<int64_t> length_;
  std::optional<std::string> art_url_;

  const std::string product_name_;

  std::unique_ptr<DbusProperties> properties_;

  scoped_refptr<dbus::Bus> bus_;
  raw_ptr<dbus::ExportedObject, DanglingUntriaged> exported_object_;

  // The generated service name given to |bus_| when requesting ownership.
  const std::string service_name_;

  base::RepeatingCallback<void(bool)> barrier_;

  // True if we have started creating the DBus service.
  bool started_ = false;

  // True if we have finished creating the DBus service and received ownership.
  bool service_ready_ = false;

  // A temporary file containing the thumbnail image.
  base::SequenceBound<base::ScopedTempFile> thumbnail_;

  scoped_refptr<base::SequencedTaskRunner> file_task_runner_;

  base::ObserverList<SystemMediaControlsObserver> observers_;

  base::WeakPtrFactory<SystemMediaControlsLinux> weak_factory_{this};
};

}  // namespace internal

}  // namespace system_media_controls

#endif  // COMPONENTS_SYSTEM_MEDIA_CONTROLS_LINUX_SYSTEM_MEDIA_CONTROLS_LINUX_H_
