/*
 * Copyright (c) 2008-2010  Mike Massonnet <mmassonnet@xfce.org>
 * Copyright (c) 2006  Johannes Zellner <webmaster@nebulon.de>
 *
 * 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 of the License, or
 * (at your option) any later version.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "task-manager.h"

static gushort _cpu_count = 0;
static gulong jiffies_total_delta = 0;

gboolean
get_memory_usage (guint64 *memory_total, guint64 *memory_available, guint64 *memory_free, guint64 *memory_cache, guint64 *memory_buffers, guint64 *swap_total, guint64 *swap_free)
{
	FILE *file;
	gchar *filename = "/proc/meminfo";
	guint64 mem_total = 0, mem_free = 0, mem_avail = 0, mem_cached = 0, mem_buffers = 0, swp_total = 0, swp_free = 0;

	if ((file = fopen (filename, "r")) != NULL)
	{
		gint found = 0;
		gchar buffer[256];
		while (found < 7 && fgets (buffer, sizeof (buffer), file) != NULL)
		{
			found += !mem_total ? sscanf (buffer, "MemTotal:\t%lu kB", &mem_total) : 0;
			found += !mem_free ? sscanf (buffer, "MemFree:\t%lu kB", &mem_free) : 0;
			found += !mem_avail ? sscanf (buffer, "MemAvailable:\t%lu kB", &mem_avail) : 0; /* Since Linux 3.14 */
			found += !mem_cached ? sscanf (buffer, "Cached:\t%lu kB", &mem_cached) : 0;
			found += !mem_buffers ? sscanf (buffer, "Buffers:\t%lu kB", &mem_buffers) : 0;
			found += !swp_total ? sscanf (buffer, "SwapTotal:\t%lu kB", &swp_total) : 0;
			found += !swp_free ? sscanf (buffer, "SwapFree:\t%lu kB", &swp_free) : 0;
		}
		fclose (file);
	}

	if (mem_avail == 0)
	{
		mem_avail = mem_free + mem_cached + mem_buffers;
	}

	*memory_total = mem_total * 1024;
	*memory_available = mem_avail * 1024;
	*memory_free = mem_free * 1024;
	*memory_cache = mem_cached * 1024;
	*memory_buffers = mem_buffers * 1024;
	*swap_total = swp_total * 1024;
	*swap_free = swp_free * 1024;

	return file ? TRUE : FALSE;
}

gboolean
get_cpu_usage (gushort *cpu_count, gfloat *cpu_user, gfloat *cpu_system)
{
	FILE *file;
	gchar *filename = "/proc/stat";
	gchar buffer[1024];
	static gulong jiffies_user = 0, jiffies_system = 0, jiffies_total = 0;
	static gulong jiffies_user_old = 0, jiffies_system_old = 0, jiffies_total_old = 0;
	gulong user = 0, user_nice = 0, system = 0, idle = 0;

	if ((file = fopen (filename, "r")) == NULL)
		return FALSE;
	if (fgets (buffer, sizeof (buffer), file) == NULL)
	{
		fclose (file);
		return FALSE;
	}

	sscanf (buffer, "cpu\t%lu %lu %lu %lu", &user, &user_nice, &system, &idle);

	if (_cpu_count == 0)
	{
		while (fgets (buffer, sizeof (buffer), file) != NULL)
		{
			if (buffer[0] != 'c' && buffer[1] != 'p' && buffer[2] != 'u')
				break;
			_cpu_count += 1;
		}
		if (_cpu_count == 0)
			_cpu_count = 1;
	}

	fclose (file);

	jiffies_user_old = jiffies_user;
	jiffies_system_old = jiffies_system;
	jiffies_total_old = jiffies_total;

	jiffies_user = user + user_nice;
	jiffies_system = system;
	jiffies_total = jiffies_user + jiffies_system + idle;

	*cpu_user = *cpu_system = 0.0f;
	if (jiffies_total > jiffies_total_old)
	{
		jiffies_total_delta = jiffies_total - jiffies_total_old;
		*cpu_user = (((jiffies_user - jiffies_user_old) * 100.0f) / (float)jiffies_total_delta);
		*cpu_system = (((jiffies_system - jiffies_system_old) * 100.0f) / (float)jiffies_total_delta);
	}
	*cpu_count = _cpu_count;

	return TRUE;
}

static inline int
get_pagesize (void)
{
	static int pagesize = 0;
	if (pagesize == 0)
	{
		pagesize = sysconf (_SC_PAGESIZE);
		if (pagesize == 0)
			pagesize = 4096;
	}
	return pagesize;
}

static gboolean
get_task_cmdline (Task *task)
{
	FILE *file;
	gchar filename[96];
	gint i;
	gint c;

	snprintf (filename, sizeof (filename), "/proc/%i/cmdline", task->pid);
	if ((file = fopen (filename, "r")) == NULL)
		return FALSE;

	/* Read full command byte per byte until EOF */
	for (i = 0; (c = fgetc (file)) != EOF && i < (gint)sizeof (task->cmdline) - 1; i++)
		task->cmdline[i] = (c == '\0') ? ' ' : (gchar)c;
	task->cmdline[i] = '\0';
	if (i > 0 && task->cmdline[i - 1] == ' ')
		task->cmdline[i - 1] = '\0';
	fclose (file);

	/* Kernel processes don't have a cmdline nor an exec path */
	if (i == 0)
	{
		size_t len = strlen (task->name);
		g_strlcpy (&task->cmdline[1], task->name, len + 1);
		task->cmdline[0] = '[';
		task->cmdline[len + 1] = ']';
		task->cmdline[len + 2] = '\0';
	}

	return TRUE;
}

static void
get_cpu_percent (GPid pid, gulong jiffies_user, gfloat *cpu_user, gulong jiffies_system, gfloat *cpu_system)
{
	static GHashTable *hash_cpu_user = NULL;
	static GHashTable *hash_cpu_system = NULL;
	gulong jiffies_user_old, jiffies_system_old;

	if (hash_cpu_user == NULL)
	{
		hash_cpu_user = g_hash_table_new (NULL, NULL);
		hash_cpu_system = g_hash_table_new (NULL, NULL);
	}

	jiffies_user_old = GPOINTER_TO_UINT (g_hash_table_lookup (hash_cpu_user, GINT_TO_POINTER (pid)));
	jiffies_system_old = GPOINTER_TO_UINT (g_hash_table_lookup (hash_cpu_system, GINT_TO_POINTER (pid)));
	g_hash_table_insert (hash_cpu_user, GINT_TO_POINTER (pid), GUINT_TO_POINTER (jiffies_user));
	g_hash_table_insert (hash_cpu_system, GINT_TO_POINTER (pid), GUINT_TO_POINTER (jiffies_system));

	if (jiffies_user < jiffies_user_old || jiffies_system < jiffies_system_old)
		return;

	if (_cpu_count > 0 && jiffies_total_delta > 0)
	{
		*cpu_user = (((jiffies_user - jiffies_user_old) * 100.0f) / (float)jiffies_total_delta);
		*cpu_system = (((jiffies_system - jiffies_system_old) * 100.0f) / (float)jiffies_total_delta);
	}
	else
	{
		*cpu_user = *cpu_system = 0.0f;
	}
}

static gboolean
get_task_details (GPid pid, Task *task)
{
	FILE *file;
	gchar filename[96];
	gchar buffer[1024];

	snprintf (filename, sizeof (filename), "/proc/%d/stat", pid);
	if ((file = fopen (filename, "r")) == NULL)
		return FALSE;
	if (fgets (buffer, sizeof (buffer), file) == NULL)
	{
		fclose (file);
		return FALSE;
	}
	fclose (file);

	memset (task, 0, sizeof (Task));

	/* Scanning the short process name is unreliable with scanf when it contains
	 * spaces, retrieve it manually and fill the buffer */
	{
		gchar *p1, *po, *p2;
		guint i = 0;
		p1 = po = g_strstr_len (buffer, -1, "(");
		p2 = g_strrstr (buffer, ")");
		while (po <= p2)
		{
			if (po > p1 && po < p2)
			{
				task->name[i++] = *po;
				task->name[i] = '\0';
			}
			*po = 'x';
			po++;
		}
	}

	/* Parse the stat file */
	{
		gchar dummy[256];
		gint idummy;
		gulong jiffies_user = 0, jiffies_system = 0;

		sscanf (buffer, "%i %255s %1s %i %i %i %i %i %255s %255s %255s %255s %255s %lu %lu %i %i %i %d %i %i %i %llu %llu %255s %255s %255s %i %255s %255s %255s %255s %255s %255s %255s %255s %255s %255s %i %255s %255s",
			&task->pid, // processid
			dummy, // processname
			task->state, // processstate
			&task->ppid, // parentid
			&idummy, // processs groupid

			&idummy, // session id
			&idummy, // tty id
			&idummy, // tpgid the process group ID of the process running on tty of the process
			dummy, // flags
			dummy, // minflt minor faults the process has maid

			dummy, // cminflt
			dummy, // majflt
			dummy, // cmajflt
			&jiffies_user, // utime the number of jiffies that this process has scheduled in user mode
			&jiffies_system, // stime " system mode

			&idummy, // cutime " waited for children in user mode
			&idummy, // cstime " system mode
			&idummy, // priority (nice value + fifteen)
			&task->prio, // nice range from 19 to -19
			&idummy, // hardcoded 0

			&idummy, // itrealvalue time in jiffies to next SIGALRM send to this process
			&idummy, // starttime jiffies the process startet after system boot
			(unsigned long long *)&task->vsz, // vsize in bytes
			(unsigned long long *)&task->rss, // rss (number of pages in real memory)
			dummy, // rlim limit in bytes for rss

			dummy, // startcode
			dummy, // endcode
			&idummy, // startstack
			dummy, // kstkesp value of esp (stack pointer)
			dummy, // kstkeip value of EIP (instruction pointer)

			dummy, // signal. bitmap of pending signals
			dummy, // blocked: bitmap of blocked signals
			dummy, // sigignore: bitmap of ignored signals
			dummy, // sigcatch: bitmap of catched signals
			dummy, // wchan

			dummy, // nswap
			dummy, // cnswap
			dummy, // exit_signal
			&idummy, // CPU number last executed on
			dummy,

			dummy);

		task->rss *= get_pagesize ();
		get_cpu_percent (task->pid, jiffies_user, &task->cpu_user, jiffies_system, &task->cpu_system);
	}

	/* Parse the status file: it contains the UIDs */
	{
		guint dummy;

		snprintf (filename, sizeof (filename), "/proc/%d/status", pid);
		if ((file = fopen (filename, "r")) == NULL)
			return FALSE;

		while (fgets (buffer, sizeof (buffer), file) != NULL)
		{
			if (sscanf (buffer, "Uid:\t%u\t%u\t%u\t%u", &dummy, &task->uid, &dummy, &dummy) == 1) // UIDs in order: real, effective, saved set, and filesystem
				break;
		}
		fclose (file);
	}

	/* Read the full command line */
	if (!get_task_cmdline (task))
		return FALSE;

	return TRUE;
}

gboolean
get_task_list (GArray *task_list)
{
	GDir *dir;
	const gchar *name;
	GPid pid;
	Task task;

	if ((dir = g_dir_open ("/proc", 0, NULL)) == NULL)
		return FALSE;

	while ((name = g_dir_read_name (dir)) != NULL)
	{
		if ((pid = (GPid)g_ascii_strtoull (name, NULL, 0)) > 0)
		{
			if (get_task_details (pid, &task))
			{
				g_array_append_val (task_list, task);
			}
		}
	}

	g_dir_close (dir);

	g_array_sort (task_list, task_pid_compare_fn);

	return TRUE;
}

gboolean
pid_is_sleeping (GPid pid)
{
	FILE *file;
	gchar filename[96];
	gchar buffer[1024];
	gchar state[2] = { 0 };

	snprintf (filename, sizeof (filename), "/proc/%i/status", pid);
	if ((file = fopen (filename, "r")) == NULL)
		return FALSE;

	while (fgets (buffer, sizeof (buffer), file) != NULL)
	{
		if (sscanf (buffer, "State:\t%1s", state) > 0)
			break;
	}
	fclose (file);

	return (state[0] == 'T') ? TRUE : FALSE;
}
