/*
 * Copyright 2020-2021 Stefan Ott
 *
 * This file is part of warmluft.
 *
 * Warmluft 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Foobar 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 Foobar.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <sys/sysinfo.h>
#include <sys/types.h>

#include "hwmon.h"

#define MAX_PROCS 128

static pid_t pids[MAX_PROCS];
static int num_procs;

static void
cleanup()
{
	int i;

	for (i=0; i < num_procs; i++)
	{
		kill(pids[i], SIGINT);
	}
}

static int
read_temp(const char *dev)
{
	int fh, len;
	char buf[16];

	fh = open(dev, 0);
	if (fh == -1)
	{
		perror(dev);
		return -1;
	}

	len = read(fh, buf, sizeof(buf) - 1);
	if (len == -1)
	{
		perror(dev);
		close(fh);
		return -1;
	}

	close(fh);
	buf[len] = 0;

	return atoi(buf);
}

static void
log_print(double t, int fh)
{
	char buf[32];

	printf("Current temperature: %0.1f°C\n", t);

	if (fh >= 0)
	{
		snprintf(buf, sizeof(buf), "%0.1f\n", t);
		if (write(fh, buf, strlen(buf)) == -1)
		{
			perror("write");
		}
	}
}

static void
run_child()
{
	void *mem;

	while (1)
	{
		mem = malloc(0x100000);
		free(mem);
	}
}

static int
check_temperature(struct hwmon_sensor *sensor, int out_fd)
{
	double t;
	char path[PATH_MAX];

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
	snprintf(path, sizeof(path), "%s/%s", sensor->device->path,
							sensor->path_in);
#pragma GCC diagnostic pop

	t = read_temp(path) / 1000;
	if (t < 0)
	{
		return 1; /* invalid */
	}
	log_print(t, out_fd);

	if (t >= sensor->temp_max)
	{
		return 1; /* all done */
	}

	return 0;
}

static int
run_parent(struct hwmon_sensor *sensor, const char *fout, int timeout)
{
	int fd;
	long t_start, t_elapsed;
	struct sysinfo info;

	if (sysinfo(&info) != 0)
	{
		perror("sysinfo");
		return 1;
	}
	t_start = info.uptime;

	printf("Maximum temperature is %0.1f°C\n", sensor->temp_max);

	if (fout)
	{
		fd = open(fout, O_RDWR|O_CREAT|O_SYNC|O_TRUNC, S_IRUSR|S_IWUSR);
		if (fd == -1)
		{
			perror(fout);
			return 1;
		}
		printf("Writing data to %s\n", fout);
	}
	else
	{
		fd = -1;
		printf("Not writing data to file\n");
	}

	while (1)
	{
		if (sysinfo(&info) != 0)
		{
			perror("sysinfo");
		}
		t_elapsed = (info.uptime - t_start) / 60;

		/* abort if max temperature is reached */
		if (check_temperature(sensor, fd) != 0)
		{
			printf("Maximum temperature reached\n");
			printf("Total time elapsed: %ldm\n", t_elapsed);
			break;
		}

		/* abort if timeout is reached */
		if (t_elapsed >= timeout)
		{
			printf("Terminating after %dm\n", timeout);
			break;
		}

		sleep(60);
	}

	if (fd >= 0)
	{
		close(fd);
	}
	return 0;
}

static void
help()
{
	printf("Syntax: warmluft [options]\n\n");
	printf("Options:\n\n");
	printf("  -d DEV   Device to read temperature from\n");
	printf("  -f FILE  Write data to FILE\n");
	printf("  -m MAX   Stop when reaching MAX temperature\n");
	printf("  -n NUM   Run NUM processes\n");
	printf("  -t TIME  Abort after TIME minutes\n");
	printf("  -h       Show help\n");
	printf("\n");
	printf("warmluft %s\n", VERSION);
	printf("https://code.ott.net/warmluft/\n");
	printf("\n");
}

int
main(int argc, char **argv)
{
	int c, i, res, timeout;
	double max_temp;
	char *fout;
	struct hwmon_device *devices;
	struct hwmon_sensor *sensor;
	pid_t pid;

	num_procs = get_nprocs();
	fout = NULL;
	max_temp = 0;
	timeout = INT_MAX;

	devices = hwmon_load_devices();
	sensor = hwmon_best_sensor(devices);

	while ((c = getopt(argc, argv, "d:f:m:n:t:h")) != -1)
	{
		switch (c)
		{
		case 'd':
			sensor = hwmon_dev_by_path(devices, optarg);
			break;
		case 'f':
			fout = optarg;
			break;
		case 'n':
			num_procs = atoi(optarg);
			break;
		case 'm':
			max_temp = atof(optarg);
			break;
		case 't':
			timeout = atoi(optarg);
			break;
		case 'h':
			help();
			return EXIT_SUCCESS;
		default:
			fprintf(stderr, "Unknown option: %c\n", c);
			return EXIT_FAILURE;
		}
	}

	if (num_procs > MAX_PROCS)
	{
		fprintf(stderr, "Too many processes: %d\n", num_procs);
		fprintf(stderr, "Maximum supported: %d\n", MAX_PROCS);
		hwmon_free_devices(devices);
		return 1;
	}

	if (!sensor)
	{
		fprintf(stderr, "No temperature sensor found, aborting\n");
		hwmon_free_devices(devices);
		return EXIT_FAILURE;
	}

	if (max_temp)
	{
		sensor->temp_max = max_temp;
	}

	printf("Using %s on %s sensor at %s/%s\n", sensor->label,
		sensor->device->name, sensor->device->path, sensor->path_in);
	printf("Starting %d child process(es)\n", num_procs);

	if (sensor->temp_max <= 0)
	{
		fprintf(stderr, "No maximum temperature found, aborting\n");
		hwmon_free_devices(devices);
		return EXIT_FAILURE;
	}

	for (i = 0; i < num_procs; i++)
	{
		pid = fork();

		if (pid == 0)
		{
			run_child();
		}
		else
		{
			pids[i] = pid;
		}
	}

	atexit(cleanup);
	signal(SIGINT, exit);

	res = run_parent(sensor, fout, timeout);
	hwmon_free_devices(devices);
	return res;
}
