/*
 * MATE CPUFreq Applet
 * Copyright (C) 2004 Carlos Garcia Campos <carlosgc@gnome.org>
 *
 *  This library 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 of the License, or (at your option) any later version.
 *
 *  This library 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 this library; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Authors : Carlos García Campos <carlosgc@gnome.org>
 */

#include <glib/gi18n.h>

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <stdlib.h>
#include <string.h>

#include "cpufreq-popup.h"
#include "cpufreq-selector.h"
#include "cpufreq-utils.h"

struct _CPUFreqPopupPrivate {
    GtkUIManager        *ui_manager;
    GSList              *radio_group;

    GtkActionGroup      *freqs_group;
    GSList              *freqs_actions;

    GtkActionGroup      *govs_group;
    GSList              *govs_actions;

    guint                merge_id;
    gboolean             need_build;
    gboolean             show_freqs;

    CPUFreqMonitor      *monitor;
    GtkWidget           *parent;
};

static void cpufreq_popup_finalize   (GObject           *object);

G_DEFINE_TYPE_WITH_PRIVATE (CPUFreqPopup, cpufreq_popup, G_TYPE_OBJECT)

static const gchar *ui_popup =
    "<ui>"
    "    <popup name=\"CPUFreqSelectorPopup\" action=\"PopupAction\">"
    "        <placeholder name=\"FreqsItemsGroup\">"
    "        </placeholder>"
    "        <separator />"
    "        <placeholder name=\"GovsItemsGroup\">"
    "        </placeholder>"
    "    </popup>"
    "</ui>";

#define FREQS_PLACEHOLDER_PATH "/CPUFreqSelectorPopup/FreqsItemsGroup"
#define GOVS_PLACEHOLDER_PATH "/CPUFreqSelectorPopup/GovsItemsGroup"

static void
cpufreq_popup_init (CPUFreqPopup *popup)
{
    popup->priv = cpufreq_popup_get_instance_private (popup);

    popup->priv->ui_manager = gtk_ui_manager_new ();
    popup->priv->radio_group = NULL;

    popup->priv->freqs_group = NULL;
    popup->priv->freqs_actions = NULL;

    popup->priv->govs_group = NULL;
    popup->priv->govs_actions = NULL;

    popup->priv->merge_id = 0;
    popup->priv->need_build = TRUE;
    popup->priv->show_freqs = FALSE;

    gtk_ui_manager_add_ui_from_string (popup->priv->ui_manager,
                                       ui_popup, -1, NULL);

    popup->priv->monitor = NULL;
}

static void
cpufreq_popup_class_init (CPUFreqPopupClass *klass)
{
    GObjectClass *g_object_class = G_OBJECT_CLASS (klass);

    g_object_class->finalize = cpufreq_popup_finalize;
}

static void
cpufreq_popup_finalize (GObject *object)
{
    CPUFreqPopup *popup = CPUFREQ_POPUP (object);

    if (popup->priv->ui_manager) {
        g_object_unref (popup->priv->ui_manager);
        popup->priv->ui_manager = NULL;
    }

    if (popup->priv->freqs_group) {
        g_object_unref (popup->priv->freqs_group);
        popup->priv->freqs_group = NULL;
    }

    if (popup->priv->freqs_actions) {
        g_slist_free (popup->priv->freqs_actions);
        popup->priv->freqs_actions = NULL;
    }

    if (popup->priv->govs_group) {
        g_object_unref (popup->priv->govs_group);
        popup->priv->govs_group = NULL;
    }

    if (popup->priv->govs_actions) {
        g_slist_free (popup->priv->govs_actions);
        popup->priv->govs_actions = NULL;
    }

    if (popup->priv->monitor) {
        g_object_unref (popup->priv->monitor);
        popup->priv->monitor = NULL;
    }

    G_OBJECT_CLASS (cpufreq_popup_parent_class)->finalize (object);
}

CPUFreqPopup *
cpufreq_popup_new (void)
{
    CPUFreqPopup *popup;

    popup = CPUFREQ_POPUP (g_object_new (CPUFREQ_TYPE_POPUP,
                                         NULL));

    return popup;
}

/* Public methods */
void
cpufreq_popup_set_monitor (CPUFreqPopup   *popup,
                           CPUFreqMonitor *monitor)
{
    g_return_if_fail (CPUFREQ_IS_POPUP (popup));
    g_return_if_fail (CPUFREQ_IS_MONITOR (monitor));

    if (popup->priv->monitor == monitor)
        return;

    if (popup->priv->monitor)
        g_object_unref (popup->priv->monitor);
    popup->priv->monitor = g_object_ref (monitor);
}

void
cpufreq_popup_set_parent (CPUFreqPopup *popup,
                          GtkWidget    *parent)
{
    g_return_if_fail (CPUFREQ_IS_POPUP (popup));
    g_return_if_fail (GTK_IS_WIDGET (parent));

    popup->priv->parent = parent;
}

static void
cpufreq_popup_frequencies_menu_activate (GtkAction    *action,
                                         CPUFreqPopup *popup)
{
    CPUFreqSelector *selector;
    const gchar     *name;
    guint            cpu;
    guint            freq;

    if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
        return;

    selector = cpufreq_selector_get_default ();

    cpu = cpufreq_monitor_get_cpu (popup->priv->monitor);
    name = gtk_action_get_name (action);
    freq = (guint) atoi (name + strlen ("Frequency"));

    cpufreq_selector_set_frequency_async (selector, cpu, freq);
}

static void
cpufreq_popup_governors_menu_activate (GtkAction    *action,
                                       CPUFreqPopup *popup)
{
    CPUFreqSelector *selector;
    const gchar     *name;
    guint            cpu;
    const gchar     *governor;

    if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
        return;

    selector = cpufreq_selector_get_default ();

    cpu = cpufreq_monitor_get_cpu (popup->priv->monitor);
    name = gtk_action_get_name (action);
    governor = name + strlen ("Governor");

    cpufreq_selector_set_governor_async (selector, cpu, governor);
}

static void
cpufreq_popup_menu_add_action (CPUFreqPopup   *popup,
                               const gchar    *menu,
                               GtkActionGroup *action_group,
                               const gchar    *action_name,
                               const gchar    *label,
                               gboolean        sensitive)
{
    GtkToggleAction *action;
    gchar           *name;

    name = g_strdup_printf ("%s%s", menu, action_name);

    action = g_object_new (GTK_TYPE_RADIO_ACTION,
                           "name", name,
                           "label", label,
                           NULL);

    gtk_action_set_sensitive (GTK_ACTION (action), sensitive);

    gtk_radio_action_set_group (GTK_RADIO_ACTION (action), popup->priv->radio_group);
    popup->priv->radio_group = gtk_radio_action_get_group (GTK_RADIO_ACTION (action));

    if (g_ascii_strcasecmp (menu, "Frequency") == 0) {
        popup->priv->freqs_actions = g_slist_prepend (popup->priv->freqs_actions,
                                                      (gpointer) action);

        g_signal_connect (action, "activate",
                          G_CALLBACK (cpufreq_popup_frequencies_menu_activate),
                          popup);
    } else if (g_ascii_strcasecmp (menu, "Governor") == 0) {
        popup->priv->govs_actions = g_slist_prepend (popup->priv->govs_actions,
                                                     (gpointer) action);

        g_signal_connect (action, "activate",
                          G_CALLBACK (cpufreq_popup_governors_menu_activate),
                          popup);
    }

    gtk_action_group_add_action (action_group, GTK_ACTION (action));
    g_object_unref (action);

    g_free (name);
}

static void
frequencies_menu_create_actions (CPUFreqPopup *popup)
{
    GList *available_freqs;

    available_freqs = cpufreq_monitor_get_available_frequencies (popup->priv->monitor);

    while (available_freqs) {
        const gchar *text;
        gchar       *freq_text;
        gchar       *label;
        gchar       *unit;
        gint         freq;

        text = (const gchar *) available_freqs->data;
        freq = atoi (text);

        freq_text = cpufreq_utils_get_frequency_label (freq);
        unit = cpufreq_utils_get_frequency_unit (freq);

        label = g_strdup_printf ("%s %s", freq_text, unit);
        g_free (freq_text);
        g_free (unit);

        cpufreq_popup_menu_add_action (popup,
                                       "Frequency",
                                       popup->priv->freqs_group,
                                       text, label, TRUE);
        g_free (label);

        available_freqs = g_list_next (available_freqs);
    }
}

static void
governors_menu_create_actions (CPUFreqPopup *popup)
{
    GList *available_govs;

    available_govs = cpufreq_monitor_get_available_governors (popup->priv->monitor);
    available_govs = g_list_sort (available_govs, (GCompareFunc)g_ascii_strcasecmp);

    while (available_govs) {
        const gchar *governor;
        gchar       *label;

        governor = (const gchar *) available_govs->data;
        if (g_ascii_strcasecmp (governor, "userspace") == 0) {
            popup->priv->show_freqs = TRUE;
            available_govs = g_list_next (available_govs);
            continue;
        }

        label = g_strdup (governor);
        label[0] = g_ascii_toupper (label[0]);

        cpufreq_popup_menu_add_action (popup,
                                       "Governor",
                                       popup->priv->govs_group,
                                       governor, label, TRUE);
        g_free (label);

        available_govs = g_list_next (available_govs);
    }
}

static void
cpufreq_popup_build_ui (CPUFreqPopup *popup,
                        GSList       *actions,
                        const gchar  *menu_path)
{
    GSList *l = NULL;

    for (l = actions; l && l->data; l = g_slist_next (l)) {
        GtkAction *action;
        gchar     *name = NULL;
        gchar     *label = NULL;

        action = (GtkAction *) l->data;

        g_object_get (G_OBJECT (action),
                      "name", &name,
                      "label", &label,
                      NULL);

        gtk_ui_manager_add_ui (popup->priv->ui_manager,
                               popup->priv->merge_id,
                               menu_path,
                               label, name,
                               GTK_UI_MANAGER_MENUITEM,
                               FALSE);

        g_free (name);
        g_free (label);
    }
}

static void
cpufreq_popup_build_frequencies_menu (CPUFreqPopup *popup,
                                      const gchar  *path)
{
    if (!popup->priv->freqs_group) {
        GtkActionGroup *action_group;

        action_group = gtk_action_group_new ("FreqsActions");
        popup->priv->freqs_group = action_group;
        gtk_action_group_set_translation_domain (action_group, NULL);

        frequencies_menu_create_actions (popup);
        popup->priv->freqs_actions = g_slist_reverse (popup->priv->freqs_actions);
        gtk_ui_manager_insert_action_group (popup->priv->ui_manager,
                                            action_group, 0);
    }

    cpufreq_popup_build_ui (popup,
                            popup->priv->freqs_actions,
                            path);
}

static void
cpufreq_popup_build_governors_menu (CPUFreqPopup *popup,
                                    const gchar  *path)
{
    if (!popup->priv->govs_group) {
        GtkActionGroup *action_group;

        action_group = gtk_action_group_new ("GovsActions");
        popup->priv->govs_group = action_group;
        gtk_action_group_set_translation_domain (action_group, NULL);

        governors_menu_create_actions (popup);
        popup->priv->govs_actions = g_slist_reverse (popup->priv->govs_actions);
        gtk_ui_manager_insert_action_group (popup->priv->ui_manager,
                                            action_group, 1);
    }

    cpufreq_popup_build_ui (popup,
                            popup->priv->govs_actions,
                            path);
}

static void
cpufreq_popup_build_menu (CPUFreqPopup *popup)
{
    if (popup->priv->merge_id > 0) {
        gtk_ui_manager_remove_ui (popup->priv->ui_manager,
                                  popup->priv->merge_id);
        gtk_ui_manager_ensure_update (popup->priv->ui_manager);
    }

    popup->priv->merge_id = gtk_ui_manager_new_merge_id (popup->priv->ui_manager);

    cpufreq_popup_build_frequencies_menu (popup, FREQS_PLACEHOLDER_PATH);
    cpufreq_popup_build_governors_menu (popup, GOVS_PLACEHOLDER_PATH);

    gtk_action_group_set_visible (popup->priv->freqs_group,
                                  popup->priv->show_freqs);
}

static void
cpufreq_popup_menu_set_active_action (CPUFreqPopup   *popup,
                                      GtkActionGroup *action_group,
                                      const gchar    *prefix,
                                      const gchar    *item)
{
    gchar      name[128];
    GtkAction *action;

    g_snprintf (name, sizeof (name), "%s%s", prefix, item);
    action = gtk_action_group_get_action (action_group, name);

    /* gtk_action_group_get_action can return NULL with frequencies (userspace governor)
     * when the CPU does not actually stay locked to that exact frequency but rather to a
     * frequency range. Since cpufreq_monitor_get_frequency gets the realtime frequency, this
     * may not match any named frequency and then returns NULL. Return when this happens to
     * avoid segfaults
     */
    if (action == NULL)
        return;

    g_signal_handlers_block_by_func (action,
                                     cpufreq_popup_frequencies_menu_activate,
                                     popup);
    g_signal_handlers_block_by_func (action,
                                     cpufreq_popup_governors_menu_activate,
                                     popup);

    gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);

    g_signal_handlers_unblock_by_func (action,
                                       cpufreq_popup_frequencies_menu_activate,
                                       popup);
    g_signal_handlers_unblock_by_func (action,
                                       cpufreq_popup_governors_menu_activate,
                                       popup);
}

static void
cpufreq_popup_menu_set_active (CPUFreqPopup *popup)
{
    const gchar *governor;

    governor = cpufreq_monitor_get_governor (popup->priv->monitor);

    if (g_ascii_strcasecmp (governor, "userspace") == 0) {
        gchar *active;
        guint  freq;

        freq = cpufreq_monitor_get_frequency (popup->priv->monitor);
        active = g_strdup_printf ("%d", freq);
        cpufreq_popup_menu_set_active_action (popup,
                                              popup->priv->freqs_group,
                                              "Frequency", active);
        g_free (active);
    } else {
        cpufreq_popup_menu_set_active_action (popup,
                                              popup->priv->govs_group,
                                              "Governor", governor);
    }
}

GtkWidget *
cpufreq_popup_get_menu (CPUFreqPopup *popup)
{
    GtkWidget *menu;

    g_return_val_if_fail (CPUFREQ_IS_POPUP (popup), NULL);
    g_return_val_if_fail (CPUFREQ_IS_MONITOR (popup->priv->monitor), NULL);

    if (!cpufreq_utils_selector_is_available ())
        return NULL;

    if (popup->priv->need_build) {
        cpufreq_popup_build_menu (popup);
        popup->priv->need_build = FALSE;
    }

    cpufreq_popup_menu_set_active (popup);

    menu = gtk_ui_manager_get_widget (popup->priv->ui_manager,
                                      "/CPUFreqSelectorPopup");

    return menu;
}
