/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** 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 Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

#include "zbxsysinfo.h"
#include "../sysinfo.h"

#include "zbxregexp.h"

#include <sys/sensors.h>

#ifdef HAVE_SENSORDEV

static void	count_sensor(int do_task, const struct sensor *sensor, double *aggr, int *cnt)
{
	double	value = sensor->value;

	switch (sensor->type)
	{
		case SENSOR_TEMP:
			value = (value - 273150000) / 1000000;
			break;
		case SENSOR_VOLTS_DC:
		case SENSOR_VOLTS_AC:
		case SENSOR_AMPS:
		case SENSOR_LUX:
			value /= 1000000;
			break;
		case SENSOR_TIMEDELTA:
			value /= 1000000000;
			break;
		default:
			break;
	}

	(*cnt)++;

	switch (do_task)
	{
		case ZBX_DO_ONE:
			*aggr = value;
			break;
		case ZBX_DO_AVG:
			*aggr += value;
			break;
		case ZBX_DO_MAX:
			*aggr = (1 == *cnt ? value : MAX(*aggr, value));
			break;
		case ZBX_DO_MIN:
			*aggr = (1 == *cnt ? value : MIN(*aggr, value));
			break;
	}
}

static int	get_device_sensors(int do_task, int *mib, const struct sensordev *sensordev, const char *name,
		double *aggr, int *cnt)
{
	if (ZBX_DO_ONE == do_task)
	{
		int		i, len = 0;
		struct sensor	sensor;
		size_t		slen = sizeof(sensor);

		for (i = 0; i < SENSOR_MAX_TYPES; i++)
		{
			if (0 == strncmp(name, sensor_type_s[i], len = strlen(sensor_type_s[i])))
				break;
		}

		if (i == SENSOR_MAX_TYPES)
			return FAIL;

		if (SUCCEED != zbx_is_uint31(name + len, &mib[4]))
			return FAIL;

		mib[3] = i;

		if (-1 == sysctl(mib, 5, &sensor, &slen, NULL, 0))
			return FAIL;

		count_sensor(do_task, &sensor, aggr, cnt);
	}
	else
	{
		for (int i = 0; i < SENSOR_MAX_TYPES; i++)
		{
			for (int j = 0; j < sensordev->maxnumt[i]; j++)
			{
				char		human[64];
				struct sensor	sensor;
				size_t		slen = sizeof(sensor);

				zbx_snprintf(human, sizeof(human), "%s%d", sensor_type_s[i], j);

				if (NULL == zbx_regexp_match(human, name, NULL))
					continue;

				mib[3] = i;
				mib[4] = j;

				if (-1 == sysctl(mib, 5, &sensor, &slen, NULL, 0))
					return FAIL;

				count_sensor(do_task, &sensor, aggr, cnt);
			}
		}
	}

	return SUCCEED;
}

int	get_sensor(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char	*device, *name, *function;
	int	do_task, mib[5], dev, cnt = 0;
	double	aggr = 0;

	if (3 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		return SYSINFO_RET_FAIL;
	}

	device = get_rparam(request, 0);
	name = get_rparam(request, 1);
	function = get_rparam(request, 2);

	if (NULL == device || '\0' == *device)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		return SYSINFO_RET_FAIL;
	}

	if (NULL == name || '\0' == *name)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
		return SYSINFO_RET_FAIL;
	}

	if (NULL == function || '\0' == *function)
		do_task = ZBX_DO_ONE;
	else if (0 == strcmp(function, "avg"))
		do_task = ZBX_DO_AVG;
	else if (0 == strcmp(function, "max"))
		do_task = ZBX_DO_MAX;
	else if (0 == strcmp(function, "min"))
		do_task = ZBX_DO_MIN;
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
		return SYSINFO_RET_FAIL;
	}

	mib[0] = CTL_HW;
	mib[1] = HW_SENSORS;

	for (dev = 0;; dev++)
	{
		struct sensordev	sensordev;
		size_t			sdlen = sizeof(sensordev);

		mib[2] = dev;

		if (-1 == sysctl(mib, 3, &sensordev, &sdlen, NULL, 0))
		{
			if (errno == ENXIO)
				continue;
			if (errno == ENOENT)
				break;

			SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain system information: %s",
					zbx_strerror(errno)));
			return SYSINFO_RET_FAIL;
		}

		if ((ZBX_DO_ONE == do_task && 0 == strcmp(sensordev.xname, device)) ||
				(ZBX_DO_ONE != do_task && NULL != zbx_regexp_match(sensordev.xname, device, NULL)))
		{
			if (SUCCEED != get_device_sensors(do_task, mib, &sensordev, name, &aggr, &cnt))
			{
				SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain sensor information."));
				return SYSINFO_RET_FAIL;
			}
		}
	}

	if (0 == cnt)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain sensor information."));
		return SYSINFO_RET_FAIL;
	}

	if (ZBX_DO_AVG == do_task)
		SET_DBL_RESULT(result, aggr / cnt);
	else
		SET_DBL_RESULT(result, aggr);

	return SYSINFO_RET_OK;
}

#else

int	get_sensor(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	SET_MSG_RESULT(result, zbx_strdup(NULL, "Agent was compiled without support for \"sensordev\" structure."));

	return SYSINFO_RET_FAIL;
}

#endif	/* HAVE_SENSORDEV */
