// 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 CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_ICON_LINUX_DBUS_H_
#define CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_ICON_LINUX_DBUS_H_

#include <map>
#include <string>

#include "base/files/file_path.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/ui/views/status_icons/concat_menu_model.h"
#include "components/dbus/utils/call_method.h"
#include "components/dbus/utils/export_method.h"
#include "components/dbus/utils/variant.h"
#include "dbus/bus.h"
#include "dbus/exported_object.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"
#include "ui/linux/status_icon_linux.h"
#include "ui/menus/simple_menu_model.h"
#include "ui/views/controls/menu/menu_runner.h"

namespace gfx {
class ImageSkia;
}  // namespace gfx

class DbusMenu;

// A status icon following the StatusNotifierItem specification.
// https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierItem/
class StatusIconLinuxDbus : public ui::StatusIconLinux,
                            public ui::SimpleMenuModel::Delegate,
                            public base::RefCounted<StatusIconLinuxDbus> {
 public:
  StatusIconLinuxDbus();
  explicit StatusIconLinuxDbus(scoped_refptr<dbus::Bus> bus);

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

  // StatusIcon:
  void SetImage(const gfx::ImageSkia& image) override;
  void SetIcon(const gfx::VectorIcon& icon) override;
  void SetToolTip(const std::u16string& tool_tip) override;
  void UpdatePlatformContextMenu(ui::MenuModel* model) override;
  void RefreshPlatformContextMenu() override;

  // ui::SimpleMenuModel::Delegate:
  void ExecuteCommand(int command_id, int event_flags) override;

 private:
  friend class base::RefCounted<StatusIconLinuxDbus>;

  ~StatusIconLinuxDbus() override;

  // Step 0: send the request to verify that the StatusNotifierWatcher service
  // is owned.
  void CheckStatusNotifierWatcherHasOwner();

  // Step 1: verify that the StatusNotifierWatcher service is owned.
  void OnNameHasOwnerResponse(dbus_utils::CallMethodResultSig<"b"> response);

  // Step 2: verify with the StatusNotifierWatcher that a StatusNotifierHost is
  // registered.
  void OnHostRegisteredResponse(dbus_utils::CallMethodResultSig<"v"> response);

  void OnInitialized(bool success);
  void OnOwnershipAcquired(const std::string& service_name, bool success);
  void RegisterStatusNotifierItem();

  // Step 5: register the StatusNotifierItem with the StatusNotifierWatcher.
  void OnRegistered(dbus_utils::CallMethodResultSig<""> response);

  // Called when the name owner of StatusNotifierWatcher has changed, which
  // can happen when lock/unlock screen.
  void OnNameOwnerChangedReceived(const std::string& old_owner,
                                  const std::string& new_owner);

  // DBus methods.
  // Action       -> KDE behavior:
  // Left-click   -> Activate
  // Right-click  -> ContextMenu
  // Scroll       -> Scroll
  // Middle-click -> SecondaryActivate
  dbus_utils::ExportMethodResult<> OnActivate(int32_t x, int32_t y);
  dbus_utils::ExportMethodResult<> OnContextMenu(int32_t x, int32_t y);
  dbus_utils::ExportMethodResult<> OnScroll(int32_t delta,
                                            std::string orientation);
  dbus_utils::ExportMethodResult<> OnSecondaryActivate(int32_t x, int32_t y);

  void OnGetProperty(dbus::MethodCall* method_call,
                     dbus::ExportedObject::ResponseSender response_sender);
  void OnGetAllProperties(dbus::MethodCall* method_call,
                          dbus::ExportedObject::ResponseSender response_sender);

  void UpdateMenuImpl(ui::MenuModel* model, bool send_signal);

  void SetImageImpl(const gfx::ImageSkia& image, bool send_signals);

  void OnIconFileWritten(const base::FilePath& icon_file);

  void CleanupIconFile();

  template <dbus_utils::SignatureLiteral Signature>
  void SetProperty(const std::string& name,
                   dbus_utils::internal::ParseDBusSignature<Signature> value,
                   bool emit_signal = true) {
    auto new_value = dbus_utils::Variant::Wrap<Signature>(std::move(value));
    auto it = properties_.find(name);
    bool value_changed = false;
    if (it == properties_.end()) {
      properties_.emplace(name, std::move(new_value));
      value_changed = true;
    } else if (it->second != new_value) {
      it->second = std::move(new_value);
      value_changed = true;
    }
    if (emit_signal && value_changed) {
      PropertyUpdated(name);
    }
  }

  void PropertyUpdated(const std::string& property_name);

  scoped_refptr<dbus::Bus> bus_;

  int service_id_ = 0;
  std::string service_name_;
  raw_ptr<dbus::ObjectProxy, DanglingUntriaged> watcher_ = nullptr;
  raw_ptr<dbus::ExportedObject, DanglingUntriaged> item_ = nullptr;

  // A map of property names (e.g. "Category", "Id") to their values.
  std::map<std::string, dbus_utils::Variant> properties_;

  // A menu that contains the click action (if there is a click action) and a
  // separator (if there's a click action and delegate_->GetMenuModel() is
  // non-empty).
  std::unique_ptr<ui::SimpleMenuModel> click_action_menu_;
  // An empty menu for use in |concat_menu_| if delegate_->GetMenuModel() is
  // null.
  std::unique_ptr<ui::SimpleMenuModel> empty_menu_;
  // A concatenation of |click_action_menu_| and either
  // delegate_->GetMenuModel() or |empty_menu_| if the delegate's menu is null.
  std::unique_ptr<ConcatMenuModel> concat_menu_;

  // Must be destroyed before the menus above, as it holds pointers to them.
  std::unique_ptr<DbusMenu> menu_;

  // Used when the server doesn't support DBus menus and requests for us to use
  // our own menu.
  std::unique_ptr<views::MenuRunner> menu_runner_;

  const bool should_write_icon_to_file_;
  const scoped_refptr<base::SequencedTaskRunner> icon_task_runner_;
  size_t icon_file_id_ = 0;
  base::FilePath icon_file_;

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

#endif  // CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_ICON_LINUX_DBUS_H_
