// Aseprite
// Copyright (C) 2020-2025  Igara Studio S.A.
// Copyright (C) 2001-2017  David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.

#ifndef APP_UI_SKIN_SKIN_THEME_H_INCLUDED
#define APP_UI_SKIN_SKIN_THEME_H_INCLUDED
#pragma once

#include "app/fonts/font_info.h"
#include "app/fonts/fonts.h"
#include "app/ui/skin/skin_part.h"
#include "gfx/color.h"
#include "gfx/fwd.h"
#include "ui/cursor.h"
#include "ui/cursor_type.h"
#include "ui/manager.h"
#include "ui/scale.h"
#include "ui/theme.h"

#include "theme.xml.h"

#include <array>
#include <map>
#include <string>

namespace ui {
class Entry;
class Graphics;
} // namespace ui

namespace app { namespace skin {

class ThemeFont {
public:
  ThemeFont() {}
  ThemeFont(text::FontRef font, bool mnemonics) : m_font(font), m_mnemonics(mnemonics) {}
  text::FontRef font() { return m_font; }
  bool mnemonics() { return m_mnemonics; }

private:
  text::FontRef m_font;
  bool m_mnemonics;
};

// This is the GUI theme used by Aseprite (which use images from
// data/skins directory).
class SkinTheme final : public ui::Theme,
                        public app::gen::ThemeFile<SkinTheme> {
public:
  static const char* kThemesFolderName;

  static SkinTheme* instance();
  static SkinTheme* get(const ui::Widget* widget);

  SkinTheme();
  ~SkinTheme();

  const std::string& path() { return m_path; }
  int preferredScreenScaling() const { return m_preferredScreenScaling; }
  int preferredUIScaling() const { return m_preferredUIScaling; }

  const FontInfo& getDefaultFontInfo() const { return m_defaultFontInfo; }
  const FontInfo& getMiniFontInfo() const { return m_miniFontInfo; }
  text::FontRef getDefaultFont() const override { return m_defaultFont; }
  text::FontRef getWidgetFont(const ui::Widget* widget) const override;
  text::FontRef getMiniFont() const { return m_miniFont; }
  text::FontRef getUnscaledFont(const text::FontRef& font) const
  {
    auto it = m_unscaledFonts.find(font.get());
    if (it != m_unscaledFonts.end())
      return it->second;
    else
      return font;
  }

  ui::Cursor* getStandardCursor(ui::CursorType type) override;
  void initWidget(ui::Widget* widget) override;
  void getWindowMask(ui::Widget* widget, gfx::Region& region) override;
  int getScrollbarSize() override;
  gfx::Size getCaretSize(ui::Widget* widget) override;
  TextColors getTextColors(ui::Widget* widget) override;
  ui::Style* viewBaseStyle() override { return styles.viewBase(); }

  void paintEntry(ui::PaintEvent& ev) override;
  void paintListBox(ui::PaintEvent& ev) override;
  void paintMenu(ui::PaintEvent& ev) override;
  void paintMenuItem(ui::PaintEvent& ev) override;
  void paintSlider(ui::PaintEvent& ev) override;
  void paintComboBoxEntry(ui::PaintEvent& ev) override;
  void paintTextBox(ui::PaintEvent& ev) override;
  void paintViewViewport(ui::PaintEvent& ev) override;

  SkinPartPtr getToolPart(const char* toolId) const;
  os::Surface* getToolIcon(const char* toolId) const;

  // Helper functions to draw bounds/hlines with sheet parts
  void drawRect(ui::Graphics* g,
                const gfx::Rect& rc,
                os::Surface* nw,
                os::Surface* n,
                os::Surface* ne,
                os::Surface* e,
                os::Surface* se,
                os::Surface* s,
                os::Surface* sw,
                os::Surface* w);
  void drawRect(ui::Graphics* g,
                const gfx::Rect& rc,
                SkinPart* skinPart,
                const bool drawCenter = true);
  void drawRectUsingUnscaledSheet(ui::Graphics* g,
                                  const gfx::Rect& rc,
                                  SkinPart* skinPart,
                                  const bool drawCenter = true);
  void drawRect2(ui::Graphics* g, const gfx::Rect& rc, int x_mid, SkinPart* nw1, SkinPart* nw2);
  void drawHline(ui::Graphics* g, const gfx::Rect& rc, SkinPart* skinPart);
  void drawVline(ui::Graphics* g, const gfx::Rect& rc, SkinPart* skinPart);
  void paintProgressBar(ui::Graphics* g, const gfx::Rect& rc, double progress);

  ui::Style* getStyleById(const std::string& id) const
  {
    auto it = m_styles.find(id);
    if (it != m_styles.end())
      return it->second;
    else
      return EmptyStyle();
  }

  SkinPartPtr getPartById(const std::string& id) const
  {
    auto it = m_parts_by_id.find(id);
    if (it != m_parts_by_id.end())
      return it->second;
    else
      return SkinPartPtr(nullptr);
  }

  SkinPartPtr getUnscaledPartById(const std::string& id) const
  {
    auto it = m_unscaledParts_by_id.find(id);
    if (it != m_unscaledParts_by_id.end())
      return it->second;
    else
      return SkinPartPtr(nullptr);
  }

  ui::Cursor* getCursorById(const std::string& id) const
  {
    auto it = m_cursors.find(id);
    if (it != m_cursors.end())
      return it->second;
    else
      return nullptr;
  }

  int getDimensionById(const std::string& id) const
  {
    auto it = m_dimensions_by_id.find(id);
    if (it != m_dimensions_by_id.end())
      return it->second * ui::guiscale();
    else
      return 0;
  }

  gfx::Color getColorById(const std::string& id) const
  {
    auto it = m_colors_by_id.find(id);
    if (it != m_colors_by_id.end())
      return it->second;
    else
      return gfx::ColorNone;
  }

  void drawEntryCaret(ui::Graphics* g, ui::Entry* widget, int x, int y);

protected:
  void onRegenerateTheme() override;

private:
  class BackwardCompatibility;

  void loadFontData();
  void loadAll(const std::string& themeId, BackwardCompatibility* backward = nullptr);
  void loadSheet();
  void loadXml(BackwardCompatibility* backward);

  os::SurfaceRef sliceSheet(os::SurfaceRef sur, const gfx::Rect& bounds);
  os::SurfaceRef sliceUnscaledSheet(os::SurfaceRef sur, const gfx::Rect& bounds);
  gfx::Color getWidgetBgColor(ui::Widget* widget);
  void drawText(ui::Graphics* g,
                const char* t,
                const gfx::Color fgColor,
                const gfx::Color bgColor,
                const ui::Widget* widget,
                const gfx::Rect& rc,
                const int textAlign,
                const int mnemonic);
  void drawEntryText(ui::Graphics* g, ui::Entry* widget);

  std::string findThemePath(const std::string& themeId) const;

  Fonts m_fonts;
  std::string m_path;
  os::SurfaceRef m_sheet;
  // Contains the sheet surface as is, without any scale.
  os::SurfaceRef m_unscaledSheet;
  std::map<std::string, SkinPartPtr> m_parts_by_id;
  // Stores the same SkinParts as m_parts_by_id but unscaled, using the same keys.
  std::map<std::string, SkinPartPtr> m_unscaledParts_by_id;
  std::map<std::string, gfx::Color> m_colors_by_id;
  std::map<std::string, int> m_dimensions_by_id;
  std::map<std::string, ui::Cursor*> m_cursors;
  std::array<ui::Cursor*, ui::kCursorTypes> m_standardCursors;
  std::map<std::string, ui::Style*> m_styles;
  std::map<std::string, ThemeFont> m_themeFonts;
  // Stores the unscaled font version of the Font pointer used as a key.
  std::map<text::Font*, text::FontRef> m_unscaledFonts;
  FontInfo m_defaultFontInfo;
  FontInfo m_miniFontInfo;
  text::FontRef m_defaultFont;
  text::FontRef m_miniFont;
  int m_preferredScreenScaling;
  int m_preferredUIScaling;
};

}} // namespace app::skin

#endif
