/*
 * Copyright 2010, Intel Corporation
 *
 * This file is part of PowerTOP
 *
 * This program file 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; version 2 of the License.
 *
 * This program 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 program in a file named COPYING; if not, write to the
 * Free Software Foundation, Inc,
 * 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 * or just google for it.
 *
 * Authors:
 *	Arjan van de Ven <arjan@linux.intel.com>
 */
#include "runtime_pm.h"

#include <string.h>

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <limits.h>

#include "../parameters/parameters.h"
#include "../lib.h"

#include <iostream>
#include <fstream>

runtime_pmdevice::runtime_pmdevice(const char *_name, const char *path) : device()
{
	pt_strcpy(sysfs_path, path);
	register_sysfs_path(sysfs_path);
	pt_strcpy(name, _name);
	snprintf(humanname, sizeof(humanname), "runtime-%s", _name);

	index = get_param_index(humanname);
	r_index = get_result_index(humanname);

	before_suspended_time = 0;
	before_active_time = 0;
	after_suspended_time = 0;
	after_active_time = 0;

	register_parameter(humanname);
}

void runtime_pmdevice::start_measurement(void)
{
	char filename[PATH_MAX];
	ifstream file;

	before_suspended_time = 0;
	before_active_time = 0;
        after_suspended_time = 0;
	after_active_time = 0;

	snprintf(filename, sizeof(filename), "%s/power/runtime_suspended_time", sysfs_path);
	file.open(filename, ios::in);
	if (!file)
		return;
	file >> before_suspended_time;
	file.close();

	snprintf(filename, sizeof(filename), "%s/power/runtime_active_time", sysfs_path);
	file.open(filename, ios::in);
	if (!file)
		return;
	file >> before_active_time;
	file.close();
}

void runtime_pmdevice::end_measurement(void)
{
	char filename[PATH_MAX];
	ifstream file;

	snprintf(filename, sizeof(filename), "%s/power/runtime_suspended_time", sysfs_path);
	file.open(filename, ios::in);
	if (!file)
		return;
	file >> after_suspended_time;
	file.close();

	snprintf(filename, sizeof(filename), "%s/power/runtime_active_time", sysfs_path);
	file.open(filename, ios::in);
	if (!file)
		return;
	file >> after_active_time;
	file.close();
}

double runtime_pmdevice::utilization(void) /* percentage */
{
	double d;
	d = 100 * (after_active_time - before_active_time) / (0.0001 + after_active_time - before_active_time + after_suspended_time - before_suspended_time);

	if (d < 0.00)
		d = 0.0;
	if (d > 99.9)
		d = 100.0;
	return d;
}

const char * runtime_pmdevice::device_name(void)
{
	return name;
}

const char * runtime_pmdevice::human_name(void)
{
	return humanname;
}


double runtime_pmdevice::power_usage(struct result_bundle *result, struct parameter_bundle *bundle)
{
	double power;
	double factor;
	double util;

	power = 0;

	factor = get_parameter_value(index, bundle);
	util = get_result_value(r_index, result);
        power += util * factor / 100.0;

	return power;
}

void runtime_pmdevice::set_human_name(char *_name)
{
	pt_strcpy(humanname, _name);
}


int device_has_runtime_pm(const char *sysfs_path)
{
	char filename[PATH_MAX];
	ifstream file;
	unsigned long value;

	snprintf(filename, sizeof(filename), "%s/power/runtime_suspended_time", sysfs_path);
	file.open(filename, ios::in);
	if (!file)
		return 0;
	file >> value;
	file.close();
	if (value)
		return 1;

	snprintf(filename, sizeof(filename), "%s/power/runtime_active_time", sysfs_path);
	file.open(filename, ios::in);
	if (!file)
		return 0;
	file >> value;
	file.close();
	if (value)
		return 1;

	return 0;
}

static void do_bus(const char *bus)
{
	/* /sys/bus/pci/devices/0000\:00\:1f.0/power/runtime_suspended_time */

	struct dirent *entry;
	DIR *dir;
	char filename[PATH_MAX];

	snprintf(filename, sizeof(filename), "/sys/bus/%s/devices/", bus);
	dir = opendir(filename);
	if (!dir)
		return;
	while (1) {
		ifstream file;
		class runtime_pmdevice *dev;
		entry = readdir(dir);

		if (!entry)
			break;
		if (entry->d_name[0] == '.')
			continue;

		snprintf(filename, sizeof(filename), "/sys/bus/%s/devices/%s", bus, entry->d_name);
		dev = new class runtime_pmdevice(entry->d_name, filename);

		if (strcmp(bus, "i2c") == 0) {
			string devname;
			char dev_name[4096];
			bool is_adapter = false;

			snprintf(filename, sizeof(filename), "/sys/bus/%s/devices/%s/new_device", bus, entry->d_name);
			if (access(filename, W_OK) == 0)
				is_adapter = true;

			snprintf(filename, sizeof(filename), "/sys/bus/%s/devices/%s/name", bus, entry->d_name);
			file.open(filename, ios::in);
			if (file) {
				getline(file, devname);
				file.close();
			}

			snprintf(dev_name, sizeof(dev_name), _("I2C %s (%s): %s"), (is_adapter ? _("Adapter") : _("Device")), entry->d_name, devname.c_str());
			dev->set_human_name(dev_name);
		}

		if (strcmp(bus, "pci") == 0) {
			uint16_t vendor = 0, device = 0;

			snprintf(filename, sizeof(filename), "/sys/bus/%s/devices/%s/vendor", bus, entry->d_name);

			file.open(filename, ios::in);
			if (file) {
				file >> hex >> vendor;
				file.close();
			}


			snprintf(filename, sizeof(filename), "/sys/bus/%s/devices/%s/device", bus, entry->d_name);
			file.open(filename, ios::in);
			if (file) {
				file >> hex >> device;
				file.close();
			}

			if (vendor && device) {
				char devname[4096];
				snprintf(devname, sizeof(devname), _("PCI Device: %s"),
					pci_id_to_name(vendor, device, filename, 4095));
				dev->set_human_name(devname);
			}
		}
		all_devices.push_back(dev);
	}
	closedir(dir);
}

void create_all_runtime_pm_devices(void)
{
	do_bus("pci");
	do_bus("spi");
	do_bus("platform");
	do_bus("i2c");
}
