/*
 * 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 "tuning.h"
#include "tunable.h"
#include "runtime.h"
#include <utility>
#include <unistd.h>
#include <format>

#include "../lib.h"
#include "../devices/runtime_pm.h"

runtime_tunable::runtime_tunable(const std::string &path, const std::string &bus, const std::string &dev, const std::string &port) : tunable("", 0.4, _("Good"), _("Bad"), _("Unknown"))
{
	runtime_path = std::format("{}/power/control", path);


	desc = pt_format(_("Runtime PM for {} device {}"), bus, dev);
	if (!device_has_runtime_pm(path)) {
		desc = pt_format(_("{} device {} has no runtime power management"), bus, dev);
	}

	if (bus == "pci") {
		std::string filename;
		uint16_t vendor = 0, device = 0;
		std::string content;

		content = read_sysfs_string(std::format("/sys/bus/{}/devices/{}/vendor", bus, dev));
		if (!content.empty()) {
			try {
				vendor = std::stoul(content, nullptr, 16);
			} catch (...) {}
		}


		content = read_sysfs_string(std::format("/sys/bus/{}/devices/{}/device", bus, dev));
		if (!content.empty()) {
			try {
				device = std::stoul(content, nullptr, 16);
			} catch (...) {}
		}

		if (vendor && device) {
			if (!device_has_runtime_pm(path)) {
				desc = pt_format(_("PCI Device {} has no runtime power management"), pci_id_to_name(vendor, device));
			} else {
				desc = pt_format(_("Runtime PM for PCI Device {}"), pci_id_to_name(vendor, device));
			}
		}

		if (path.find("ata") != std::string::npos) {
			desc = pt_format(_("Runtime PM for port {} of PCI device: {}"), port, pci_id_to_name(vendor, device));
		}

		if (path.find("block") != std::string::npos) {
			desc = pt_format(_("Runtime PM for disk {}"), port);
		}

	}
	toggle_good = std::format("echo 'auto' > '{}';", runtime_path);
	toggle_bad = std::format("echo 'on' > '{}';", runtime_path);
}

int runtime_tunable::good_bad(void)
{
	const std::string content = read_sysfs_string(runtime_path);

	if (content == "auto")
		return TUNE_GOOD;
	if (content == "on")
		return TUNE_BAD;

	return TUNE_BAD;
}

void runtime_tunable::toggle(void)
{
	const int good = good_bad();

	if (good == TUNE_GOOD) {
		write_sysfs(runtime_path, "on");
		return;
	}

	write_sysfs(runtime_path, "auto");
}


void add_runtime_tunables(const std::string &bus)
{
	std::string filename, port;
	constexpr int max_ports = 32;
	int count = 0;

	for (const auto &dev : list_directory(std::format("/sys/bus/{}/devices/", bus))) {

		filename = std::format("/sys/bus/{}/devices/{}/power/control", bus, dev);

		if (pt_access(filename, R_OK) != 0)
			continue;


		filename = std::format("/sys/bus/{}/devices/{}", bus, dev);

		auto runtime = std::make_unique<runtime_tunable>(filename, bus, dev, "");

		if (!device_has_runtime_pm(filename))
			all_untunables.push_back(std::move(runtime));
		else
			all_tunables.push_back(std::move(runtime));

		for (int i=0; i < max_ports; i++) {
			port = std::format("ata{}", i);
			filename = std::format("/sys/bus/{}/devices/{}/{}/power/control", bus, dev, port);

			if (pt_access(filename, R_OK) != 0)
				continue;

			filename = std::format("/sys/bus/{}/devices/{}/{}", bus, dev, port);
			auto runtime_ahci_port = std::make_unique<runtime_tunable>(filename, bus, dev, port);

			if (!device_has_runtime_pm(filename))
				all_untunables.push_back(std::move(runtime_ahci_port));
			else
				all_tunables.push_back(std::move(runtime_ahci_port));
		}

		for (char blk = 'a'; blk <= 'z'; blk++)
		{
			if (count != 0)
				break;

			filename = std::format("/sys/block/sd{}/device/power/control", blk);

			if (pt_access(filename, R_OK) != 0)
				continue;

			port = std::format("sd{}", blk);
			filename = std::format("/sys/block/{}/device", port);
			auto runtime_ahci_disk = std::make_unique<runtime_tunable>(filename, bus, dev, port);
			if (!device_has_runtime_pm(filename))
				all_untunables.push_back(std::move(runtime_ahci_disk));
			else
				all_tunables.push_back(std::move(runtime_ahci_disk));
		}
		count = 1;

	}
}

void runtime_tunable::collect_json_fields(std::string &_js)
{
    tunable::collect_json_fields(_js);
    JSON_FIELD(runtime_path);
}
