/*
 * Copyright (C) 2011-2017 Andreas Persson
 * Copyright (C) 2017-2026 Christian Schoenebeck
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with program; see the file COPYING. If not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

#ifdef GTKMM_HEADER_FILE
# include GTKMM_HEADER_FILE(gtkmmconfig.h)
#else
# include <gtkmmconfig.h>
#endif

#if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2
#include <gdkmm/window.h>
#include <gdkmm/general.h>
#include <gdk/gdkcairo.h>
#include "compat.h"

namespace Gdk {
    namespace Cairo {
        void set_source_rgba(const ::Cairo::RefPtr< ::Cairo::Context >& context,
                             const Gdk::RGBA& color) {
#if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 10) || GTKMM_MAJOR_VERSION < 2
            gdk_cairo_set_source_color(context->cobj(),
                                       const_cast<GdkColor*>(color.gobj()));
#else
            set_source_color(context, color);
#endif
        }
    }
}
#endif

#if GTKMM_MAJOR_VERSION >= 3

#ifdef GLIB_HEADER_FILE
# include GLIB_HEADER_FILE(glib.h)
#else
# include <glib.h>
#endif

#include <gtk/gtk.h>
#include <string>
#include <set>
#include "Settings.h"

static std::string systemDefaultTheme;

static bool systemDefaultForDarkMode = false;

static const char* const* allGtkThemes = NULL;

static bool hasDarkThemeName() {
    g_autofree gchar* themeName = NULL;
    g_object_get(gtk_settings_get_default(), "gtk-theme-name", &themeName, NULL);
    if (!themeName)
        return false;
    const std::string name = themeName;
    return name.find("-dark") != std::string::npos;
}

static bool preferDarkTheme() {
    gboolean preferDark = false;
    g_object_get(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", &preferDark, NULL);
    return preferDark;
}

bool isDarkMode() {
    return preferDarkTheme() || hasDarkThemeName();
}

void setDarkModeEnabled(bool b) {
    g_object_set(
        gtk_settings_get_default(), "gtk-application-prefer-dark-theme",
        b, NULL
    );
    Settings::singleton()->uiColors = b ? "dark" : "bright";
}

void setDarkModeAuto() {
    g_object_set(
        gtk_settings_get_default(), "gtk-application-prefer-dark-theme",
        systemDefaultForDarkMode, NULL
    );
    Settings::singleton()->uiColors = "";
}

static std::set<std::string> _findGtkThemesAtPath(std::string path) {
    std::set<std::string> res;
    GDir* dir = g_dir_open(path.c_str(), 0, NULL);
    if (!dir) return res;
    for (const char* name = NULL; (name = g_dir_read_name(dir)); ) {
        g_autofree char* cssFilename =
            g_build_filename(path.c_str(), name, "gtk-3.0", "gtk.css", NULL);
        if (g_file_test(cssFilename, G_FILE_TEST_IS_REGULAR)) {
            res.insert(name);
        }
    }
    g_dir_close(dir);
    return res;
}

static std::set<std::string> findGtkThemesAtPath(std::string path, std::string subdir = "themes") {
    return _findGtkThemesAtPath(path + G_DIR_SEPARATOR_S + subdir);
}

static std::set<std::string> findBuiltinGtkThemes() {
    std::set<std::string> res;
    char** builtinThemes = g_resources_enumerate_children(
        "/org/gtk/libgtk/theme", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL
    );
    for (uint i = 0; builtinThemes[i]; ++i) {
        if (!g_str_has_suffix(builtinThemes[i], "/")) continue;
        std::string name = builtinThemes[i];
        name = name.substr(0, name.length() - 1); // drop trailing slash
        res.insert(name);
    }
    g_strfreev(builtinThemes);
    return res;
}

static std::set<std::string> findAllGtkThemes() {
    std::set<std::string> res = findBuiltinGtkThemes();

    std::set<std::string> themes = findGtkThemesAtPath(g_get_user_data_dir());
    res.insert(themes.begin(), themes.end());

    themes = findGtkThemesAtPath(g_get_home_dir(), ".themes");
    res.insert(themes.begin(), themes.end());

    const char* const* dirs = g_get_system_data_dirs();
    for (uint i = 0; dirs[i]; ++i) {
        themes = findGtkThemesAtPath(dirs[i]);
        res.insert(themes.begin(), themes.end());
    }

    // Yeah, gtk_rc_get_theme_dir() is Gtk2 and it is deprecated, nevertheless
    // it does the job on Gtk3 as well and it's so much PITA to implement this
    // otherwise without this function and working on all kinds of systems.
    char* path = gtk_rc_get_theme_dir();
    if (path) {
        themes = findGtkThemesAtPath(path);
        res.insert(themes.begin(), themes.end());
    }

    return res;
}

const char* const* getAllGtkThemes() {
    if (allGtkThemes) return allGtkThemes;
    std::set<std::string> themes = findAllGtkThemes();
    char** v = new char*[themes.size() + 1];
    uint i = 0;
    for (const std::string& name : themes) {
        v[i++] = strdup(name.c_str());
    }
    v[themes.size()] = NULL;
    allGtkThemes = v;
    return v;
}

Glib::ustring getActiveGtkTheme() {
    const char* name = NULL;
    g_object_get(gtk_settings_get_default(), "gtk-theme-name", &name, NULL);
    return name ? name : "";
}

Glib::ustring getSystemDefaultGtkTheme() {
    return systemDefaultTheme;
}

void setGtkTheme(Glib::ustring name) {
    Settings::singleton()->uiTheme = name;
    if (name.empty())
        name = systemDefaultTheme;
    GtkSettings* s = gtk_settings_get_default();
    g_object_set(s, "gtk-theme-name", name.c_str(), NULL);
}

static void restoreThemeSetting() {
    std::set<std::string> themes = findAllGtkThemes();

    GtkSettings* s = gtk_settings_get_default();

    // get and remember system's default theme name
    if (systemDefaultTheme.empty()) {
        const char* name = NULL;
        g_object_get(s, "gtk-theme-name", &name, NULL);
        if (name)
            systemDefaultTheme = name;
    }

    // restore user's theme setting (if any)
    const Glib::ustring name = Settings::singleton()->uiTheme;
    if (name != "" && themes.count(name)) {
        g_object_set(s, "gtk-theme-name", name.c_str(), NULL);
    }
}

static void restoreDarkModeSetting() {
    // get and remember system's default setting for dark mode
    GtkSettings* s = gtk_settings_get_default();
    g_object_get(s, "gtk-application-prefer-dark-theme",
                 &systemDefaultForDarkMode, NULL);

    // restore user's dark mode setting (if any)
    const Glib::ustring colors = Settings::singleton()->uiColors;
    if (colors == "dark") {
        g_object_set(s, "gtk-application-prefer-dark-theme", true, NULL);
    } else if (colors == "bright") {
        g_object_set(s, "gtk-application-prefer-dark-theme", false, NULL);
    }
}

void restoreAllThemeSettings() {
    restoreThemeSetting();
    restoreDarkModeSetting();
}

#else

const char* const* getAllGtkThemes() {
    static const char* const emptyList[] = {
        NULL // expected NULL termination of list
    };
    return emptyList;
}

Glib::ustring getActiveGtkTheme() {
    return "";
}

Glib::ustring getSystemDefaultGtkTheme() {
    return "";
}

void setGtkTheme(Glib::ustring name) {
}

void setDarkModeEnabled(bool b) {
}

void setDarkModeAuto() {
}

void restoreAllThemeSettings() {
}

#endif
