/*
 * 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.h>
#include <glib/gi18n.h>

#include <string.h>
#include <stdlib.h>

#include "cpufreq-monitor-sysfs.h"
#include "cpufreq-utils.h"

enum {
    SCALING_MAX,
    SCALING_MIN,
    GOVERNOR,
    CPUINFO_MAX,
    SCALING_SETSPEED,
    SCALING_CUR_FREQ,
    N_FILES
};

static gboolean cpufreq_monitor_sysfs_run                       (CPUFreqMonitor *monitor);
static GList   *cpufreq_monitor_sysfs_get_available_frequencies (CPUFreqMonitor *monitor);
static GList   *cpufreq_monitor_sysfs_get_available_governors   (CPUFreqMonitor *monitor);

static gchar   *cpufreq_sysfs_read                              (const gchar    *path,
                                                                 GError        **error);

/* /sys/devices/system/cpu/cpu[0]/cpufreq/scaling_max_freq
 * /sys/devices/system/cpu/cpu[0]/cpufreq/scaling_min_freq
 * /sys/devices/system/cpu/cpu[0]/cpufreq/scaling_governor
 * /sys/devices/system/cpu/cpu[0]/cpufreq/cpuinfo_max_freq
 * /sys/devices/system/cpu/cpu[0]/cpufreq/scaling_setspeed (userspace)
 * /sys/devices/system/cpu/cpu[0]/cpufreq/scaling_cur_freq (new governors)
 */
const gchar *monitor_sysfs_files[] = {
    "scaling_max_freq",
    "scaling_min_freq",
    "scaling_governor",
    "cpuinfo_max_freq",
    "scaling_setspeed",
    "scaling_cur_freq",
    NULL
};

#define CPUFREQ_SYSFS_BASE_PATH "/sys/devices/system/cpu/cpu%u/cpufreq/%s"

G_DEFINE_TYPE (CPUFreqMonitorSysfs, cpufreq_monitor_sysfs, CPUFREQ_TYPE_MONITOR)

static void
cpufreq_monitor_sysfs_init (CPUFreqMonitorSysfs *monitor)
{
}

static GObject *
cpufreq_monitor_sysfs_constructor (GType                  type,
                                   guint                  n_construct_properties,
                                   GObjectConstructParam *construct_params)
{
    GObject *object;
    gchar   *path;
    gchar   *frequency;
    gint     max_freq;
    guint    cpu;
    GError  *error = NULL;

    object =
        G_OBJECT_CLASS (cpufreq_monitor_sysfs_parent_class)
            ->constructor (type,
                           n_construct_properties,
                           construct_params);

    g_object_get (G_OBJECT (object),
                  "cpu", &cpu,
                  NULL);

    path = g_strdup_printf (CPUFREQ_SYSFS_BASE_PATH,
                            cpu, monitor_sysfs_files[CPUINFO_MAX]);

    frequency = cpufreq_sysfs_read (path, &error);
    if (!frequency) {
        g_warning ("%s", error->message);
        g_error_free (error);
        max_freq = -1;
    } else {
        max_freq = atoi (frequency);
    }

    g_free (path);
    g_free (frequency);

    g_object_set (G_OBJECT (object),
                  "max-frequency", max_freq,
                  NULL);

    return object;
}

static void
cpufreq_monitor_sysfs_class_init (CPUFreqMonitorSysfsClass *klass)
{
    GObjectClass        *object_class = G_OBJECT_CLASS (klass);
    CPUFreqMonitorClass *monitor_class = CPUFREQ_MONITOR_CLASS (klass);

    object_class->constructor = cpufreq_monitor_sysfs_constructor;

    monitor_class->run = cpufreq_monitor_sysfs_run;
    monitor_class->get_available_frequencies = cpufreq_monitor_sysfs_get_available_frequencies;
    monitor_class->get_available_governors = cpufreq_monitor_sysfs_get_available_governors;
}

CPUFreqMonitor *
cpufreq_monitor_sysfs_new (guint cpu)
{
    CPUFreqMonitorSysfs *monitor;

    monitor = g_object_new (CPUFREQ_TYPE_MONITOR_SYSFS,
                            "cpu", cpu, NULL);

    return CPUFREQ_MONITOR (monitor);
}

static gchar *
cpufreq_sysfs_read (const gchar *path,
                    GError     **error)
{
    gchar *buffer = NULL;

    if (!cpufreq_file_get_contents (path, &buffer, NULL, error)) {
        return NULL;
    }

    return g_strchomp (buffer);
}

static gboolean
cpufreq_sysfs_cpu_is_online (guint cpu)
{
    gchar   *path;
    gboolean retval;

    path = g_strdup_printf ("/sys/devices/system/cpu/cpu%u/", cpu);
    retval = g_file_test (path, G_FILE_TEST_IS_DIR);
    g_free (path);

    return retval;
}

static gboolean
cpufreq_monitor_sysfs_run (CPUFreqMonitor *monitor)
{
    guint   cpu;
    gchar  *frequency;
    gchar  *governor;
    gchar  *path;
    GError *error = NULL;

    g_object_get (G_OBJECT (monitor),
                  "cpu", &cpu,
                  NULL);

    path = g_strdup_printf (CPUFREQ_SYSFS_BASE_PATH,
                            cpu, monitor_sysfs_files[GOVERNOR]);
    governor = cpufreq_sysfs_read (path, &error);
    if (!governor) {
        gboolean retval = FALSE;

        /* Check whether it failed because
         * cpu is not online.
         */
        if (!cpufreq_sysfs_cpu_is_online (cpu)) {
            g_object_set (G_OBJECT (monitor), "online", FALSE, NULL);
            retval = TRUE;
        } else {
            g_warning ("%s", error->message);
        }

        g_error_free (error);
        g_free (path);

        return retval;
    }

    g_free (path);

    if (g_ascii_strcasecmp (governor, "userspace") == 0) {
        path = g_strdup_printf (CPUFREQ_SYSFS_BASE_PATH,
                                cpu, monitor_sysfs_files[SCALING_SETSPEED]);
    } else if (g_ascii_strcasecmp (governor, "powersave") == 0) {
        path = g_strdup_printf (CPUFREQ_SYSFS_BASE_PATH,
                                cpu, monitor_sysfs_files[SCALING_MIN]);
    } else if (g_ascii_strcasecmp (governor, "performance") == 0) {
        path = g_strdup_printf (CPUFREQ_SYSFS_BASE_PATH,
                                cpu, monitor_sysfs_files[SCALING_MAX]);
    } else { /* Ondemand, Conservative, ... */
        path = g_strdup_printf (CPUFREQ_SYSFS_BASE_PATH,
                                cpu, monitor_sysfs_files[SCALING_CUR_FREQ]);
    }

    frequency = cpufreq_sysfs_read (path, &error);
    if (!frequency) {
        g_warning ("%s", error->message);
        g_error_free (error);
        g_free (path);
        g_free (governor);

        return FALSE;
    }

    g_free (path);

    g_object_set (G_OBJECT (monitor),
                  "online", TRUE,
                  "governor", governor,
                  "frequency", atoi (frequency),
                  NULL);

    g_free (governor);
    g_free (frequency);

    return TRUE;
}

static gint
compare (gconstpointer a, gconstpointer b)
{
    gint aa, bb;

    aa = atoi ((gchar *) a);
    bb = atoi ((gchar *) b);

    if (aa == bb)
        return 0;
    else if (aa > bb)
        return -1;
    else
        return 1;
}

static GList *
cpufreq_monitor_sysfs_get_available_frequencies (CPUFreqMonitor *monitor)
{
    gchar  *path;
    GList  *list = NULL;
    gchar **frequencies = NULL;
    gint    i;
    guint   cpu;
    gchar  *buffer = NULL;
    GError *error = NULL;

    g_object_get (G_OBJECT (monitor),
                  "cpu", &cpu, NULL);

    path = g_strdup_printf (CPUFREQ_SYSFS_BASE_PATH, cpu,
                            "scaling_available_frequencies");

    if (!cpufreq_file_get_contents (path, &buffer, NULL, &error)) {
        g_warning ("%s", error->message);
        g_error_free (error);

        g_free (path);

        return NULL;
    }

    g_free (path);

    buffer = g_strchomp (buffer);
    frequencies = g_strsplit (buffer, " ", -1);

    i = 0;
    while (frequencies[i]) {
        if (!g_list_find_custom (list, frequencies[i], compare))
            list = g_list_prepend (list, g_strdup (frequencies[i]));
        i++;
    }

    g_strfreev (frequencies);
    g_free (buffer);

    return g_list_sort (list, compare);
}

static GList *
cpufreq_monitor_sysfs_get_available_governors (CPUFreqMonitor *monitor)
{
    gchar   *path;
    GList   *list = NULL;
    gchar  **governors = NULL;
    gint     i;
    guint    cpu;
    gchar   *buffer = NULL;
    GError  *error = NULL;

    g_object_get (G_OBJECT (monitor),
                  "cpu", &cpu, NULL);

    path = g_strdup_printf (CPUFREQ_SYSFS_BASE_PATH, cpu,
                            "scaling_available_governors");

    if (!cpufreq_file_get_contents (path, &buffer, NULL, &error)) {
        g_warning ("%s", error->message);
        g_error_free (error);

        g_free (path);

        return NULL;
    }

    g_free (path);

    buffer = g_strchomp (buffer);

    governors = g_strsplit (buffer, " ", -1);

    i = 0;
    while (governors[i] != NULL) {
        list = g_list_prepend (list, g_strdup (governors[i]));
        i++;
    }

    g_strfreev (governors);
    g_free (buffer);

    return list;
}
