/*
 * Copyright 2014 Canonical Ltd.
 * Copyright 2023-2024 Robert Tari
 *
 * 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; version 3.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *   Charles Kerr <charles.kerr@canonical.com>
 *   Robert Tari <robert@tari.in>
 */

#include <src/service.h>
#include <glib/gi18n.h>

#ifdef COLOR_TEMP_ENABLED
    #include <geoclue.h>

    #ifdef RDA_ENABLED
        #include <rda/rda.h>
    #endif

    #include <X11/Xlib.h>
    #include <X11/extensions/Xrandr.h>
#endif

extern "C"
{
    #include <ayatana/common/utils.h>

    #ifdef COLOR_TEMP_ENABLED
        #include <act/act.h>
        #include <pwd.h>
        #include "solar.h"
    #endif
}

#ifdef COLOR_TEMP_ENABLED
    #define GREETER_BUS_NAME "org.ayatana.greeter"
    #define GREETER_BUS_PATH "/org/ayatana/greeter"

    typedef struct
    {
       guint nTempLow;
       guint nTempHigh;
       const gchar *sName;
    } TempProfile;

    TempProfile m_lTempProfiles[] =
    {
        {0, 0, N_("Manual")},
        {4500, 6500, N_("Adaptive (Colder)")},
        {3627, 4913, N_("Adaptive")},
        {3058, 4913, N_("Adaptive (Warmer)")},
        {0, 0, NULL}
    };
#endif

class DisplayIndicator::Impl
{
public:

  Impl()
  {
#ifdef COLOR_TEMP_ENABLED
    const gchar *sTest = g_getenv ("TEST_NAME");
    this->bTest = (sTest != NULL && g_str_equal (sTest, "rotation-lock-test"));

    if (!this->bTest)
    {
        // Check if we are on Wayland
        const gchar *sWayland = g_getenv ("WAYLAND_DISPLAY");
        this->bXsctUnsupported = (sWayland != NULL);
        //~Check if we are on Wayland

        // Check if we are in a virtual environment
        if (!this->bXsctUnsupported)
        {
            Display *pDisplay = XOpenDisplay (NULL);

            if (!pDisplay)
            {
                g_warning ("Panic: Failed to open X display while checking for virtual environment");
            }
            else
            {
                guint nScreen = DefaultScreen (pDisplay);
                Window pWindow = RootWindow (pDisplay, nScreen);
                XRRScreenResources *pResources = XRRGetScreenResources (pDisplay, pWindow);

                if (!pResources)
                {
                    g_warning ("Panic: Failed to get screen resources while checking for virtual environment");
                    XCloseDisplay (pDisplay);
                }
                else
                {
                    RROutput nOutputPrimary = XRRGetOutputPrimary (pDisplay, pWindow);
                    XRROutputInfo *pOutputInfo = XRRGetOutputInfo (pDisplay, pResources, nOutputPrimary);
                    GRegex *pRegex = NULL;
                    GError *pError = NULL;

                    #if GLIB_CHECK_VERSION(2, 73, 0)
                        pRegex = g_regex_new (".*virtual.*", G_REGEX_CASELESS, G_REGEX_MATCH_DEFAULT, &pError);
                    #else
                        pRegex = g_regex_new (".*virtual.*", G_REGEX_CASELESS, (GRegexMatchFlags) 0, &pError);
                    #endif

                    if (!pError)
                    {
                        #if GLIB_CHECK_VERSION(2, 73, 0)
                            gboolean bMatch = g_regex_match (pRegex, pOutputInfo->name, G_REGEX_MATCH_DEFAULT, NULL);
                        #else
                            gboolean bMatch = g_regex_match (pRegex, pOutputInfo->name, (GRegexMatchFlags) 0, NULL);
                        #endif

                        if (bMatch)
                        {
                            this->bXsctUnsupported = TRUE;
                        }

                        g_regex_unref (pRegex);
                    }
                    else
                    {
                        g_warning ("PANIC: Failed to compile regex: %s", pError->message);
                        g_error_free (pError);
                    }

                    XRRFreeOutputInfo (pOutputInfo);
                    XRRFreeScreenResources (pResources);
                    XCloseDisplay (pDisplay);

                    #ifdef RDA_ENABLED
                        gboolean bRemote = rda_session_is_remote ();
                        this->bXsctUnsupported = this->bXsctUnsupported || bRemote;
                    #endif
                }
            }
        }
        //~Check if we are in a virtual environment
    }
#endif
    const char *sUserName = g_get_user_name();
    this->bGreeter = g_str_equal (sUserName, "lightdm");
    GSettingsSchemaSource *pSource = g_settings_schema_source_get_default();

    if (pSource != NULL)
    {
        if (ayatana_common_utils_is_lomiri()) {

            GSettingsSchema *pSchema = g_settings_schema_source_lookup(pSource, "com.lomiri.touch.system", TRUE);

            if (pSchema != NULL)
            {
                g_settings_schema_unref(pSchema);
                m_settings = g_settings_new("com.lomiri.touch.system");
            }
            else
            {
                g_error ("Panic: No com.lomiri.touch.system schema could be found");
            }
        }
        else {

            GSettingsSchema *pSchema = g_settings_schema_source_lookup(pSource, "org.ayatana.indicator.display", TRUE);

            if (pSchema != NULL)
            {
                g_settings_schema_unref(pSchema);
                m_settings = g_settings_new("org.ayatana.indicator.display");
            }
            else
            {
                g_error ("Panic: No org.ayatana.indicator.display schema could be found");
            }

#ifdef COLOR_TEMP_ENABLED
            this->pAccountsServiceConnection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);

            if (!this->bGreeter)
            {
                const gchar *sSchema = NULL;
                const gchar *sCursorSchema = NULL;
                const gchar *sMetacitySchema = NULL;

                if (this->bTest)
                {
                    sSchema = "org.ayatana.indicator.display";
                    sCursorSchema = "org.ayatana.indicator.display";
                    sMetacitySchema = "org.ayatana.indicator.display";
                }
                else
                {
                    if (ayatana_common_utils_is_mate ())
                    {
                        sSchema = "org.mate.interface";
                        sCursorSchema = "org.mate.peripherals-mouse";
                        sMetacitySchema = "org.mate.Marco.general";
                    }
                    else
                    {
                        sSchema = "org.gnome.desktop.interface";
                        sCursorSchema = "org.gnome.desktop.interface";
                        sMetacitySchema = "org.gnome.desktop.wm.preferences";
                    }
                }

                pSchema = g_settings_schema_source_lookup (pSource, sSchema, TRUE);

                if (pSchema != NULL)
                {
                    g_settings_schema_unref (pSchema);
                    this->pThemeSettings = g_settings_new (sSchema);
                }
                else
                {
                    g_warning ("Panic: No %s schema could be found", sSchema);
                }

                pSchema = g_settings_schema_source_lookup (pSource, sCursorSchema, TRUE);

                if (pSchema != NULL)
                {
                    g_settings_schema_unref (pSchema);
                    this->pCursorSettings = g_settings_new (sCursorSchema);
                }
                else
                {
                    g_warning ("Panic: No %s schema could be found", sCursorSchema);
                }

                pSchema = g_settings_schema_source_lookup (pSource, sMetacitySchema, TRUE);

                if (pSchema != NULL)
                {
                    g_settings_schema_unref (pSchema);
                    this->pMetacitySettings = g_settings_new (sMetacitySchema);
                }
                else
                {
                    g_warning ("Panic: No %s schema could be found", sMetacitySchema);
                }

                if (this->bTest)
                {
                    sSchema = "org.ayatana.indicator.display";
                }
                else
                {
                    sSchema = "org.gnome.desktop.interface";
                }

                pSchema = g_settings_schema_source_lookup (pSource, sSchema, TRUE);

                if (pSchema != NULL)
                {
                    gboolean bColorScheme = g_settings_schema_has_key (pSchema, "color-scheme");
                    g_settings_schema_unref (pSchema);

                    if (bColorScheme)
                    {
                        this->pColorSchemeSettings = g_settings_new (sSchema);
                    }
                    else
                    {
                        g_warning ("org.gnome.desktop.interface::color-scheme not found. Native theme profile changes will not be triggered.");
                    }
                }
                else
                {
                    g_warning ("Panic: No %s schema could be found", sSchema);
                }
            }
            else
            {
                this->pConnection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
                this->nGreeterSubscription = g_dbus_connection_signal_subscribe (this->pConnection, NULL, GREETER_BUS_NAME, "UserChanged", GREETER_BUS_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, onUserChanged, this, NULL);
                loadManager (this);
            }
#endif
        }
    }

#ifdef COLOR_TEMP_ENABLED
    if (!this->bGreeter)
    {
        gint nUid = geteuid ();
        getAccountsService (this, nUid);
    }
#endif
    m_action_group = create_action_group();

    // build the icon
    const char *icon_name {"orientation-lock"};

    if (!ayatana_common_utils_is_lomiri())
    {
        icon_name = "display-panel";
    }

    auto icon = g_themed_icon_new_with_default_fallbacks(icon_name);
    auto icon_deleter = [](GIcon* o){g_object_unref(G_OBJECT(o));};
    m_icon.reset(icon, icon_deleter);

    // build the phone profile
    auto menu_model_deleter = [](GMenuModel* o){g_object_unref(G_OBJECT(o));};
    std::shared_ptr<GMenuModel> phone_menu (create_phone_menu(), menu_model_deleter);
    m_phone = std::make_shared<SimpleProfile>("phone", phone_menu);
    update_phone_header();

    // build the desktop profile
    std::shared_ptr<GMenuModel> desktop_menu (create_desktop_menu(), menu_model_deleter);
    m_desktop = std::make_shared<SimpleProfile>("desktop", desktop_menu);
    gboolean bVisible = !this->bGreeter;

    #ifdef COLOR_TEMP_ENABLED
        bVisible = bVisible || !this->bXsctUnsupported;
    #endif

    update_desktop_header(bVisible);

#ifdef COLOR_TEMP_ENABLED
    if (ayatana_common_utils_is_lomiri() == FALSE)
    {
        if (!this->bTest)
        {
            this->fLatitude = g_settings_get_double (this->m_settings, "latitude");
            this->fLongitude = g_settings_get_double (this->m_settings, "longitude");
            gclue_simple_new ("ayatana-indicator-display", GCLUE_ACCURACY_LEVEL_CITY, NULL, onGeoClueLoaded, this);
            this->nCallback = g_timeout_add_seconds (60, updateColor, this);
            updateColor (this);
        }
    }
#endif
  }

  ~Impl()
  {
#ifdef COLOR_TEMP_ENABLED
    if (this->nGreeterSubscription)
    {
        g_dbus_connection_signal_unsubscribe (this->pConnection, this->nGreeterSubscription);
    }

    if (this->lUsers)
    {
        g_slist_free (this->lUsers);
    }

    if (this->pConnection)
    {
        g_object_unref (this->pConnection);
    }

    if (nCallback)
    {
        g_source_remove (nCallback);
    }

    if (sLastTheme)
    {
        g_free (sLastTheme);
    }

    g_clear_object (&pThemeSettings);
    g_clear_object (&pCursorSettings);
    g_clear_object (&pMetacitySettings);
    g_clear_object (&pColorSchemeSettings);

    if (this->pAccountsServiceConnection)
    {
        g_object_unref (this->pAccountsServiceConnection);
    }
#endif

    g_signal_handlers_disconnect_by_data(m_settings, this);
    g_clear_object(&m_action_group);
    g_clear_object(&m_settings);
  }

  GSimpleActionGroup* action_group() const
  {
    return m_action_group;
  }

  std::vector<std::shared_ptr<Profile>> profiles()
  {
    std::vector<std::shared_ptr<Profile>> ret;
    ret.push_back(m_phone);
    ret.push_back(m_desktop);
    return ret;
  }

private:

#ifdef COLOR_TEMP_ENABLED
    static void onUserChanged (GDBusConnection *pConnection, const gchar *sSender, const gchar *sPath, const gchar *sInterface, const gchar *sSignal, GVariant *pParameters, gpointer pUserData)
    {
        DisplayIndicator::Impl *pImpl = static_cast<DisplayIndicator::Impl*>(pUserData);
        g_variant_get (pParameters, "(s)", &pImpl->sUser);
        loadManager (pImpl);
    }

    static void getAccountsService (DisplayIndicator::Impl *pImpl, gint nUid)
    {
        if (!pImpl->bXsctUnsupported)
        {
            pImpl->bReadingAccountsService = TRUE;
            gchar *sPath = g_strdup_printf ("/org/freedesktop/Accounts/User%i", nUid);
            GDBusProxy *pProxy = g_dbus_proxy_new_sync (pImpl->pAccountsServiceConnection, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.Accounts", sPath, "org.freedesktop.DBus.Properties", NULL, NULL);
            g_free (sPath);

            if (pProxy)
            {
                const gchar *lProperties[] = {"brightness", "color-temp", "color-temp-profile"};

                for (gint nIndex = 0; nIndex < 3; nIndex++)
                {
                    GVariant *pParams = g_variant_new ("(ss)", "org.ayatana.indicator.display.AccountsService", lProperties[nIndex]);
                    GVariant *pValue = g_dbus_proxy_call_sync (pProxy, "Get", pParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL);

                    if (pValue)
                    {
                        GVariant *pChild0 = g_variant_get_child_value (pValue, 0);
                        g_variant_unref (pValue);
                        GVariant *pChild1 = g_variant_get_child_value (pChild0, 0);
                        g_variant_unref (pChild0);
                        g_settings_set_value (pImpl->m_settings, lProperties[nIndex], pChild1);
                        g_variant_unref (pChild1);
                    }
                }
            }

            pImpl->bReadingAccountsService = FALSE;
        }
    }

    static void onUserLoaded (DisplayIndicator::Impl *pImpl, ActUser *pUser)
    {
        g_signal_handlers_disconnect_by_func (G_OBJECT (pUser), (gpointer) G_CALLBACK (onUserLoaded), pImpl);

        if (!pImpl->sUser)
        {
            GError *pError = NULL;
            GVariant *pGreeterUser = g_dbus_connection_call_sync (pImpl->pConnection, GREETER_BUS_NAME, GREETER_BUS_PATH, GREETER_BUS_NAME, "GetUser", NULL, G_VARIANT_TYPE ("(s)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &pError);

            if (pError)
            {
                g_debug ("Failed calling GetUser, the greeter may not be ready yet: %s", pError->message);
                g_error_free (pError);

                return;
            }

            g_variant_get (pGreeterUser, "(s)", &pImpl->sUser);
        }

        gboolean bPrefix = g_str_has_prefix (pImpl->sUser, "*");

        if (!bPrefix)
        {
            const gchar *sUserName = act_user_get_user_name (pUser);
            gboolean bSame = g_str_equal (pImpl->sUser, sUserName);

            if (bSame)
            {
                gint nUid = act_user_get_uid (pUser);
                getAccountsService (pImpl, nUid);
                updateColor (pImpl);
            }
        }
    }

    static void onManagerLoaded (DisplayIndicator::Impl *pImpl)
    {
        ActUserManager *pManager = act_user_manager_get_default ();

        if (!pImpl->lUsers)
        {
            pImpl->lUsers = act_user_manager_list_users (pManager);
        }

        for (GSList *lUser = pImpl->lUsers; lUser; lUser = lUser->next)
        {
            ActUser *pUser = static_cast<ActUser*>(lUser->data);
            gboolean bLoaded = act_user_is_loaded (pUser);

            if (bLoaded)
            {
                onUserLoaded (pImpl, pUser);
            }
            else
            {
                g_signal_connect_swapped (pUser, "notify::is-loaded", G_CALLBACK (onUserLoaded), pImpl);
            }
        }
    }

    static void loadManager (DisplayIndicator::Impl *pImpl)
    {
        ActUserManager *pManager = act_user_manager_get_default ();
        gboolean bLoaded = FALSE;
        g_object_get (pManager, "is-loaded", &bLoaded, NULL);

        if (bLoaded)
        {
            onManagerLoaded (pImpl);
        }
        else
        {
            g_signal_connect_swapped (pManager, "notify::is-loaded", G_CALLBACK (onManagerLoaded), pImpl);
        }
    }

    static gboolean updateColor (gpointer pData)
    {
        DisplayIndicator::Impl *pImpl = static_cast<DisplayIndicator::Impl*>(pData);

        if (pImpl->bReadingAccountsService)
        {
            return G_SOURCE_CONTINUE;
        }

        guint nProfile = 0;
        gdouble fBrightness = 0.0;

        if (!pImpl->bXsctUnsupported)
        {
            g_settings_get (pImpl->m_settings, "color-temp-profile", "q", &nProfile);
            fBrightness = g_settings_get_double (pImpl->m_settings, "brightness");
        }

        gchar *sThemeProfile = NULL;
        gboolean bThemeAdaptive = FALSE;

        if (!pImpl->bGreeter)
        {
            sThemeProfile = g_settings_get_string (pImpl->m_settings, "theme-profile");
            bThemeAdaptive = g_str_equal (sThemeProfile, "adaptive");
        }

        guint nTemperature = 0;
        const gchar *sColorScheme = NULL;
        gchar *sTheme = NULL;
        gint64 nNow = g_get_real_time ();
        gdouble fElevation = solar_elevation((gdouble) nNow / 1000000.0, pImpl->fLatitude, pImpl->fLongitude);

        if (!pImpl->bXsctUnsupported)
        {
            if (nProfile == 0)
            {
                g_settings_get (pImpl->m_settings, "color-temp", "q", &nTemperature);
            }
            else
            {
                gdouble fShifting = 0.0;

                if (fElevation < SOLAR_CIVIL_TWILIGHT_ELEV)
                {
                    fShifting = 1.0;
                }
                else if (fElevation < 3.0)
                {
                    fShifting = 1.0 - ((SOLAR_CIVIL_TWILIGHT_ELEV - fElevation) / (SOLAR_CIVIL_TWILIGHT_ELEV - 3.0));
                }

                nTemperature = m_lTempProfiles[nProfile].nTempHigh - (m_lTempProfiles[nProfile].nTempHigh - m_lTempProfiles[nProfile].nTempLow) * fShifting;
                pImpl->bAutoSliderUpdate = TRUE;
            }
        }

        if (!pImpl->bGreeter)
        {
            if (!bThemeAdaptive)
            {
                gchar *sThemeKey = g_strdup_printf ("%s-theme", sThemeProfile);
                sTheme = g_settings_get_string (pImpl->m_settings, sThemeKey);
                g_free (sThemeKey);

                gboolean bLightTheme = g_str_equal (sThemeProfile, "light");

                if (bLightTheme)
                {
                    sColorScheme = "prefer-light";
                }
                else
                {
                    sColorScheme = "prefer-dark";
                }
            }
            else
            {
                if (fElevation < SOLAR_CIVIL_TWILIGHT_ELEV)
                {
                    sColorScheme = "prefer-dark";
                    sTheme = g_settings_get_string (pImpl->m_settings, "dark-theme");
                }
                else
                {
                    sColorScheme = "prefer-light";
                    sTheme = g_settings_get_string (pImpl->m_settings, "light-theme");
                }
            }
        }

        if (!pImpl->bXsctUnsupported)
        {
            if (pImpl->fLastBrightness != fBrightness || pImpl->nLasColorTemp != nTemperature)
            {
                g_debug ("Calling xsct with %u %f", nTemperature, fBrightness);

                GAction *pAction = g_action_map_lookup_action (G_ACTION_MAP (pImpl->m_action_group), "color-temp");
                GVariant *pTemperature = g_variant_new_double (nTemperature);
                g_action_change_state (pAction, pTemperature);

                GError *pError = NULL;
                gchar *sCommand = g_strdup_printf ("xsct %u %f", nTemperature, fBrightness);
                gboolean bSuccess = g_spawn_command_line_sync (sCommand, NULL, NULL, NULL, &pError);

                if (!bSuccess)
                {
                    g_warning ("Panic: The call to '%s' failed: %s", sCommand, pError->message);
                    g_error_free (pError);
                }
                else
                {
                    pImpl->fLastBrightness = fBrightness;
                    pImpl->nLasColorTemp = nTemperature;
                    gint nUid = 0;

                    if (!pImpl->bGreeter)
                    {
                        nUid = geteuid ();
                    }
                    else if (pImpl->sUser)
                    {
                        const struct passwd *pPasswd = getpwnam (pImpl->sUser);

                        if (pPasswd)
                        {
                            nUid = pPasswd->pw_uid;
                        }
                    }

                    if (nUid)
                    {
                        gchar *sPath = g_strdup_printf ("/org/freedesktop/Accounts/User%i", nUid);
                        GDBusProxy *pProxy = g_dbus_proxy_new_sync (pImpl->pAccountsServiceConnection, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.Accounts", sPath, "org.freedesktop.DBus.Properties", NULL, NULL);
                        g_free (sPath);
                        GVariant *pBrightnessValue = g_variant_new ("d", pImpl->fLastBrightness);
                        GVariant *pBrightnessParams = g_variant_new ("(ssv)", "org.ayatana.indicator.display.AccountsService", "brightness", pBrightnessValue);
                        g_dbus_proxy_call (pProxy, "Set", pBrightnessParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
                        GVariant *pColorTempValue = g_variant_new ("q", pImpl->nLasColorTemp);
                        GVariant *pColorTempParams = g_variant_new ("(ssv)", "org.ayatana.indicator.display.AccountsService", "color-temp", pColorTempValue);
                        g_dbus_proxy_call (pProxy, "Set", pColorTempParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
                        GVariant *pProfileValue = g_variant_new ("q", nProfile);
                        GVariant *pProfileParams = g_variant_new ("(ssv)", "org.ayatana.indicator.display.AccountsService", "color-temp-profile", pProfileValue);
                        g_dbus_proxy_call (pProxy, "Set", pProfileParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
                    }
                }

                g_free (sCommand);
            }
        }

        if (!pImpl->bGreeter)
        {
            gboolean bSameColorScheme = g_str_equal (sColorScheme, pImpl->sLastColorScheme);

            if (pImpl->pColorSchemeSettings && !bSameColorScheme)
            {
                g_debug ("Changing color scheme to %s", sColorScheme);

                g_settings_set_string (pImpl->pColorSchemeSettings, "color-scheme", sColorScheme);
                pImpl->sLastColorScheme = sColorScheme;
            }

            gboolean bSameTheme = FALSE;

            if (pImpl->sLastTheme)
            {
                bSameTheme = g_str_equal (pImpl->sLastTheme, sTheme);
            }

            gboolean bCurrentTheme = g_str_equal ("current", sTheme);

            if (!bSameTheme && !bCurrentTheme)
            {
                g_debug ("Changing theme to %s", sTheme);

                if (pImpl->pThemeSettings)
                {
                    g_settings_set_string (pImpl->pThemeSettings, "gtk-theme", sTheme);
                }

                gchar *sThemePath = g_strdup_printf ("/usr/share/themes/%s/index.theme", sTheme);
                gboolean bThemePath = g_file_test (sThemePath, G_FILE_TEST_EXISTS);

                if (bThemePath)
                {
                    gchar *sFile = NULL;
                    GError *pError = NULL;
                    g_file_get_contents (sThemePath, &sFile, NULL, &pError);

                    if (!pError)
                    {
                        #if GLIB_CHECK_VERSION(2, 73, 0)
                            GRegex *pRegex = g_regex_new ("IconTheme *= *(.*)", G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, &pError);
                        #else
                            GRegex *pRegex = g_regex_new ("IconTheme *= *(.*)", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, &pError);
                        #endif

                        if (!pError)
                        {
                            GMatchInfo *pMatchInfo = NULL;

                            #if GLIB_CHECK_VERSION(2, 73, 0)
                                gboolean bMatch = g_regex_match (pRegex, sFile, G_REGEX_MATCH_DEFAULT, &pMatchInfo);
                            #else
                                gboolean bMatch = g_regex_match (pRegex, sFile, (GRegexMatchFlags) 0, &pMatchInfo);
                            #endif

                            if (bMatch)
                            {
                                gchar *sIconTheme = g_match_info_fetch (pMatchInfo, 1);

                                if (pImpl->pThemeSettings)
                                {
                                    g_settings_set_string (pImpl->pThemeSettings, "icon-theme", sIconTheme);
                                }

                                g_free (sIconTheme);
                            }
                            else
                            {
                                g_warning ("/usr/share/themes/%s/index.theme does not define an IconTheme", sTheme);
                            }

                            g_match_info_free (pMatchInfo);
                            g_regex_unref (pRegex);
                        }
                        else
                        {
                            g_warning ("Panic: Failed to compile regex: %s", pError->message);
                            g_error_free (pError);
                        }

                        #if GLIB_CHECK_VERSION(2, 73, 0)
                            pRegex = g_regex_new ("MetacityTheme *= *(.*)", G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, &pError);
                        #else
                            pRegex = g_regex_new ("MetacityTheme *= *(.*)", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, &pError);
                        #endif

                        if (!pError)
                        {
                            GMatchInfo *pMatchInfo = NULL;

                            #if GLIB_CHECK_VERSION(2, 73, 0)
                                gboolean bMatch = g_regex_match (pRegex, sFile, G_REGEX_MATCH_DEFAULT, &pMatchInfo);
                            #else
                                gboolean bMatch = g_regex_match (pRegex, sFile, (GRegexMatchFlags) 0, &pMatchInfo);
                            #endif

                            if (bMatch)
                            {
                                gchar *sMetacityTheme = g_match_info_fetch (pMatchInfo, 1);

                                if (pImpl->pMetacitySettings)
                                {
                                    g_settings_set_string (pImpl->pMetacitySettings, "theme", sMetacityTheme);
                                }

                                g_free (sMetacityTheme);
                            }
                            else
                            {
                                g_warning ("/usr/share/themes/%s/index.theme does not define a MetacityTheme", sTheme);
                            }

                            g_match_info_free (pMatchInfo);
                            g_regex_unref (pRegex);
                        }
                        else
                        {
                            g_warning ("Panic: Failed to compile regex: %s", pError->message);
                            g_error_free (pError);
                        }

                        #if GLIB_CHECK_VERSION(2, 73, 0)
                            pRegex = g_regex_new ("CursorTheme *= *(.*)", G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, &pError);
                        #else
                            pRegex = g_regex_new ("CursorTheme *= *(.*)", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, &pError);
                        #endif

                        if (!pError)
                        {
                            GMatchInfo *pMatchInfo = NULL;

                            #if GLIB_CHECK_VERSION(2, 73, 0)
                                gboolean bMatch = g_regex_match (pRegex, sFile, G_REGEX_MATCH_DEFAULT, &pMatchInfo);
                            #else
                                gboolean bMatch = g_regex_match (pRegex, sFile, (GRegexMatchFlags) 0, &pMatchInfo);
                            #endif

                            if (bMatch)
                            {
                                gchar *sCursorTheme = g_match_info_fetch (pMatchInfo, 1);

                                if (pImpl->pCursorSettings)
                                {
                                    g_settings_set_string (pImpl->pCursorSettings, "cursor-theme", sTheme);
                                }

                                g_free (sCursorTheme);
                            }
                            else
                            {
                                g_warning ("/usr/share/themes/%s/index.theme does not define a CursorTheme", sTheme);
                            }

                            g_match_info_free (pMatchInfo);
                            g_regex_unref (pRegex);
                        }
                        else
                        {
                            g_warning ("Panic: Failed to compile regex: %s", pError->message);
                            g_error_free (pError);
                        }

                        g_free (sFile);
                    }
                    else
                    {
                        g_warning ("Panic: Failed to get index.theme contents: %s", pError->message);
                        g_error_free (pError);
                    }
                }
                else
                {
                    g_warning ("/usr/share/themes/%s/index.theme does not exist", sTheme);
                }

                g_free (sThemePath);

                if (pImpl->sLastTheme)
                {
                    g_free (pImpl->sLastTheme);
                }

                pImpl->sLastTheme = g_strdup (sTheme);
            }

            g_free (sTheme);
            g_free (sThemeProfile);
        }

        return G_SOURCE_CONTINUE;
    }

    static void onGeoClueLoaded (GObject *pObject, GAsyncResult *pResult, gpointer pData)
    {
        DisplayIndicator::Impl *pImpl = static_cast<DisplayIndicator::Impl*>(pData);
        GError *pError = NULL;
        GClueSimple *pSimple = gclue_simple_new_finish (pResult, &pError);

        if (pError != NULL)
        {
            g_warning ("Failed to connect to GeoClue2 service: %s", pError->message);
        }
        else
        {
            GClueLocation *pLocation = gclue_simple_get_location (pSimple);
            pImpl->fLatitude = gclue_location_get_latitude (pLocation);
            pImpl->fLongitude = gclue_location_get_longitude (pLocation);
            g_settings_set_double (pImpl->m_settings, "latitude", pImpl->fLatitude);
            g_settings_set_double (pImpl->m_settings, "longitude", pImpl->fLongitude);
        }

        updateColor (pImpl);
    }

    static void onColorTempSettings (GSettings *pSettings, const gchar *sKey, gpointer pData)
    {
        GVariant *pProfile = g_variant_new_uint16 (0);
        g_settings_set_value (pSettings, "color-temp-profile", pProfile);

        updateColor (pData);
    }

    static gboolean settingsIntToActionStateString (GValue *pValue, GVariant *pVariant, gpointer pData)
    {
        guint16 nVariant = g_variant_get_uint16 (pVariant);
        gchar *sVariant = g_strdup_printf ("%u", nVariant);
        GVariant *pVariantString = g_variant_new_string (sVariant);
        g_free (sVariant);
        g_value_set_variant (pValue, pVariantString);

        return TRUE;
    }

    static GVariant* actionStateStringToSettingsInt (const GValue *pValue, const GVariantType *pVariantType, gpointer pData)
    {
        GVariant *pVariantString = g_value_get_variant (pValue);
        const gchar *sValue = g_variant_get_string (pVariantString, NULL);
        guint16 nValue = (guint16) g_ascii_strtoull (sValue, NULL, 10);
        GVariant *pVariantInt = g_variant_new_uint16 (nValue);
        GValue cValue = G_VALUE_INIT;
        g_value_init (&cValue, G_TYPE_VARIANT);
        g_value_set_variant (&cValue, pVariantInt);

        return g_value_dup_variant (&cValue);
    }

    static void onColorTempState (GSimpleAction *pAction, GVariant *pVariant, gpointer pData)
    {
        g_simple_action_set_state (pAction, pVariant);

        DisplayIndicator::Impl *pImpl = static_cast<DisplayIndicator::Impl*>(pData);

        if (pImpl->bAutoSliderUpdate)
        {
            pImpl->bAutoSliderUpdate = FALSE;

            return;
        }

        GVariant *pProfile = g_variant_new_uint16 (0);
        g_settings_set_value (pImpl->m_settings, "color-temp-profile", pProfile);

        guint16 nTemperature = (guint16) g_variant_get_double (pVariant);
        GVariant *pTemperature = g_variant_new_uint16 (nTemperature);
        g_settings_set_value (pImpl->m_settings, "color-temp", pTemperature);
    }
#endif

  /***
  ****  Actions
  ***/

  static gboolean settings_to_action_state(GValue *value,
                                           GVariant *variant,
                                           gpointer /*unused*/)
  {
    g_value_set_variant(value, variant);
    return TRUE;
  }

  static GVariant* action_state_to_settings(const GValue *value,
                                            const GVariantType * /*expected_type*/,
                                            gpointer /*unused*/)
  {
    return g_value_dup_variant(value);
  }

  GSimpleActionGroup* create_action_group()
  {
    GSimpleActionGroup* group;
    GSimpleAction* action;

    group = g_simple_action_group_new();
    GVariantType *pVariantType = g_variant_type_new("b");
    action = g_simple_action_new_stateful("rotation-lock",
                                          pVariantType,
                                          g_variant_new_boolean(false));
    g_variant_type_free(pVariantType);
    g_settings_bind_with_mapping(m_settings, "rotation-lock",
                                 action, "state",
                                 G_SETTINGS_BIND_DEFAULT,
                                 settings_to_action_state,
                                 action_state_to_settings,
                                 nullptr,
                                 nullptr);

    g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(action));
    g_object_unref(G_OBJECT(action));
    g_signal_connect_swapped(m_settings, "changed::rotation-lock",
                             G_CALLBACK(on_rotation_lock_setting_changed), this);

#ifdef COLOR_TEMP_ENABLED
    if (ayatana_common_utils_is_lomiri() == FALSE)
    {
        if (!this->bXsctUnsupported)
        {
            pVariantType = g_variant_type_new ("d");
            guint nTemperature = 0;
            g_settings_get (this->m_settings, "color-temp", "q", &nTemperature);
            action = g_simple_action_new_stateful ("color-temp", pVariantType, g_variant_new_double (nTemperature));
            g_variant_type_free (pVariantType);
            g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
            g_signal_connect (m_settings, "changed::color-temp", G_CALLBACK (onColorTempSettings), this);
            g_signal_connect (action, "change-state", G_CALLBACK (onColorTempState), this);
            g_object_unref(G_OBJECT (action));

            pVariantType = g_variant_type_new ("s");
            guint nProfile = 0;
            g_settings_get (this->m_settings, "color-temp-profile", "q", &nProfile);
            gchar *sProfile = g_strdup_printf ("%i", nProfile);
            action = g_simple_action_new_stateful ("profile", pVariantType, g_variant_new_string (sProfile));
            g_free (sProfile);
            g_variant_type_free (pVariantType);
            g_settings_bind_with_mapping (this->m_settings, "color-temp-profile", action, "state", G_SETTINGS_BIND_DEFAULT, settingsIntToActionStateString, actionStateStringToSettingsInt, NULL, NULL);
            g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(action));
            g_object_unref(G_OBJECT(action));
            g_signal_connect_swapped (m_settings, "changed::color-temp-profile", G_CALLBACK (updateColor), this);

            pVariantType = g_variant_type_new("d");
            gdouble fBrightness = g_settings_get_double (this->m_settings, "brightness");
            action = g_simple_action_new_stateful ("brightness", pVariantType, g_variant_new_double (fBrightness));
            g_variant_type_free(pVariantType);
            g_settings_bind_with_mapping (m_settings, "brightness", action, "state", G_SETTINGS_BIND_DEFAULT, settings_to_action_state, action_state_to_settings, NULL, NULL);
            g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
            g_object_unref (G_OBJECT (action));
            g_signal_connect_swapped (m_settings, "changed::brightness", G_CALLBACK (updateColor), this);
        }

        if (!this->bGreeter)
        {
            pVariantType = g_variant_type_new ("s");
            action = g_simple_action_new_stateful ("theme", pVariantType, g_variant_new_string ("light"));
            g_variant_type_free (pVariantType);
            g_settings_bind_with_mapping (this->m_settings, "theme-profile", action, "state", G_SETTINGS_BIND_DEFAULT, settings_to_action_state, action_state_to_settings, NULL, NULL);
            g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(action));
            g_object_unref(G_OBJECT(action));
            g_signal_connect_swapped (m_settings, "changed::theme-profile", G_CALLBACK (updateColor), this);
            g_signal_connect_swapped (m_settings, "changed::light-theme", G_CALLBACK (updateColor), this);
            g_signal_connect_swapped (m_settings, "changed::dark-theme", G_CALLBACK (updateColor), this);
        }
    }
#endif

    if (!this->bGreeter)
    {
        action = g_simple_action_new ("settings", NULL);
        g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
        g_signal_connect (action, "activate", G_CALLBACK (onSettings), this);
        g_object_unref (G_OBJECT (action));
    }

    return group;
  }

  /***
  ****  Phone profile
  ***/

  static void on_rotation_lock_setting_changed (gpointer gself)
  {
    static_cast<Impl*>(gself)->update_phone_header();
  }

  GMenuModel* create_phone_menu()
  {
    GMenu* menu;
    GMenu* section;
    GMenuItem* menu_item;

    menu = g_menu_new();
    section = g_menu_new();
    menu_item = g_menu_item_new(_("Rotation Lock"), "indicator.rotation-lock(true)");
    g_menu_item_set_attribute(menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.switch");
    g_menu_append_item(section, menu_item);
    g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
    g_object_unref(section);
    g_object_unref(menu_item);

    return G_MENU_MODEL(menu);
  }

  static void onSettings (GSimpleAction *pAction, GVariant *pVariant, gpointer pData)
  {
    if (ayatana_common_utils_is_mate ())
    {
      ayatana_common_utils_execute_command ("mate-display-properties");
    }
    else if (ayatana_common_utils_is_xfce ())
    {
      ayatana_common_utils_execute_command ("xfce4-display-settings");
    }
    else
    {
      ayatana_common_utils_execute_command ("gnome-control-center display");
    }
  }

  GMenuModel* create_desktop_menu()
  {
    GMenu* menu;
    GMenu* section;
    GMenuItem* menu_item;

    menu = g_menu_new();

    if (ayatana_common_utils_is_lomiri())
    {
        section = g_menu_new();
        menu_item = g_menu_item_new(_("Rotation Lock"), "indicator.rotation-lock(true)");
        g_menu_item_set_attribute(menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.switch");
        g_menu_append_item(section, menu_item);
        g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
        g_object_unref(section);
        g_object_unref(menu_item);
    }
    else
    {
#ifdef COLOR_TEMP_ENABLED
        section = g_menu_new ();

        if (!this->bXsctUnsupported)
        {
            GIcon *pIconMin = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-brightness-low");
            GIcon *pIconMax = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-brightness-high");
            GVariant *pIconMinSerialised = g_icon_serialize (pIconMin);
            GVariant *pIconMaxSerialised = g_icon_serialize (pIconMax);
            menu_item = g_menu_item_new (_("Brightness"), "indicator.brightness");
            g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider");
            g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider");
            g_menu_item_set_attribute_value (menu_item, "min-icon", pIconMinSerialised);
            g_menu_item_set_attribute_value (menu_item, "max-icon", pIconMaxSerialised);
            g_menu_item_set_attribute (menu_item, "min-value", "d", 0.5);
            g_menu_item_set_attribute (menu_item, "max-value", "d", 1.0);
            g_menu_item_set_attribute (menu_item, "step", "d", 0.01);
            g_menu_item_set_attribute (menu_item, "digits", "y", 2);
            g_menu_append_item (section, menu_item);

            pIconMin = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-colortemp-on");
            pIconMax = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-colortemp-off");
            pIconMinSerialised = g_icon_serialize (pIconMin);
            pIconMaxSerialised = g_icon_serialize (pIconMax);
            menu_item = g_menu_item_new (_("Color temperature"), "indicator.color-temp");
            g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider");
            g_menu_item_set_attribute_value (menu_item, "min-icon", pIconMinSerialised);
            g_menu_item_set_attribute_value (menu_item, "max-icon", pIconMaxSerialised);
            g_menu_item_set_attribute (menu_item, "min-value", "d", 3000.0);
            g_menu_item_set_attribute (menu_item, "max-value", "d", 6500.0);
            g_menu_item_set_attribute (menu_item, "step", "d", 100.0);
            g_menu_item_set_attribute (menu_item, "digits", "y", 0);
            g_menu_append_item (section, menu_item);

            GMenu *pMenuProfiles = g_menu_new ();
            GMenuItem *pItemProfiles = g_menu_item_new_submenu (_("Color temperature profile"), G_MENU_MODEL (pMenuProfiles));
            guint nProfile = 0;

            while (m_lTempProfiles[nProfile].sName != NULL)
            {
                gchar *sAction = g_strdup_printf ("indicator.profile::%u", nProfile);
                gchar *sName = gettext (m_lTempProfiles[nProfile].sName);
                GMenuItem *pItemProfile = g_menu_item_new (sName, sAction);
                g_free(sAction);
                g_menu_append_item (pMenuProfiles, pItemProfile);
                g_object_unref (pItemProfile);

                nProfile++;
            }

            g_menu_append_item (section, pItemProfiles);
            g_object_unref (pItemProfiles);
            g_object_unref (pMenuProfiles);

            g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
            g_object_unref (pIconMin);
            g_object_unref (pIconMax);
            g_variant_unref (pIconMinSerialised);
            g_variant_unref (pIconMaxSerialised);
            g_object_unref (section);
            g_object_unref (menu_item);
        }

        if (!this->bGreeter)
        {
            section = g_menu_new ();
            GMenu *pMenuProfiles = g_menu_new ();
            GMenuItem *pItemProfiles = g_menu_item_new_submenu (_("Theme profile"), G_MENU_MODEL (pMenuProfiles));
            GMenuItem *pItemProfile = g_menu_item_new (_("Light"), "indicator.theme::light");
            g_menu_append_item (pMenuProfiles, pItemProfile);
            g_object_unref (pItemProfile);
            pItemProfile = g_menu_item_new (_("Dark"), "indicator.theme::dark");
            g_menu_append_item (pMenuProfiles, pItemProfile);
            g_object_unref (pItemProfile);
            pItemProfile = g_menu_item_new (_("Adaptive"), "indicator.theme::adaptive");
            g_menu_append_item (pMenuProfiles, pItemProfile);
            g_object_unref (pItemProfile);
            g_menu_append_item (section, pItemProfiles);
            g_object_unref (pItemProfiles);
            g_object_unref (pMenuProfiles);
            g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
            g_object_unref (section);
        }
#endif
        // cppcheck fails on !this->bGreeter
        if (this->bGreeter == FALSE)
        {
            section = g_menu_new ();
            menu_item = g_menu_item_new (_("Display settings…"), "indicator.settings");
            g_menu_append_item (section, menu_item);
            g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
            g_object_unref (section);
            g_object_unref (menu_item);
        }
    }

    return G_MENU_MODEL(menu);
  }

  void update_phone_header()
  {
    Header h;
    h.title = _("Rotation");
    h.tooltip = h.title;
    h.a11y = h.title;
    h.is_visible = g_settings_get_boolean(m_settings, "rotation-lock");
    h.icon = m_icon;
    m_phone->header().set(h);
  }

  void update_desktop_header(gboolean bVisible)
  {
    Header h;
    h.title = _("Display");
    h.tooltip = _("Display settings and features");
    h.a11y = h.title;
    h.is_visible = bVisible;
    h.icon = m_icon;
    m_desktop->header().set(h);
  }

  /***
  ****
  ***/

  GSettings* m_settings = nullptr;
  GSimpleActionGroup* m_action_group = nullptr;
  std::shared_ptr<SimpleProfile> m_phone;
  std::shared_ptr<SimpleProfile> m_desktop;
  std::shared_ptr<GIcon> m_icon;
  gboolean bGreeter;
#ifdef COLOR_TEMP_ENABLED
  gdouble fLatitude = 0.0;
  gdouble fLongitude = 0.0;
  gboolean bAutoSliderUpdate = FALSE;
  guint nCallback = 0;
  gdouble fLastBrightness = 0.0;
  guint nLasColorTemp = 0;
  gchar *sLastTheme = NULL;
  const gchar *sLastColorScheme = "default";
  GSettings *pThemeSettings = NULL;
  GSettings *pCursorSettings = NULL;
  GSettings *pMetacitySettings = NULL;
  GSettings *pColorSchemeSettings = NULL;
  gboolean bTest;
  guint nGreeterSubscription = 0;
  GDBusConnection *pConnection = NULL;
  gchar *sUser = NULL;
  GSList *lUsers = NULL;
  gboolean bReadingAccountsService = FALSE;
  GDBusConnection *pAccountsServiceConnection = NULL;
  gboolean bXsctUnsupported = FALSE;
#endif
};

/***
****
***/

DisplayIndicator::DisplayIndicator():
    impl(new Impl())
{
}

DisplayIndicator::~DisplayIndicator()
{
}

std::vector<std::shared_ptr<Profile>>
DisplayIndicator::profiles() const
{
  return impl->profiles();
}

GSimpleActionGroup*
DisplayIndicator::action_group() const
{
  return impl->action_group();
}

const char*
DisplayIndicator::name() const
{
  return "display";
}

/***
****
***/

