/*
 * libsysactivity
 * http://sourceforge.net/projects/libsysactivity/
 * Copyright (c) 2009, 2010 Carlos Olmedo Escobar <carlos.olmedo.e@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include <time.h>

#include "libsysactivity.h"

int error = 0;
pid_t self_pid;

void print_process_activity(struct sa_process_activity* activity) {
#ifdef SA_PROCESS_PID
	printf("pid %d, ", (int) activity->pid);
#endif
#ifdef SA_PROCESS_STATE
	printf("state %d, ", activity->state);
#endif
#ifdef SA_PROCESS_USER_TIME
	printf("user_time %"PRIu64", ", activity->user_time);
#endif
#ifdef SA_PROCESS_SYS_TIME
	printf("sys_time %"PRIu64", ", activity->sys_time);
#endif
#ifdef SA_PROCESS_THREADS
	printf("threads %"PRIu32", ", activity->threads);
#endif
#ifdef SA_PROCESS_VM_SIZE
	printf("vm_size %"PRIu32", ", activity->vm_size);
#endif
#ifdef SA_PROCESS_RSS
	printf("rss %d ", activity->rss);
#endif
	printf("\n");
}

void print_process(struct sa_process* process) {
#ifdef SA_PROCESS_PID
	printf("pid %d, ", (int) process->pid);
#endif
#ifdef SA_PROCESS_UID
	printf("uid %d, ", process->uid);
#endif
#ifdef SA_PROCESS_GID
	printf("gid %d, ", process->gid);
#endif
#ifdef SA_PROCESS_FILENAME
	printf("filename %s, ", process->filename);
#endif
#ifdef SA_PROCESS_CMDLINE
	printf("cmdline %s, ", process->cmdline);
#endif
#ifdef SA_PROCESS_PARENT_PID
	printf("parent pid %d, ", (int) process->parent_pid);
#endif
#ifdef SA_PROCESS_PGRP
	printf("pgrp %d, ", (int) process->pgrp);
#endif
#ifdef SA_PROCESS_SID
	printf("sid %d, ", (int) process->sid);
#endif
#ifdef SA_PROCESS_TTY
	printf("tty %d, ", (int) process->tty);
#endif
#ifdef SA_PROCESS_NICE
	printf("nice %d, ", process->nice);
#endif
#ifdef SA_PROCESS_START_TIME
	printf("start_time %"PRIu64", ", process->start_time);
#endif

	print_process_activity(&process->activity);
}

void test_process_activity(struct sa_process_activity* activity) {
#if !defined(__DragonFly__) && defined(SA_PROCESS_THREADS) // gcc -E -dM -x c /dev/null
	if (activity->threads == 0) {
		printf("%s:%d ERROR: The number of threads of the process %d is zero.\n", __FILE__, __LINE__, (int) activity->pid);
		error = 1;
	}
#endif
}

void test_process(struct sa_process* process) {
#ifdef SA_PROCESS_FILENAME
#ifdef __NetBSD__
	size_t len = strlen(process->filename);
#else
	size_t len = strnlen(process->filename, SA_PROCESS_FILENAME);
#endif
	if (len == 0) {
		printf("%s:%d ERROR: The length of the filename of the process %d is zero\n", __FILE__, __LINE__, (int) process->pid);
		error = 1;
	}
#endif
#ifdef SA_PROCESS_PARENT_PID
	if (process->parent_pid > 1) {
		struct sa_process parent;
		int ret = sa_get_process(process->parent_pid, &parent);
		if (ret != 0 && sa_get_process(process->pid, process) == 0) { // Checking if the current process still exists
			printf("%s:%d ERROR: Can't retrieve the parent (%d) of the process %d\n", __FILE__, __LINE__, (int) parent.pid, (int) process->pid);
			error = 1;
		}
	}
#endif
#ifdef SA_PROCESS_NICE
	if (process->nice < -20 || process->nice > 19) {
		printf("%s:%d ERROR: Nice value of the process %d is out of range (%d).\n", __FILE__, __LINE__, (int) process->pid, process->nice);
		error = 1;
	}
#endif
	if (process->pid != process->activity.pid) {
		printf("%s:%d ERROR: The PIDs of the both structures are different.\n", __FILE__, __LINE__);
		error = 1;
	}
	test_process_activity(&process->activity);
}

void* get_process(void* arg) {
	int ret;
#ifdef SA_OPEN_PROCESS
	ret = sa_open_process();
	if (ret != 0) {
		printf("%s:%d ERROR: sa_open_process(): %s\n", __FILE__, __LINE__, strerror(ret));
		exit(EXIT_FAILURE);
	}
#endif

	pid_t pid;
	uint32_t written, size;
	ret = sa_get_processes_ids(&pid, 1, &written);
	if (ret != ENOMEM || written != 1) { // The value returned has to be ENOMEM because i asked for just one pid
		printf("%s:%d ERROR: sa_get_processes_ids(): %s\n", __FILE__, __LINE__, strerror(ret));
		exit(EXIT_FAILURE);
	}

	ret = sa_count_processes(&size);
	if (ret != 0) {
		printf("%s:%d ERROR: sa_count_processes(): %s\n", __FILE__, __LINE__, strerror(ret));
		exit(EXIT_FAILURE);
	}
	pid_t* pids = malloc(size * sizeof(pid_t));
	ret = sa_get_processes_ids(pids, size, &written);
	if (ret != 0) {
		printf("%s:%d ERROR: sa_get_processes_ids(): %s\n", __FILE__, __LINE__, strerror(ret));
		exit(EXIT_FAILURE);
	}
	uint32_t i;
	printf("pids:");
	for (i = 0; i < written; i++)
		printf(" %d", (int) pids[i]);
	printf("\n");

	struct sa_process proc_beginning;
	ret = sa_get_process(self_pid, &proc_beginning);
	if (ret != 0) {
		printf("%s:%d ERROR: sa_get_process(): %s\n", __FILE__, __LINE__, strerror(ret));
		exit(EXIT_FAILURE);
	}
	print_process(&proc_beginning);
	printf("\n");
	test_process(&proc_beginning);

	struct sa_process proc;
	struct sa_process_activity proc_activity;
	for (i = 0; i < 6; i++) {
		ret = sa_count_processes(&size);
		if (ret != 0 || size < 2) {
			printf("%s:%d ERROR: sa_count_processes(): %s\n", __FILE__, __LINE__, strerror(ret));
			exit(EXIT_FAILURE);
		}
		printf("there are %d processes\n", (int) size);

		pids = realloc(pids, size * sizeof(pid_t));
		ret = sa_get_processes_ids(pids, size, &written);
		if (ret != 0) {
			printf("%s:%d ERROR: sa_get_processes_ids(): %s\n", __FILE__, __LINE__, strerror(ret));
			exit(EXIT_FAILURE);
		}

		uint32_t j;
		if (i % 2 == 0) {
			for (j = 0; j < written; j++) {
				ret = sa_get_process(pids[j], &proc);
				if (ret != 0) {
					printf("%s:%d ERROR: sa_get_process(): %s\n", __FILE__, __LINE__, strerror(ret));
					exit(EXIT_FAILURE);
				}
				print_process(&proc);
				test_process(&proc);
			}
		} else {
			for (j = 0; j < written; j++) {
				ret = sa_get_process_activity(pids[written - j - 1], &proc_activity);
				if (ret != 0) {
					printf("%s:%d ERROR: sa_get_process_activity(): %s\n", __FILE__, __LINE__, strerror(ret));
					exit(EXIT_FAILURE);
				}
				print_process_activity(&proc_activity);
				test_process_activity(&proc_activity);
			}
		}

		printf("\n");
		sleep(1);
	}
	free(pids);

#ifdef SA_CLOSE_PROCESS
	ret = sa_close_process();
	if (ret != 0) {
		printf("%s:%d ERROR: sa_close_process(): %s\n", __FILE__, __LINE__, strerror(ret));
		exit(EXIT_FAILURE);
	}
#endif

#ifdef SA_OPEN_PROCESS
	ret = sa_open_process();
	if (ret != 0) {
		printf("%s:%d ERROR: sa_open_process(): %s\n", __FILE__, __LINE__, strerror(ret));
		exit(EXIT_FAILURE);
	}
#endif

#if defined(SA_PROCESS_SYS_TIME) && defined(__linux__)
	if (proc.activity.sys_time <= proc_beginning.activity.sys_time) {
		printf("%s:%d ERROR: The sys_time of the process %d (self process) is not growing\n", __FILE__, __LINE__, (int) self_pid);
		exit(EXIT_FAILURE);
	}
#endif

#ifdef SA_CLOSE_PROCESS
	ret = sa_close_process();
	if (ret != 0) {
		printf("%s:%d ERROR: sa_close_process(): %s\n", __FILE__, __LINE__, strerror(ret));
		exit(EXIT_FAILURE);
	}
#endif
	return NULL;
}

int main() {
	self_pid = getpid();
	pthread_t thread1;
	struct timespec delay;
	delay.tv_sec = 0;
	delay.tv_nsec = 500000000;

	pthread_create(&thread1, NULL, get_process, NULL);
	nanosleep(&delay, NULL);
	get_process(NULL);

	if (error)
		return EXIT_FAILURE;

	return EXIT_SUCCESS;
}
