/*
 * 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 <kstat.h>
#include <errno.h>
#include <sys/sysinfo.h>
#include <stdlib.h>
#include <strings.h>

#include "utils.h"

typedef struct {
	uint32_t user, system, idle, waiting_for_io;
} CPU_TIMES_t;

static void calculate_cpu_times(struct sa_cpu* dst, CPU_TIMES_t* cpu_old_times, CPU_TIMES_t* cpu_new_times) SA_NONNULL;

#define NUMBER_CPUS 1
__thread kstat_ctl_t* cpu_kc;
__thread CPU_TIMES_t cpus;
__thread CPU_TIMES_t diffs;
__thread cpu_stat_t* cpu_stat;

int sa_open_cpu() {
	cpu_kc = kstat_open();
	if (cpu_kc == NULL)
		return ENOSYS;

	bzero(&cpus, sizeof cpus);
	return 0;
}

int sa_close_cpu() {
	if (cpu_kc != NULL)
		kstat_close(cpu_kc);
	return 0;
}

int sa_reset_cpus() {
	kstat_t* ks;
	if (get_ks(cpu_kc, &ks, "cpu_stat", "cpu_stat0") != 0)
		return ENOSYS;

	if (ks->ks_data_size != sizeof(cpu_stat_t))
		return ENOSYS;

	cpu_stat = (cpu_stat_t*) ks->ks_data;
	return 0;
}

int sa_get_cpu(uint16_t index, struct sa_cpu* dst) {
	if (index > 0 || dst == NULL)
		return EINVAL;

	CPU_TIMES_t cpu_new_times;
	cpu_new_times.user = cpu_stat->cpu_sysinfo.cpu[CPU_USER];
	cpu_new_times.system = cpu_stat->cpu_sysinfo.cpu[CPU_KERNEL];
	cpu_new_times.idle = cpu_stat->cpu_sysinfo.cpu[CPU_IDLE];
	cpu_new_times.waiting_for_io = cpu_stat->cpu_syswait.iowait;

	dst->id = 0; //TODO smp..
	calculate_cpu_times(dst, &cpus, &cpu_new_times);
	return 0;
}

int sa_count_cpus(uint16_t* number) {
	if (number == NULL)
		return EINVAL;

	*number = NUMBER_CPUS;
	return 0;
}

int sa_get_cpus(struct sa_cpu* dst, uint16_t dst_size, uint16_t* written) {
	if (dst == NULL || dst_size == 0 || written == NULL)
		return EINVAL;

	sa_get_cpu(0, dst);
	*written = NUMBER_CPUS;
	return 0;
}

static void calculate_cpu_times(struct sa_cpu* dst, CPU_TIMES_t* cpu_old_times, CPU_TIMES_t* cpu_new_times) {
	diffs.user = cpu_new_times->user - cpu_old_times->user;
	diffs.system = cpu_new_times->system - cpu_old_times->system;
	diffs.idle = cpu_new_times->idle - cpu_old_times->idle;
	diffs.waiting_for_io = cpu_new_times->waiting_for_io - cpu_old_times->waiting_for_io;

	uint64_t total = diffs.user + diffs.system + diffs.idle + diffs.waiting_for_io;
	float scale;
	if (total == 0)
		scale = 1;
	else
		scale = (float) total / 100.0;

	dst->user = diffs.user / scale;
	dst->system = diffs.system / scale;
	dst->idle = diffs.idle / scale;
	dst->waiting_for_io = diffs.waiting_for_io / scale;

	*cpu_old_times = *cpu_new_times;
}
