/*
** 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 "zbxvariant.h"

#include "zbxstr.h"
#include "zbxnum.h"
#include "zbxalgo.h"

ZBX_VECTOR_IMPL(var, zbx_variant_t)

static void	*zbx_variant_data_bin_copy(const void *bin)
{
	zbx_uint32_t		size;
	void	*value_bin;

	memcpy(&size, bin, sizeof(size));
	value_bin = zbx_malloc(NULL, size + sizeof(size));
	memcpy(value_bin, bin, size + sizeof(size));

	return value_bin;
}

void	*zbx_variant_data_bin_create(const void *data, zbx_uint32_t size)
{
	void	*value_bin;

	value_bin = zbx_malloc(NULL, size + sizeof(size));
	memcpy(value_bin, &size, sizeof(size));
	memcpy((unsigned char *)value_bin + sizeof(size), data, size);

	return value_bin;
}

zbx_uint32_t	zbx_variant_data_bin_get(const void *bin, const void ** const data)
{
	zbx_uint32_t	size;

	memcpy(&size, bin, sizeof(zbx_uint32_t));

	if (NULL != data)
		*data = (const unsigned char *)bin + sizeof(size);

	return size;
}

void	zbx_variant_clear(zbx_variant_t *value)
{
	switch (value->type)
	{
		case ZBX_VARIANT_STR:
			zbx_free(value->data.str);
			break;
		case ZBX_VARIANT_BIN:
			zbx_free(value->data.bin);
			break;
		case ZBX_VARIANT_ERR:
			zbx_free(value->data.err);
			break;
		case ZBX_VARIANT_VECTOR:
			if (NULL != value->data.vector)
			{
				if (0 < value->data.vector->values_num)
					zbx_vector_var_clear_ext(value->data.vector);

				zbx_vector_var_destroy(value->data.vector);
			}

			zbx_free(value->data.vector);
			break;
		case ZBX_VARIANT_NONE:
		case ZBX_VARIANT_DBL:
		case ZBX_VARIANT_UI64:
			break;
		default:
			THIS_SHOULD_NEVER_HAPPEN;
			exit(EXIT_FAILURE);
	}

	value->type = ZBX_VARIANT_NONE;
}

/******************************************************************************
 *                                                                            *
 * Setter functions assign passed data and set corresponding variant          *
 * type. Note that for complex data it means the pointer is simply copied     *
 * instead of making a copy of the specified data.                            *
 *                                                                            *
 * The contents of the destination value are not freed. When setting already  *
 * initialized variant it's safer to clear it beforehand, even if the variant *
 * contains primitive value (numeric).                                        *
 *                                                                            *
 ******************************************************************************/

void	zbx_variant_set_str(zbx_variant_t *value, char *text)
{
	value->data.str = text;
	value->type = ZBX_VARIANT_STR;
}

void	zbx_variant_set_dbl(zbx_variant_t *value, double value_dbl)
{
	value->data.dbl = value_dbl;
	value->type = ZBX_VARIANT_DBL;
}

void	zbx_variant_set_ui64(zbx_variant_t *value, zbx_uint64_t value_ui64)
{
	value->data.ui64 = value_ui64;
	value->type = ZBX_VARIANT_UI64;
}

void	zbx_variant_set_none(zbx_variant_t *value)
{
	value->type = ZBX_VARIANT_NONE;
}

void	zbx_variant_set_bin(zbx_variant_t *value, void *value_bin)
{
	value->data.bin = value_bin;
	value->type = ZBX_VARIANT_BIN;
}

void	zbx_variant_set_error(zbx_variant_t *value, char *error)
{
	value->data.err = error;
	value->type = ZBX_VARIANT_ERR;
}

void	zbx_variant_set_vector(zbx_variant_t *value, zbx_vector_var_t *vector)
{
	value->data.vector = vector;
	value->type = ZBX_VARIANT_VECTOR;
}

/******************************************************************************
 *                                                                            *
 * Purpose: copies variant contents from source to value                      *
 *                                                                            *
 * Comments: String and binary data are cloned, which is different from       *
 *           setters where only the pointers are copied. The contents of the  *
 *           destination value are not freed. If copied over already          *
 *           initialized variant it's safer to clear it beforehand.           *
 *                                                                            *
 ******************************************************************************/
void	zbx_variant_copy(zbx_variant_t *value, const zbx_variant_t *source)
{
	int			i;
	zbx_vector_var_t	*var_vector;

	switch (source->type)
	{
		case ZBX_VARIANT_STR:
			zbx_variant_set_str(value, zbx_strdup(NULL, source->data.str));
			break;
		case ZBX_VARIANT_UI64:
			zbx_variant_set_ui64(value, source->data.ui64);
			break;
		case ZBX_VARIANT_DBL:
			zbx_variant_set_dbl(value, source->data.dbl);
			break;
		case ZBX_VARIANT_BIN:
			zbx_variant_set_bin(value, zbx_variant_data_bin_copy(source->data.bin));
			break;
		case ZBX_VARIANT_NONE:
			value->type = ZBX_VARIANT_NONE;
			break;
		case ZBX_VARIANT_ERR:
			zbx_variant_set_error(value, zbx_strdup(NULL, source->data.err));
			break;
		case ZBX_VARIANT_VECTOR:
			var_vector = (zbx_vector_var_t *)zbx_malloc(NULL, sizeof(zbx_vector_var_t));
			zbx_vector_var_create(var_vector);
			zbx_vector_var_reserve(var_vector, source->data.vector->values_num);
			var_vector->values_num = source->data.vector->values_num;

			for (i = 0; i < source->data.vector->values_num; i++)
				zbx_variant_copy(&(var_vector->values[i]), &(source->data.vector->values[i]));

			zbx_variant_set_vector(value, var_vector);
			break;
	}
}

static int	variant_to_dbl(zbx_variant_t *value)
{
	char	buffer[MAX_STRING_LEN];
	double	value_dbl;

	switch (value->type)
	{
		case ZBX_VARIANT_DBL:
			return SUCCEED;
		case ZBX_VARIANT_UI64:
			zbx_variant_set_dbl(value, (double)value->data.ui64);
			return SUCCEED;
		case ZBX_VARIANT_STR:
			zbx_strlcpy(buffer, value->data.str, sizeof(buffer));
			break;
		default:
			return FAIL;
	}

	zbx_rtrim(buffer, "\n\r"); /* trim newline for historical reasons / backwards compatibility */
	zbx_trim_float(buffer);

	if (SUCCEED != zbx_is_double(buffer, &value_dbl))
		return FAIL;

	zbx_variant_clear(value);
	zbx_variant_set_dbl(value, value_dbl);

	return SUCCEED;
}

static int	variant_to_ui64(zbx_variant_t *value)
{
	zbx_uint64_t	value_ui64;
	char		buffer[MAX_STRING_LEN];

	switch (value->type)
	{
		case ZBX_VARIANT_UI64:
			return SUCCEED;
		case ZBX_VARIANT_DBL:
			if (0 > value->data.dbl)
				return FAIL;

			/* uint64_t(double(UINT64_MAX)) conversion results in 0, to avoid      */
			/* conversion issues require floating value to be less than UINT64_MAX */
			if ((double)ZBX_MAX_UINT64 <= value->data.dbl)
				return FAIL;

			zbx_variant_set_ui64(value, (zbx_uint64_t)(value->data.dbl));
			return SUCCEED;
		case ZBX_VARIANT_STR:
			zbx_strlcpy(buffer, value->data.str, sizeof(buffer));
			break;
		default:
			return FAIL;
	}

	zbx_rtrim(buffer, "\n\r"); /* trim newline for historical reasons / backwards compatibility */
	zbx_trim_integer(buffer);
	zbx_del_zeros(buffer);

	if (SUCCEED != zbx_is_uint64(buffer, &value_ui64))
	{
		double	dbl;

		if (SUCCEED != zbx_is_double(buffer, &dbl) || 0 > dbl || dbl >= (double)ZBX_MAX_UINT64)
			return FAIL;

		value_ui64 = (zbx_uint64_t)dbl;
	}

	zbx_variant_clear(value);
	zbx_variant_set_ui64(value, value_ui64);

	return SUCCEED;
}

static int	variant_to_str(zbx_variant_t *value)
{
	char	*value_str, buffer[ZBX_MAX_DOUBLE_LEN + 1];

	switch (value->type)
	{
		case ZBX_VARIANT_STR:
			return SUCCEED;
		case ZBX_VARIANT_DBL:
			value_str = zbx_strdup(NULL, zbx_print_double(buffer, sizeof(buffer), value->data.dbl));
			zbx_del_zeros(value_str);
			break;
		case ZBX_VARIANT_UI64:
			value_str = zbx_dsprintf(NULL, ZBX_FS_UI64, value->data.ui64);
			break;
		default:
			return FAIL;
	}

	zbx_variant_clear(value);
	zbx_variant_set_str(value, value_str);

	return SUCCEED;
}

int	zbx_variant_convert(zbx_variant_t *value, int type)
{
	switch(type)
	{
		case ZBX_VARIANT_UI64:
			return variant_to_ui64(value);
		case ZBX_VARIANT_DBL:
			return variant_to_dbl(value);
		case ZBX_VARIANT_STR:
			return variant_to_str(value);
		case ZBX_VARIANT_NONE:
			zbx_variant_clear(value);
			return SUCCEED;
		default:
			return FAIL;
	}
}

int	zbx_variant_set_numeric(zbx_variant_t *value, const char *text)
{
	zbx_uint64_t	value_ui64;
	double		dbl_tmp;
	char		buffer[MAX_STRING_LEN];

	zbx_strlcpy(buffer, text, sizeof(buffer));

	zbx_rtrim(buffer, "\n\r"); /* trim newline for historical reasons / backwards compatibility */
	zbx_trim_integer(buffer);
	zbx_del_zeros(buffer);

	if ('+' == buffer[0])
	{
		/* zbx_trim_integer() stripped one '+' sign, so there's more than one '+' sign in the 'text' argument */
		return FAIL;
	}

	if (SUCCEED == zbx_is_uint64(buffer, &value_ui64))
	{
		zbx_variant_set_ui64(value, value_ui64);
		return SUCCEED;
	}

	if (SUCCEED == zbx_is_double(buffer, &dbl_tmp))
	{
		zbx_variant_set_dbl(value, dbl_tmp);
		return SUCCEED;
	}

	return FAIL;
}

const char	*zbx_variant_value_desc(const zbx_variant_t *value)
{
	static ZBX_THREAD_LOCAL char	buffer[64];
	zbx_uint32_t			size, i, len;

	switch (value->type)
	{
		case ZBX_VARIANT_DBL:
			zbx_print_double(buffer, sizeof(buffer), value->data.dbl);
			zbx_del_zeros(buffer);
			return buffer;
		case ZBX_VARIANT_UI64:
			zbx_snprintf(buffer, sizeof(buffer), ZBX_FS_UI64, value->data.ui64);
			return buffer;
		case ZBX_VARIANT_STR:
			return value->data.str;
		case ZBX_VARIANT_NONE:
			return "";
		case ZBX_VARIANT_BIN:
			memcpy(&size, value->data.bin, sizeof(size));
			if (0 != (len = MIN(sizeof(buffer) / 3, size)))
			{
				const unsigned char	*ptr = (const unsigned char *)value->data.bin + sizeof(size);

				for (i = 0; i < len; i++)
					zbx_snprintf(buffer + i * 3, sizeof(buffer) - i * 3, "%02x ", ptr[i]);

				buffer[i * 3 - 1] = '\0';
			}
			else
				buffer[0] = '\0';
			return buffer;
		case ZBX_VARIANT_ERR:
			return value->data.err;
		case ZBX_VARIANT_VECTOR:
			zbx_snprintf(buffer, sizeof(buffer), "var vector[0:%d]", value->data.vector->values_num);
			return buffer;
		default:
			THIS_SHOULD_NEVER_HAPPEN;
			return ZBX_UNKNOWN_STR;
	}
}

const char	*zbx_get_variant_type_desc(unsigned char type)
{
	switch (type)
	{
		case ZBX_VARIANT_DBL:
			return "double";
		case ZBX_VARIANT_UI64:
			return "uint64";
		case ZBX_VARIANT_STR:
			return "string";
		case ZBX_VARIANT_NONE:
			return "none";
		case ZBX_VARIANT_BIN:
			return "binary";
		case ZBX_VARIANT_ERR:
			return "error";
		case ZBX_VARIANT_VECTOR:
			return "vector";
		default:
			THIS_SHOULD_NEVER_HAPPEN;
			return ZBX_UNKNOWN_STR;
	}
}

const char	*zbx_variant_type_desc(const zbx_variant_t *value)
{
	return zbx_get_variant_type_desc(value->type);
}

/******************************************************************************
 *                                                                            *
 * Purpose: compares two variant values when at least one is empty (having    *
 *          type of ZBX_VARIANT_NONE)                                         *
 *                                                                            *
 ******************************************************************************/
static int	variant_compare_empty(const zbx_variant_t *value1, const zbx_variant_t *value2)
{
	if (ZBX_VARIANT_NONE == value1->type)
	{
		if (ZBX_VARIANT_NONE == value2->type)
			return 0;

		return -1;
	}

	return 1;
}

/*******************************************************************************
 *                                                                             *
 * Purpose: compares two variant values when at least one contains binary data *
 *                                                                             *
 *******************************************************************************/
static int	variant_compare_bin(const zbx_variant_t *value1, const zbx_variant_t *value2)
{
	if (ZBX_VARIANT_BIN == value1->type)
	{
		zbx_uint32_t	size1, size2;

		if (ZBX_VARIANT_BIN != value2->type)
			return 1;

		memcpy(&size1, value1->data.bin, sizeof(size1));
		memcpy(&size2, value2->data.bin, sizeof(size2));
		ZBX_RETURN_IF_NOT_EQUAL(size1, size2);
		return memcmp(value1->data.bin, value2->data.bin, size1 + sizeof(size1));
	}

	return -1;
}

/******************************************************************************
 *                                                                            *
 * Purpose: compares two variant values when at least one contains error      *
 *                                                                            *
 ******************************************************************************/
static int	variant_compare_error(const zbx_variant_t *value1, const zbx_variant_t *value2)
{
	if (ZBX_VARIANT_ERR == value1->type)
	{
		if (ZBX_VARIANT_ERR != value2->type)
			return 1;

		return strcmp(value1->data.err, value2->data.err);
	}

	return -1;
}

/******************************************************************************
 *                                                                            *
 * Purpose: compares two variant values when at least one contains error      *
 *                                                                            *
 ******************************************************************************/
static int	variant_compare_vector(const zbx_variant_t *value1, const zbx_variant_t *value2)
{
	if (ZBX_VARIANT_VECTOR == value1->type)
	{
		int	i;

		if (ZBX_VARIANT_VECTOR != value2->type)
			return 1;

		ZBX_RETURN_IF_NOT_EQUAL(value1->data.vector->values_num, value2->data.vector->values_num);

		for (i = 0; i < value1->data.vector->values_num; i++)
		{
			int	ret;

			if (0 != (ret = zbx_variant_compare(&value1->data.vector->values[i],
					&value2->data.vector->values[i])))
			{
				return ret;
			}
		}

		return 0;
	}

	return -1;
}
/******************************************************************************
 *                                                                            *
 * Purpose: compares two variant values when at least one is string           *
 *                                                                            *
 ******************************************************************************/
static int	variant_compare_str(const zbx_variant_t *value1, const zbx_variant_t *value2)
{
	if (ZBX_VARIANT_STR == value1->type)
		return strcmp(value1->data.str, zbx_variant_value_desc(value2));

	return strcmp(zbx_variant_value_desc(value1), value2->data.str);
}

/******************************************************************************
 *                                                                            *
 * Purpose: Compares two variant values when at least one is double and the   *
 *          other is double, uint64 or a string representing a valid double   *
 *          value.                                                            *
 *                                                                            *
 ******************************************************************************/
static int	variant_compare_dbl(const zbx_variant_t *value1, const zbx_variant_t *value2)
{
	double	value1_dbl, value2_dbl;
	char	buf1[ZBX_MAX_DOUBLE_LEN + 1], buf2[ZBX_MAX_DOUBLE_LEN + 1];

	switch (value1->type)
	{
		case ZBX_VARIANT_DBL:
			value1_dbl = value1->data.dbl;
			break;
		case ZBX_VARIANT_UI64:
			value1_dbl = value1->data.ui64;
			break;
		case ZBX_VARIANT_STR:
			value1_dbl = atof(value1->data.str);
			break;
		default:
			THIS_SHOULD_NEVER_HAPPEN;
			exit(EXIT_FAILURE);
	}

	switch (value2->type)
	{
		case ZBX_VARIANT_DBL:
			value2_dbl = value2->data.dbl;
			break;
		case ZBX_VARIANT_UI64:
			value2_dbl = value2->data.ui64;
			break;
		case ZBX_VARIANT_STR:
			value2_dbl = atof(value2->data.str);
			break;
		default:
			THIS_SHOULD_NEVER_HAPPEN;
			exit(EXIT_FAILURE);
	}

	if (SUCCEED == zbx_double_compare(value1_dbl, value2_dbl))
		return 0;

	ZBX_RETURN_IF_NOT_EQUAL(value1_dbl, value2_dbl);

	zbx_print_double(buf1, sizeof(buf1), value1_dbl);
	zbx_print_double(buf2, sizeof(buf2), value2_dbl);
	zabbix_log(LOG_LEVEL_ERR, "\"%s\" to \"%s\" comparison result forced to 0", buf1, buf2);

	THIS_SHOULD_NEVER_HAPPEN;

	return 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: compares two variant values when both are uint64                  *
 *                                                                            *
 ******************************************************************************/
static int	variant_compare_ui64(const zbx_variant_t *value1, const zbx_variant_t *value2)
{
	ZBX_RETURN_IF_NOT_EQUAL(value1->data.ui64, value2->data.ui64);

	return 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: compares two variant values                                       *
 *                                                                            *
 * Parameters: value1 - [IN] first value                                      *
 *             value2 - [IN] second value                                     *
 *                                                                            *
 * Return value: <0 - first value is less than second                         *
 *               >0 - first value is greater than second                      *
 *               0  - values are equal                                        *
 *                                                                            *
 * Comments: The following comparison logic is applied:                       *
 *           1) value of 'none' type is always less than other types, two     *
 *              'none' types are equal                                        *
 *           2) value of error type is always greater than other types, two   *
 *              error types are compared by error messages as strings         *
 *           3) value of binary type is always greater than other types       *
 *              except error, two binary types are compared by length and     *
 *              then by contents                                              *
 *           4) value of double vector type is always greater than other      *
 *              types except error and binary, two double vectors are compared*
 *              by their size and contents                                    *
 *           5) if both values have uint64 types, they are compared as is     *
 *           6) if both values can be converted to floating point values the  *
 *              conversion is done and the result is compared                 *
 *           7) if any of value is of string type, the other is converted to  *
 *              string and both are compared                                  *
 *                                                                            *
 ******************************************************************************/
int	zbx_variant_compare(const zbx_variant_t *value1, const zbx_variant_t *value2)
{
	if (ZBX_VARIANT_NONE == value1->type || ZBX_VARIANT_NONE == value2->type)
		return variant_compare_empty(value1, value2);

	if (ZBX_VARIANT_ERR == value1->type || ZBX_VARIANT_ERR == value2->type)
		return variant_compare_error(value1, value2);

	if (ZBX_VARIANT_BIN == value1->type || ZBX_VARIANT_BIN == value2->type)
		return variant_compare_bin(value1, value2);

	if (ZBX_VARIANT_VECTOR == value1->type || ZBX_VARIANT_VECTOR == value2->type)
		return variant_compare_vector(value1, value2);

	if (ZBX_VARIANT_UI64 == value1->type && ZBX_VARIANT_UI64 == value2->type)
		return  variant_compare_ui64(value1, value2);

	if ((ZBX_VARIANT_STR != value1->type || SUCCEED == zbx_is_double(value1->data.str, NULL)) &&
			(ZBX_VARIANT_STR != value2->type || SUCCEED == zbx_is_double(value2->data.str, NULL)))
	{
		return variant_compare_dbl(value1, value2);
	}

	/* at this point at least one of the values is string data, other can be uint64, floating or string */
	return variant_compare_str(value1, value2);
}

int	zbx_vector_var_get_type(zbx_vector_var_t *v)
{
	int	i, type = ITEM_VALUE_TYPE_NONE;

	for (i = 0; i < v->values_num; i++)
	{
		if (ZBX_VARIANT_UI64 == v->values[i].type)
		{
			if (ITEM_VALUE_TYPE_NONE == type)
				type = ITEM_VALUE_TYPE_UINT64;
			else if (ITEM_VALUE_TYPE_UINT64 != type)
				return ITEM_VALUE_TYPE_STR;
		}
		else if (ZBX_VARIANT_DBL == v->values[i].type)
		{
			if (ITEM_VALUE_TYPE_NONE == type)
				type = ITEM_VALUE_TYPE_FLOAT;
			else if (ITEM_VALUE_TYPE_FLOAT != type)
				return ITEM_VALUE_TYPE_STR;
		}
		else
			return ITEM_VALUE_TYPE_STR;
	}

	return type;
}

void	zbx_vector_var_clear_ext(zbx_vector_var_t *v)
{
	int	i;

	for (i = 0; i < v->values_num; i++)
		zbx_variant_clear(&v->values[i]);
}
