/*
 * MATE CPUFreq Applet
 * Copyright (C) 2006 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 <config.h>

#include <glib.h>
#include <gtk/gtk.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include "cpufreq-utils.h"

#ifdef HAVE_POLKIT
#include <gio/gio.h>
#endif /* HAVE_POLKIT */

guint
cpufreq_utils_get_n_cpus (void)
{
	gint   mcpu = -1;
	gchar *file = NULL;
	static guint n_cpus = 0;

	if (n_cpus > 0)
		return n_cpus;

	do {
		if (file)
			g_free (file);
		mcpu ++;
		file = g_strdup_printf ("/sys/devices/system/cpu/cpu%d", mcpu);
	} while (g_file_test (file, G_FILE_TEST_EXISTS));
	g_free (file);

	if (mcpu >= 0) {
		n_cpus = (guint)mcpu;
		return mcpu;
	}

	mcpu = -1;
	file = NULL;
	do {
		if (file)
			g_free (file);
		mcpu ++;
		file = g_strdup_printf ("/proc/sys/cpu/%d", mcpu);
	} while (g_file_test (file, G_FILE_TEST_EXISTS));
	g_free (file);

	if (mcpu >= 0) {
		n_cpus = (guint)mcpu;
		return mcpu;
	}

	n_cpus = 1;
	
	return 1;
}

void
cpufreq_utils_display_error (const gchar *message,
			     const gchar *secondary)
{
	GtkWidget *dialog;

	g_return_if_fail (message != NULL);

	dialog = gtk_message_dialog_new (NULL,
					 GTK_DIALOG_DESTROY_WITH_PARENT,
					 GTK_MESSAGE_ERROR,
					 GTK_BUTTONS_OK,
					 "%s", message);
	if (secondary) {
		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
							  "%s", secondary);
	}
	
	gtk_window_set_title (GTK_WINDOW (dialog), ""); /* as per HIG */
	gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), TRUE);
	g_signal_connect (G_OBJECT (dialog),
			  "response",
			  G_CALLBACK (gtk_widget_destroy), NULL);
	gtk_widget_show (dialog);
}

#ifdef HAVE_POLKIT
#define CACHE_VALIDITY_SEC 2

static gboolean
selector_is_available (void)
{
	GDBusProxy             *proxy;
	static GDBusConnection *system_bus = NULL;
	GError                 *error = NULL;
	GVariant               *reply;
	gboolean                result;

	if (!system_bus) {
		system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
		if (!system_bus) {
			g_warning ("%s", error->message);
			g_error_free (error);

			return FALSE;
		}
	}

	proxy = g_dbus_proxy_new_sync (system_bus,
	                               G_DBUS_PROXY_FLAGS_NONE,
	                               NULL,
	                               "org.mate.CPUFreqSelector",
	                               "/org/mate/cpufreq_selector/selector",
	                               "org.mate.CPUFreqSelector",
	                               NULL,
	                               &error);

	if (!proxy) {
		g_warning ("%s", error->message);
		g_error_free (error);

		return FALSE;
	}

	reply = g_dbus_proxy_call_sync (proxy,
	                                "CanSet",
	                                NULL,
	                                G_DBUS_CALL_FLAGS_NONE,
	                                -1,
	                                NULL,
	                                &error);

	if (!reply) {
		g_warning ("Error calling org.mate.CPUFreqSelector.CanSet: %s", error->message);
		g_error_free (error);
		result = FALSE;
	} else {
		g_variant_get (reply, "(b)", &result);
		g_variant_unref (reply);
	}

	g_object_unref (proxy);

	return result;
}

gboolean
cpufreq_utils_selector_is_available (void)
{
	static gboolean cache = FALSE;
	static time_t   last_refreshed = 0;
	time_t          now;

	time (&now);
	if (ABS (now - last_refreshed) > CACHE_VALIDITY_SEC) {
		cache = selector_is_available ();
		last_refreshed = now;
	}

	return cache;
}
#else /* !HAVE_POLKIT */
gboolean
cpufreq_utils_selector_is_available (void)
{
	struct stat *info;
	gchar       *path = NULL;

	path = g_find_program_in_path ("cpufreq-selector");
	if (!path)
		return FALSE;

	if (geteuid () == 0) {
		g_free (path);
		return TRUE;
	}

	info = (struct stat *) g_malloc (sizeof (struct stat));

	if ((lstat (path, info)) != -1) {
		if ((info->st_mode & S_ISUID) && (info->st_uid == 0)) {
			g_free (info);
			g_free (path);

			return TRUE;
		}
	}

	g_free (info);
	g_free (path);

	return FALSE;
}
#endif /* HAVE_POLKIT_MATE */

gchar *
cpufreq_utils_get_frequency_label (guint freq)
{
	gint divisor;
	
	if (freq > 999999) /* freq (kHz) */
		divisor = (1000 * 1000);
	else
		divisor = 1000;

	if (((freq % divisor) == 0) || divisor == 1000) /* integer */
		return g_strdup_printf ("%d", freq / divisor);
	else /* float */
		return g_strdup_printf ("%3.2f", ((gfloat)freq / divisor));
}

gchar *
cpufreq_utils_get_frequency_unit (guint freq)
{
	if (freq > 999999) /* freq (kHz) */
		return g_strdup ("GHz");
	else
		return g_strdup ("MHz");
}

gboolean
cpufreq_utils_governor_is_automatic (const gchar *governor)
{
	g_return_val_if_fail (governor != NULL, FALSE);
	
	if (g_ascii_strcasecmp (governor, "userspace") == 0)
		return FALSE;

	return TRUE;
}

gboolean
cpufreq_file_get_contents (const gchar *filename,
			   gchar      **contents,
			   gsize       *length,
			   GError     **error)
{
	gint     fd;
	GString *buffer = NULL;
	gchar   *display_filename;
	
	g_return_val_if_fail (filename != NULL, FALSE);
	g_return_val_if_fail (contents != NULL, FALSE);

	display_filename = g_filename_display_name (filename);

	*contents = NULL;
	if (length)
		*length = 0;

	fd = open (filename, O_RDONLY);
	if (fd < 0) {
		gint save_errno = errno;

		g_set_error (error,
			     G_FILE_ERROR,
			     g_file_error_from_errno (save_errno),
			     "Failed to open file '%s': %s",
			     display_filename,
			     g_strerror (save_errno));
		g_free (display_filename);

		return FALSE;
	}

	while (TRUE) {
		ssize_t bytes_read;
		gchar  buf[1024];
		
		bytes_read = read (fd, buf, sizeof (buf));
		if (bytes_read < 0) { /* Error */
			if (errno != EINTR) {
				gint save_errno = errno;
				
				g_set_error (error,
					     G_FILE_ERROR,
					     g_file_error_from_errno (save_errno),
					     "Failed to read from file '%s': %s",
					     display_filename,
					     g_strerror (save_errno));

				if (buffer)
					g_string_free (buffer, TRUE);
				
				g_free (display_filename);
				close (fd);
				
				return FALSE;
			}
		} else if (bytes_read == 0) { /* EOF */
			break;
		} else {
			if (!buffer)
				buffer = g_string_sized_new (bytes_read);
			buffer = g_string_append_len (buffer, buf, bytes_read);
		}
	}

	g_free (display_filename);

	if (buffer) {
		*contents = g_string_free (buffer, FALSE);

		if (length)
			*length = strlen (*contents);
	}

	close (fd);

	return TRUE;
}
