/*
 * 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 <iostream>
#include <fstream>
#include <algorithm>
#include <format>

#include "calibrate.h"
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
#include <cmath>
#include <sys/types.h>
#include <cerrno>
#include <climits>

#include "../parameters/parameters.h"
extern "C" {
#include "../tuning/iw.h"
}

#include <map>
#include <vector>
#include <string>


static std::vector<std::string> usb_devices;
static std::vector<std::string> rfkill_devices;
static std::vector<std::string> backlight_devices;
static std::vector<std::string> scsi_link_devices;
static int blmax;

static std::map<std::string, std::string> saved_sysfs;


static volatile int stop_measurement;

static int wireless_PS;


static void save_sysfs(const std::string &filename)
{
	saved_sysfs[filename] = read_sysfs_string(filename);
}

static void restore_all_sysfs(void)
{
	std::map<std::string, std::string>::iterator it;

	for (it = saved_sysfs.begin(); it != saved_sysfs.end(); it++)
		write_sysfs(it->first, it->second);

	set_wifi_power_saving("wlan0", wireless_PS);
}

static void find_all_usb_callback(const std::string &d_name)
{
	std::string filename;

	filename = std::format("/sys/bus/usb/devices/{}/power/active_duration", d_name);
	if (pt_access(filename, R_OK) != 0)
		return;

	std::string vendor_id = read_file_content(std::format("/sys/bus/usb/devices/{}/idVendor", d_name));
	if (!vendor_id.empty()) {
		vendor_id.erase(std::remove(vendor_id.begin(), vendor_id.end(), '\n'), vendor_id.end());
		if (vendor_id == "1d6b")
			return;
	}

	filename = std::format("/sys/bus/usb/devices/{}/power/control", d_name);
	save_sysfs(filename);
	usb_devices.push_back(filename);
}

static void find_all_usb(void)
{
	process_directory("/sys/bus/usb/devices/", find_all_usb_callback);
}

static void suspend_all_usb_devices(void)
{
	for (const auto &dev : usb_devices)
		write_sysfs(dev, "auto\n");
}

static void find_all_rfkill_callback(const std::string &d_name)
{
	const std::string filename = std::format("/sys/class/rfkill/{}/soft", d_name);
	if (pt_access(filename, R_OK) != 0)
		return;
	save_sysfs(filename);
	rfkill_devices.push_back(filename);
}

static void find_all_rfkill(void)
{
	process_directory("/sys/class/rfkill/", find_all_rfkill_callback);
}

static void rfkill_all_radios(void)
{
	for (const auto &dev : rfkill_devices)
		write_sysfs(dev, "1\n");
}
static void unrfkill_all_radios(void)
{
	for (const auto &dev : rfkill_devices)
		write_sysfs(dev, "0\n");
}

static void find_backlight_callback(const std::string &d_name)
{
	std::string filename;
	filename = std::format("/sys/class/backlight/{}/brightness", d_name);
	if (pt_access(filename, R_OK) != 0)
		return;

	save_sysfs(filename);
	backlight_devices.push_back(filename);
	filename = std::format("/sys/class/backlight/{}/max_brightness", d_name);
	blmax = read_sysfs(filename);
}

static void find_backlight(void)
{
	process_directory("/sys/class/backlight/", find_backlight_callback);
}

static void lower_backlight(void)
{
	for (const auto &dev : backlight_devices)
		write_sysfs(dev, "0\n");
}

static void find_scsi_link_callback(const std::string &d_name)
{
	const std::string filename = std::format("/sys/class/scsi_host/{}/link_power_management_policy", d_name);
	if (pt_access(filename, R_OK) != 0)
		return;

	save_sysfs(filename);
	scsi_link_devices.push_back(filename);
}

static void find_scsi_link(void)
{
	process_directory("/sys/class/scsi_host/", find_scsi_link_callback);
}

static void set_scsi_link(const std::string &state)
{
	for (const auto &dev : scsi_link_devices)
		write_sysfs(dev, state);
}


static void *burn_cpu([[maybe_unused]] void *dummy)
{
	volatile double d = 1.1;

	while (!stop_measurement) {
		d = pow(d, 1.0001);
	}
	return nullptr;
}

static void *burn_cpu_wakeups(void *dummy)
{
	struct timespec tm;

	while (!stop_measurement) {
		tm.tv_sec = 0;
		tm.tv_nsec = (unsigned long)(uintptr_t)dummy;
		nanosleep(&tm, nullptr);
	}
	return nullptr;
}

static void *burn_disk([[maybe_unused]] void *dummy)
{
	char buffer[64*1024];
	char filename[] = "/tmp/powertop.XXXXXX";

	const int fd = mkstemp(filename);

	if (fd < 0) {
		printf(_("Cannot create temp file\n"));
		return nullptr;
	}

	while (!stop_measurement) {
		lseek(fd, 0, SEEK_SET);
		if (write(fd, buffer, 64*1024) == -1)
			fprintf(stderr, _("Error: %s\n"), strerror(errno));
		fdatasync(fd);
	}
	unlink(filename);
	return nullptr;
}


static void cpu_calibration(const int threads)
{
	int i;
	std::vector<pthread_t> thr_vec;

	printf(_("Calibrating: CPU usage on %i threads\n"), threads);

	stop_measurement = 0;
	for (i = 0; i < threads; i++) {
		pthread_t thr;
		if (pthread_create(&thr, nullptr, burn_cpu, nullptr) != 0) {
			fprintf(stderr, _("Failed to create calibration thread\n"));
			return;
		}
		thr_vec.push_back(thr);
	}

	one_measurement(15, 15, "");
	stop_measurement = 1;
	sleep(1);
	for (auto &thr : thr_vec)
		pthread_join(thr, nullptr);
}

static void wakeup_calibration(const unsigned long interval)
{
	pthread_t thr;

	printf(_("Calibrating: CPU wakeup power consumption\n"));

	stop_measurement = 0;

	if (pthread_create(&thr, nullptr, burn_cpu_wakeups, (void *)(uintptr_t)interval) != 0) {
		fprintf(stderr, _("Failed to create wakeup calibration thread\n"));
		return;
	}

	one_measurement(15, 15, "");
	stop_measurement = 1;
	sleep(1);
	pthread_join(thr, nullptr);
}

static void usb_calibration(void)
{
	/* chances are one of the USB devices is bluetooth; unrfkill first */
	unrfkill_all_radios();
	printf(_("Calibrating USB devices\n"));
	for (const auto &dev : usb_devices) {
		printf(_(".... device %s \n"), dev.c_str());
		suspend_all_usb_devices();
		write_sysfs(dev, "on\n");
		one_measurement(15, 15, "");
		suspend_all_usb_devices();
		sleep(3);
	}
	rfkill_all_radios();
	sleep(4);
}

static void rfkill_calibration(void)
{
	printf(_("Calibrating radio devices\n"));
	for (const auto &dev : rfkill_devices) {
		printf(_(".... device %s \n"), dev.c_str());
		rfkill_all_radios();
		write_sysfs(dev, "0\n");
		one_measurement(15, 15, "");
		rfkill_all_radios();
		sleep(3);
	}
	for (const auto &dev : rfkill_devices) {
		printf(_(".... device %s \n"), dev.c_str());
		unrfkill_all_radios();
		write_sysfs(dev, "1\n");
		one_measurement(15, 15, "");
		unrfkill_all_radios();
		sleep(3);
	}
	rfkill_all_radios();
}
/* Write "On" or "Off" to every enabled DRM connector's dpms sysfs attribute.
 * This is display-server agnostic: it works on X11, Wayland, and headless.
 * Returns true if at least one connector was successfully written. */
static bool sysfs_dpms_set(const std::string &state)
{
	bool any_set = false;

	for (const auto &card : list_directory("/sys/class/drm")) {
		/* Only process card entries without a '-' (e.g. "card0", not "card0-HDMI-A-1") */
		if (!card.starts_with("card") || card.find('-') != std::string::npos)
			continue;

		const std::string card_path = std::format("/sys/class/drm/{}", card);
		for (const auto &entry : list_directory(card_path)) {
			/* Connector entries have the form "card0-HDMI-A-1" */
			if (!entry.starts_with("card") || entry.find('-') == std::string::npos)
				continue;

			/* Skip connectors that are not physically connected */
			const std::string enabled = read_sysfs_string(
				std::format("{}/{}/enabled", card_path, entry));
			if (enabled != "enabled")
				continue;

			write_sysfs(std::format("{}/{}/dpms", card_path, entry), state + "\n");
			any_set = true;
		}
	}
	return any_set;
}

/* Set display DPMS state.  Try sysfs first (works on X11, Wayland, headless);
 * fall back to xset for legacy X11 systems where sysfs writes are not
 * supported by the driver.  Failures in both paths are silently ignored. */
static void set_dpms(const std::string &state)
{
	if (sysfs_dpms_set(state))
		return;

	/* xset fallback: drop the hardcoded DISPLAY=:0 so the calling session's
	 * display (if any) is used; suppress all output to avoid blocking on
	 * missing X11 or Wayland-only setups. */
	system(std::format("xset dpms force {} >/dev/null 2>&1", state).c_str());
}

static void backlight_calibration(void)
{
	printf(_("Calibrating backlight\n"));
	for (const auto &dev : backlight_devices) {
		std::string str;
		printf(_(".... device %s \n"), dev.c_str());
		lower_backlight();
		one_measurement(15, 15, "");
		str = std::format("{}\n", blmax / 4);
		write_sysfs(dev, str);
		one_measurement(15, 15, "");

		str = std::format("{}\n", blmax / 2);
		write_sysfs(dev, str);
		one_measurement(15, 15, "");

		str = std::format("{}\n", 3 * blmax / 4 );
		write_sysfs(dev, str);
		one_measurement(15, 15, "");

		str = std::format("{}\n", blmax);
		write_sysfs(dev, str);
		one_measurement(15, 15, "");
		lower_backlight();
		sleep(1);
	}
	printf(_("Calibrating idle\n"));
	set_dpms("off");
	one_measurement(15, 15, "");
	set_dpms("on");
}

static void idle_calibration(void)
{
	printf(_("Calibrating idle\n"));
	set_dpms("off");
	one_measurement(15, 15, "");
	set_dpms("on");
}


static void disk_calibration(void)
{
	pthread_t thr;

	printf(_("Calibrating: disk usage \n"));

	set_scsi_link("min_power");

	stop_measurement = 0;
	if (pthread_create(&thr, nullptr, burn_disk, nullptr) != 0) {
		fprintf(stderr, _("Failed to create disk calibration thread\n"));
		return;
	}

	one_measurement(15, 15, "");
	stop_measurement = 1;
	sleep(1);
	pthread_join(thr, nullptr);


}


void calibrate(void)
{
	find_all_usb();
	find_all_rfkill();
	find_backlight();
	find_scsi_link();
	wireless_PS = get_wifi_power_saving("wlan0");

        save_sysfs("/sys/module/snd_hda_intel/parameters/power_save");

	std::cout << _("Starting PowerTOP power estimate calibration \n");
	suspend_all_usb_devices();
	rfkill_all_radios();
	lower_backlight();
	set_wifi_power_saving("wlan0", 1);

	sleep(4);


	idle_calibration();
	disk_calibration();
	backlight_calibration();

	write_sysfs("/sys/module/snd_hda_intel/parameters/power_save", "1\n");
	cpu_calibration(1);
	cpu_calibration(4);
	wakeup_calibration(10000);
	wakeup_calibration(100000);
	wakeup_calibration(1000000);
	set_wifi_power_saving("wlan0", 0);
	usb_calibration();
	rfkill_calibration();

	std::cout << _("Finishing PowerTOP power estimate calibration \n");

	restore_all_sysfs();
        learn_parameters(300, 1);
	printf(_("Parameters after calibration:\n"));
	dump_parameter_bundle();
	save_parameters("saved_parameters.powertop");
        save_all_results("saved_results.powertop");

}
