/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef _nsLookAndFeel
#define _nsLookAndFeel

#include "X11UndefineNone.h"
#include "nsXPLookAndFeel.h"
#include "gfxFont.h"

struct _GtkStyle;
typedef struct _GDBusProxy GDBusProxy;
typedef struct _GtkCssProvider GtkCssProvider;
typedef struct _GFile GFile;
typedef struct _GFileMonitor GFileMonitor;
typedef struct _GVariant GVariant;

namespace mozilla {
enum class StyleGtkThemeFamily : uint8_t;

namespace widget {

enum class NativeChangeKind : uint8_t {
  None = 0,
  GtkTheme = 1 << 0,
  OtherSettings = 1 << 1,
  All = GtkTheme | OtherSettings,
};

MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(NativeChangeKind)

}  // namespace widget
}  // namespace mozilla

class nsLookAndFeel final : public nsXPLookAndFeel {
  using NativeChangeKind = mozilla::widget::NativeChangeKind;

 public:
  nsLookAndFeel();
  virtual ~nsLookAndFeel();

  void RecordChange(NativeChangeKind aKind) { mPendingChanges |= aKind; }
  void NativeInit() final;
  nsresult NativeGetInt(IntID aID, int32_t& aResult) override;
  nsresult NativeGetFloat(FloatID aID, float& aResult) override;
  nsresult NativeGetColor(ColorID, ColorScheme, nscolor& aResult) override;
  bool NativeGetFont(FontID aID, nsString& aFontName,
                     gfxFontStyle& aFontStyle) override;

  char16_t GetPasswordCharacterImpl() override;
  bool GetEchoPasswordImpl() override;

  void RefreshImpl() override {
    // When calling Refresh(), we don't need to reload all our GTK theme info,
    // but we might need to change our effective theme.
    RecordChange(NativeChangeKind::OtherSettings);
  }

  bool GetDefaultDrawInTitlebar() override;

  nsXPLookAndFeel::TitlebarAction GetTitlebarAction(
      TitlebarEvent aEvent) override;

  void GetThemeInfo(nsACString&) override;

  nsresult GetKeyboardLayoutImpl(nsACString& aLayout) override;

  static const nscolor kBlack = NS_RGB(0, 0, 0);
  static const nscolor kWhite = NS_RGB(255, 255, 255);
#ifdef MOZ_ENABLE_DBUS
  // Returns whether any setting changed.
  bool RecomputeDBusSettings();
  // Returns whether the setting really changed.
  bool RecomputeDBusAppearanceSetting(const nsACString& aKey, GVariant* aValue);
#endif

  struct ColorPair {
    nscolor mBg = kWhite;
    nscolor mFg = kBlack;

    bool operator==(const ColorPair& aOther) const {
      return mBg == aOther.mBg && mFg == aOther.mFg;
    }
    bool operator!=(const ColorPair& aOther) const {
      return !(*this == aOther);
    }
  };

  struct ButtonColors : ColorPair {
    nscolor mBorder = kBlack;

    bool operator==(const ButtonColors& aOther) const {
      return mBg == aOther.mBg && mFg == aOther.mFg &&
             mBorder == aOther.mBorder;
    }
    bool operator!=(const ButtonColors& aOther) const {
      return !(*this == aOther);
    }
  };

  using ThemeFamily = mozilla::StyleGtkThemeFamily;

 protected:
  void RecordLookAndFeelSpecificTelemetry() override;
  static bool ShouldHonorThemeScrollbarColors();
  mozilla::Maybe<ColorScheme> ComputeColorSchemeSetting();

#ifdef MOZ_ENABLE_DBUS
  void WatchDBus();
  void UnwatchDBus();
#endif

  // We use up to two themes (one light, one dark), which might have different
  // sets of fonts and colors.
  struct PerThemeData {
    nsCString mName;
    bool mIsDark = false;
    bool mHighContrast = false;
    bool mPreferDarkTheme = false;
    bool mIsDefaultThemeFallback = false;

    ThemeFamily mFamily{0};

    // Cached fonts
    nsString mDefaultFontName;
    nsString mButtonFontName;
    nsString mFieldFontName;
    nsString mMenuFontName;
    gfxFontStyle mDefaultFontStyle;
    gfxFontStyle mButtonFontStyle;
    gfxFontStyle mFieldFontStyle;
    gfxFontStyle mMenuFontStyle;

    // Cached colors
    nscolor mGrayText = kBlack;
    ColorPair mInfo;
    ColorPair mMenu;
    ColorPair mMenuHover;
    ColorPair mHeaderBar;
    ColorPair mHeaderBarInactive;
    ButtonColors mButton;
    ButtonColors mButtonHover;
    ButtonColors mButtonActive;
    ButtonColors mButtonDisabled;
    nscolor mFrameBorder = kBlack;
    nscolor mNativeHyperLinkText = kBlack;
    nscolor mNativeVisitedHyperLinkText = kBlack;
    ColorPair mField;
    ColorPair mWindow;
    ColorPair mDialog;
    ColorPair mSidebar;
    nscolor mSidebarBorder = kBlack;

    nscolor mMozWindowActiveBorder = kBlack;
    nscolor mMozWindowInactiveBorder = kBlack;

    ColorPair mCellHighlight;
    ColorPair mSelectedText;
    ColorPair mAccent;
    ColorPair mSelectedItem;

    ColorPair mMozColHeader;
    ColorPair mMozColHeaderHover;
    ColorPair mMozColHeaderActive;

    ColorPair mTitlebar;
    ColorPair mTitlebarInactive;

    nscolor mThemedScrollbar = kWhite;
    nscolor mThemedScrollbarThumb = kBlack;
    nscolor mThemedScrollbarThumbHover = kBlack;
    nscolor mThemedScrollbarThumbActive = kBlack;

    float mCaretRatio = 0.0f;
    int32_t mTitlebarRadius = 0;
    int32_t mTooltipRadius = 0;
    char16_t mInvisibleCharacter = 0;
    bool mMenuSupportsDrag = false;

    void Init();
    nsresult GetColor(ColorID, nscolor&) const;
    bool GetFont(FontID, nsString& aFontName, gfxFontStyle&,
                 float aTextScaleFactor) const;
    void InitCellHighlightColors();
    void RestoreColorOverrides();
    void ApplyColorOverride(nscolor* aMember, nscolor aNewColor);
    void ApplyColorOverride(ColorPair* aMember, const ColorPair& aNewPair);

    struct ColorOverride {
      uint32_t mByteOffset;
      nscolor mOriginalColor;
    };
    nsTArray<ColorOverride> mOverrides;
  };

  PerThemeData mSystemTheme;

  // If the system theme is light, a dark theme. Otherwise, a light theme. The
  // alternative theme to the current one is preferred, but otherwise we fall
  // back to Adwaita / Adwaita Dark, respectively.
  PerThemeData mAltTheme;

  const PerThemeData& LightTheme() const {
    return mSystemTheme.mIsDark ? mAltTheme : mSystemTheme;
  }

  const PerThemeData& DarkTheme() const {
    return mSystemTheme.mIsDark ? mSystemTheme : mAltTheme;
  }

  const PerThemeData& EffectiveTheme() const {
    return mSystemThemeOverridden ? mAltTheme : mSystemTheme;
  }

  uint32_t mDBusID = 0;
  RefPtr<GFile> mKdeColors;
  RefPtr<GFileMonitor> mKdeColorsMonitor;

  mozilla::Maybe<ColorScheme> mColorSchemePreference;
  RefPtr<GDBusProxy> mDBusSettingsProxy;
  // DBus settings from:
  // https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.Settings.xml
  struct DBusSettings {
    mozilla::Maybe<ColorScheme> mColorScheme;
    bool mPrefersContrast = false;
    // Transparent means no accent-color. Note that the real accent color cannot
    // have transparency.
    ColorPair mAccentColor{NS_TRANSPARENT, NS_TRANSPARENT};

    bool HasAccentColor() const { return NS_GET_A(mAccentColor.mBg); }
  } mDBusSettings;
  int32_t mCaretBlinkTime = 0;
  int32_t mCaretBlinkCount = -1;
  bool mCSDMaximizeButton = false;
  bool mCSDMinimizeButton = false;
  bool mCSDCloseButton = false;
  bool mCSDReversedPlacement = false;
  bool mPrefersReducedMotion = false;
  bool mSystemThemeOverridden = false;
  NativeChangeKind mPendingChanges = NativeChangeKind::All;
  int32_t mCSDMaximizeButtonPosition = 0;
  int32_t mCSDMinimizeButtonPosition = 0;
  int32_t mCSDCloseButtonPosition = 0;
  TitlebarAction mDoubleClickAction = TitlebarAction::None;
  TitlebarAction mMiddleClickAction = TitlebarAction::None;
  float mTextScaleFactor = 1.0f;

  int32_t mRoundedCornerProviderRadius = 0;
  RefPtr<GtkCssProvider> mRoundedCornerProvider;
  void UpdateRoundedBottomCornerStyles();

  void ClearRoundedCornerProvider();

  void EnsureInit() {
    if (mPendingChanges == NativeChangeKind::None) {
      return;
    }
    Initialize();
  }

  void Initialize();

  void RestoreSystemTheme();
  void InitializeGlobalSettings();
  // Returns whether we found an alternative theme.
  bool ConfigureAltTheme();
  void ConfigureAndInitializeAltTheme();
  void ConfigureFinalEffectiveTheme();
  void MaybeApplyColorOverrides();
};

#endif
