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

#ifndef UI_LINUX_LINUX_UI_H_
#define UI_LINUX_LINUX_UI_H_

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/component_export.h"
#include "base/containers/flat_map.h"
#include "base/observer_list.h"
#include "base/scoped_observation_traits.h"
#include "build/buildflag.h"
#include "build/chromecast_buildflags.h"
#include "printing/buildflags/buildflags.h"
#include "ui/display/types/display_config.h"

#if BUILDFLAG(ENABLE_PRINTING)
#include "printing/printing_context_linux.h"  // nogncheck
#endif

// The main entrypoint into Linux toolkit specific code. GTK/QT code should only
// be executed behind this interface.

using SkColor = uint32_t;
// TODO(thomasanderson): Remove Profile forward declaration.
class Profile;

namespace aura {
class Window;
}

namespace gfx {
struct FontRenderParams;
class Image;
class Size;
}  // namespace gfx

namespace printing {
class PrintingContextLinux;
class PrintDialogLinuxInterface;
}  // namespace printing

namespace ui {

class CursorThemeManagerObserver;
class DeviceScaleFactorObserver;
class Event;
class LinuxInputMethodContext;
class LinuxInputMethodContextDelegate;
class LinuxUiTheme;
class NativeTheme;
class NavButtonProvider;
class PrimaryPastePrefObserver;
class SelectFileDialog;
class SelectFilePolicy;
class WindowButtonOrderObserver;
class WindowFrameProvider;
enum class TextEditCommand;

class COMPONENT_EXPORT(LINUX_UI) PrintingContextLinuxDelegate {
 public:
  virtual ~PrintingContextLinuxDelegate() = default;

  virtual printing::PrintDialogLinuxInterface* CreatePrintDialog(
      printing::PrintingContextLinux* context) = 0;

  virtual gfx::Size GetPdfPaperSize(printing::PrintingContextLinux* context) = 0;

  static PrintingContextLinuxDelegate* SetInstance(
      PrintingContextLinuxDelegate* delegate);
  static PrintingContextLinuxDelegate* instance();
};

// Adapter class with targets to render like different toolkits. Set by any
// project that wants to do linux desktop native rendering.
class COMPONENT_EXPORT(LINUX_UI) LinuxUi
#if BUILDFLAG(ENABLE_PRINTING)
    : public PrintingContextLinuxDelegate
#endif
 {
 public:
  // Describes the window management actions that could be taken in response to
  // a middle click in the non client area.
  enum class WindowFrameAction {
    kNone,
    kLower,
    kMinimize,
    kToggleMaximize,
    kMenu,
  };

  // The types of clicks that might invoke a WindowFrameAction.
  enum class WindowFrameActionSource {
    kDoubleClick,
    kMiddleClick,
    kRightClick,
  };

  struct FontSettings {
    std::string family;
    int size_pixels = 0;
    // Holds a bitfield of gfx::Font::Style values.
    int style = 0;
    // A standard font weight as used in Pango.  Must be a value in [1, 999].
    int weight = 0;
  };

  LinuxUi(const LinuxUi&) = delete;
  LinuxUi& operator=(const LinuxUi&) = delete;
  virtual ~LinuxUi();

  // Sets the dynamically loaded singleton that draws the desktop native UI.
  // Returns the old instance if any.
  static LinuxUi* SetInstance(LinuxUi* instance);

  // Returns a LinuxUI instance for the toolkit used in the user's desktop
  // environment.
  //
  // Can return NULL, in case no toolkit has been set. (For example, if we're
  // running with the "--ash" flag.)
  static LinuxUi* instance();

  // Registers |observer| to be notified about changes to the device
  // scale factor.
  void AddDeviceScaleFactorObserver(DeviceScaleFactorObserver* observer);

  // Unregisters |observer| from receiving changes to the device scale
  // factor.
  void RemoveDeviceScaleFactorObserver(DeviceScaleFactorObserver* observer);

  // Adds |observer| and makes initial OnCursorThemNameChanged() and/or
  // OnCursorThemeSizeChanged() calls if the respective settings were set.
  void AddCursorThemeObserver(CursorThemeManagerObserver* observer);

  void RemoveCursorThemeObserver(CursorThemeManagerObserver* observer);

  // Adds `observer` and calls if the middle click paste preference changes.
  void AddPrimaryPastePrefObserver(PrimaryPastePrefObserver* observer);

  void RemovePrimaryPastePrefObserver(PrimaryPastePrefObserver* observer);

  // Returns details about the default UI font.
  FontSettings GetDefaultFontDescription();

  // Determines the device scale factor for all screens.
  const display::DisplayConfig& display_config() const {
    return display_config_;
  }

  // Returns true on success.  If false is returned, this instance shouldn't
  // be used and the behavior of all functions is undefined.
  [[nodiscard]] virtual bool Initialize() = 0;

  // Caches the default font render parameters.  This doesn't need to be called
  // explicitly since the first call to get the font settings will implicitly
  // initialize the default front render parameters.
  virtual void InitializeFontSettings() = 0;

  // Returns the icon for a given content type from the icon theme.
  // TODO(davidben): Add an observer for the theme changing, so we can drop the
  // caches.
  virtual gfx::Image GetIconForContentType(const std::string& content_type,
                                           int size,
                                           float scale) const = 0;

  // Returns a map of KeyboardEvent code to KeyboardEvent key values.
  virtual base::flat_map<std::string, std::string> GetKeyboardLayoutMap() = 0;

  // Returns a native file selection dialog.  `listener` is of type
  // SelectFileDialog::Listener.  TODO(thomasanderson): Move
  // SelectFileDialog::Listener to SelectFileDialogListener so that it can be
  // forward declared.
  virtual SelectFileDialog* CreateSelectFileDialog(
      void* listener,
      std::unique_ptr<SelectFilePolicy> policy) const = 0;

  // Returns the prefererd theme name for cursor loading.
  virtual std::string GetCursorThemeName() = 0;

  // Returns the preferred size for cursor bitmaps.  A value of 64 indicates
  // that 64x64 px bitmaps are preferred.
  virtual int GetCursorThemeSize() = 0;

  // Returns a platform specific input method context.
  virtual std::unique_ptr<LinuxInputMethodContext> CreateInputMethodContext(
      LinuxInputMethodContextDelegate* delegate) const = 0;

  // Matches a key event against the users' platform specific key bindings.
  // Returns ui::TextEditCommand::INVALID_COMMAND if the key event doesn't
  // correspond to a predefined key binding.
  //
  // `text_flags` is the current ui::TextInputFlags if available.
  virtual TextEditCommand GetTextEditCommandForEvent(const Event& event,
                                                     int text_flags) = 0;

  // Returns the default font rendering settings.
  virtual gfx::FontRenderParams GetDefaultFontRenderParams() = 0;

  // Indicates if animations are enabled by the toolkit.
  virtual bool AnimationsEnabled() const = 0;

  // Notifies the observer about changes about how window buttons should be
  // laid out.
  virtual void AddWindowButtonOrderObserver(
      WindowButtonOrderObserver* observer) = 0;

  // Removes the observer from the LinuxUI's list.
  virtual void RemoveWindowButtonOrderObserver(
      WindowButtonOrderObserver* observer) = 0;

  // What action we should take when the user clicks on the non-client area.
  // |source| describes the type of click.
  virtual WindowFrameAction GetWindowFrameAction(
      WindowFrameActionSource source) = 0;

  // Whether a middle mouse click should paste the primary clipboard contents.
  virtual bool PrimaryPasteEnabled() const = 0;
  // Returns the command line flags that should be copied to subprocesses
  // to have the same toolkit and version as this process.
  virtual std::vector<std::string> GetCmdLineFlagsForCopy() const = 0;

 protected:
  struct CmdLineArgs {
    CmdLineArgs();
    CmdLineArgs(CmdLineArgs&&);
    CmdLineArgs& operator=(CmdLineArgs&&);
    ~CmdLineArgs();

    // `argc` is modified by toolkits, so store it explicitly.
    int argc = 0;

    // Contains C-strings that point into `args`.  `argv.size()` >= `argc`.
    std::vector<char*> argv;

    // `argv` concatenated with NUL characters.
    std::vector<char> args;
  };

  LinuxUi();

  static CmdLineArgs CopyCmdLine(const base::CommandLine& command_line);

  base::ObserverList<DeviceScaleFactorObserver>::Unchecked&
  device_scale_factor_observer_list() {
    return device_scale_factor_observer_list_;
  }

  base::ObserverList<CursorThemeManagerObserver>& cursor_theme_observers() {
    return cursor_theme_observer_list_;
  }

  base::ObserverList<PrimaryPastePrefObserver>& primary_paste_observers() {
    return primary_paste_observer_list_;
  }

  display::DisplayConfig& display_config() { return display_config_; }

  void set_default_font_settings(
      const std::optional<FontSettings>& default_font_settings) {
    default_font_settings_ = default_font_settings;
  }

 private:
  // Objects to notify when the device scale factor changes.
  base::ObserverList<DeviceScaleFactorObserver>::Unchecked
      device_scale_factor_observer_list_;

  // Objects to notify when the cursor theme or size changes.
  base::ObserverList<CursorThemeManagerObserver> cursor_theme_observer_list_;

  // Objects to notify when the middle click paste preference changes.
  base::ObserverList<PrimaryPastePrefObserver> primary_paste_observer_list_;

  display::DisplayConfig display_config_;

  std::optional<FontSettings> default_font_settings_;
};

class COMPONENT_EXPORT(LINUX_UI) LinuxUiTheme {
 public:
  LinuxUiTheme(const LinuxUiTheme&) = delete;
  LinuxUiTheme& operator=(const LinuxUiTheme&) = delete;
  virtual ~LinuxUiTheme();

  // Returns the LinuxUi instance for the given window, or the default instance
  // if the window is nullptr.
  static LinuxUiTheme* GetForWindow(aura::Window* window);

  // Returns the LinuxUi instance for the given profile, or the default instance
  // if the profile is nullptr.
  static LinuxUiTheme* GetForProfile(Profile* profile);

  // Returns the native theme for this toolkit.
  virtual ui::NativeTheme* GetNativeTheme() const = 0;

  virtual bool GetColor(int id,
                        SkColor* color,
                        bool use_custom_frame) const = 0;
  virtual bool GetDisplayProperty(int id, int* result) const = 0;

  // Returns the preferences that we pass to Blink.
  virtual void GetFocusRingColor(SkColor* color) const = 0;
  virtual void GetActiveSelectionBgColor(SkColor* color) const = 0;
  virtual void GetActiveSelectionFgColor(SkColor* color) const = 0;
  virtual void GetInactiveSelectionBgColor(SkColor* color) const = 0;
  virtual void GetInactiveSelectionFgColor(SkColor* color) const = 0;

  // Only used on GTK to indicate if the dark GTK theme variant is
  // preferred.
  virtual bool PreferDarkTheme() const = 0;

  // Override the toolkit's dark mode preference.  Used when the dark mode
  // setting is provided by org.freedesktop.appearance instead of the toolkit.
  virtual void SetDarkTheme(bool dark) = 0;

  // Override the toolkit's accent color.
  virtual void SetAccentColor(std::optional<SkColor> accent_color) = 0;

  // Returns a new NavButtonProvider, or nullptr if the underlying
  // toolkit does not support drawing client-side navigation buttons.
  virtual std::unique_ptr<NavButtonProvider> CreateNavButtonProvider() = 0;

  // Returns a WindowFrameProvider, or nullptr if the underlying toolkit does
  // not support drawing client-side window decorations. |solid_frame| indicates
  // if transparency is unsupported and the frame should be rendered opaque.
  // The returned object is not owned by the caller and will remain alive until
  // the process ends.
  virtual WindowFrameProvider* GetWindowFrameProvider(bool solid_frame,
                                                      bool tiled,
                                                      bool maximized) = 0;

 protected:
  LinuxUiTheme();
};

// This is used internally by LinuxUi implementations and linux_ui_factory to
// allow converting a LinuxUi to a LinuxUiTheme.  Users should not use (and have
// no way of obtaining) an instance of this class.
class LinuxUiAndTheme : public LinuxUi, public LinuxUiTheme {
 public:
  ~LinuxUiAndTheme() override = default;
};

}  // namespace ui

namespace base {

template <>
struct ScopedObservationTraits<ui::LinuxUi, ui::CursorThemeManagerObserver> {
  static void AddObserver(ui::LinuxUi* source,
                          ui::CursorThemeManagerObserver* observer) {
    source->AddCursorThemeObserver(observer);
  }
  static void RemoveObserver(ui::LinuxUi* source,
                             ui::CursorThemeManagerObserver* observer) {
    source->RemoveCursorThemeObserver(observer);
  }
};

template <>
struct ScopedObservationTraits<ui::LinuxUi, ui::DeviceScaleFactorObserver> {
  static void AddObserver(ui::LinuxUi* source,
                          ui::DeviceScaleFactorObserver* observer) {
    source->AddDeviceScaleFactorObserver(observer);
  }
  static void RemoveObserver(ui::LinuxUi* source,
                             ui::DeviceScaleFactorObserver* observer) {
    source->RemoveDeviceScaleFactorObserver(observer);
  }
};

template <>
struct ScopedObservationTraits<ui::LinuxUi, ui::WindowButtonOrderObserver> {
  static void AddObserver(ui::LinuxUi* source,
                          ui::WindowButtonOrderObserver* observer) {
    source->AddWindowButtonOrderObserver(observer);
  }
  static void RemoveObserver(ui::LinuxUi* source,
                             ui::WindowButtonOrderObserver* observer) {
    source->RemoveWindowButtonOrderObserver(observer);
  }
};

template <>
struct ScopedObservationTraits<ui::LinuxUi, ui::PrimaryPastePrefObserver> {
  static void AddObserver(ui::LinuxUi* source,
                          ui::PrimaryPastePrefObserver* observer) {
    source->AddPrimaryPastePrefObserver(observer);
  }
  static void RemoveObserver(ui::LinuxUi* source,
                             ui::PrimaryPastePrefObserver* observer) {
    source->RemovePrimaryPastePrefObserver(observer);
  }
};

}  // namespace base

#endif  // UI_LINUX_LINUX_UI_H_