/*
** 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 "evalfunc.h"
#include "expression.h"

#include "zbxdbhigh.h"
#include "zbxcacheconfig.h"
#include "zbxcachevalue.h"
#include "zbx_trigger_constants.h"
#include "zbx_item_constants.h"
#include "zbx_host_constants.h"
#include "zbxvariant.h"
#include "zbxalgo.h"
#include "zbxexpr.h"
#include "zbxeval.h"
#include "zbxexpression.h"
#include "zbxnum.h"
#include "zbxtime.h"

static void	zbx_extract_functionids(zbx_vector_uint64_t *functionids, zbx_vector_dc_trigger_t *triggers)
{
	zabbix_log(LOG_LEVEL_DEBUG, "In %s() tr_num:%d", __func__, triggers->values_num);

	zbx_vector_uint64_reserve(functionids, triggers->values_num);

	for (int i = 0; i < triggers->values_num; i++)
	{
		zbx_dc_trigger_t	*tr = triggers->values[i];

		if (NULL != tr->new_error)
			continue;

		zbx_eval_get_functionids(tr->eval_ctx, functionids);

		if (TRIGGER_RECOVERY_MODE_RECOVERY_EXPRESSION == tr->recovery_mode)
			zbx_eval_get_functionids(tr->eval_ctx_r, functionids);
	}

	zbx_vector_uint64_sort(functionids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);
	zbx_vector_uint64_uniq(functionids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() functionids_num:%d", __func__, functionids->values_num);
}

typedef struct
{
	zbx_dc_trigger_t	*trigger;
	int			start_index;
	int			count;
}
zbx_trigger_func_position_t;

ZBX_PTR_VECTOR_DECL(trigger_func_position, zbx_trigger_func_position_t *)
ZBX_PTR_VECTOR_IMPL(trigger_func_position, zbx_trigger_func_position_t *)

/******************************************************************************
 *                                                                            *
 * Purpose: expand macros in a trigger expression.                            *
 *                                                                            *
 ******************************************************************************/
static int	expand_normal_trigger_macros(zbx_eval_context_t *ctx, const zbx_db_event *event, char *error,
		size_t maxerrlen)
{
	int	i;

	for (i = 0; i < ctx->stack.values_num; i++)
	{
		zbx_eval_token_t	*token = &ctx->stack.values[i];

		if (ZBX_EVAL_TOKEN_VAR_MACRO != token->type && ZBX_EVAL_TOKEN_VAR_STR != token->type)
		{
			continue;
		}

		/* all trigger macros are already extracted into strings */
		if (ZBX_VARIANT_STR != token->value.type)
			continue;

		if (FAIL == substitute_simple_macros_impl(NULL, event, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL,&token->value.data.str, ZBX_MACRO_TYPE_TRIGGER_EXPRESSION, error,
				(int)maxerrlen))
		{
			return FAIL;
		}
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: triggers links with functions.                                    *
 *                                                                            *
 * Parameters: triggers_func_pos - [IN/OUT] pointer to the list of triggers   *
 *                                          with functions position in        *
 *                                          functionids array                 *
 *             functionids       - [IN/OUT] array of function IDs             *
 *             trigger_order     - [IN] array of triggers                     *
 *                                                                            *
 ******************************************************************************/
static void	zbx_link_triggers_with_functions(zbx_vector_trigger_func_position_t *triggers_func_pos,
		zbx_vector_uint64_t *functionids, zbx_vector_dc_trigger_t *trigger_order)
{
	zbx_vector_uint64_t	funcids;
	zbx_dc_trigger_t	*tr;
	int			i;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() trigger_order_num:%d", __func__, trigger_order->values_num);

	zbx_vector_uint64_create(&funcids);
	zbx_vector_uint64_reserve(&funcids, functionids->values_num);

	for (i = 0; i < trigger_order->values_num; i++)
	{
		zbx_trigger_func_position_t	*tr_func_pos;

		tr = trigger_order->values[i];

		if (NULL != tr->new_error)
			continue;

		zbx_eval_get_functionids(tr->eval_ctx, &funcids);

		tr_func_pos = (zbx_trigger_func_position_t *)zbx_malloc(NULL, sizeof(zbx_trigger_func_position_t));
		tr_func_pos->trigger = tr;
		tr_func_pos->start_index = functionids->values_num;
		tr_func_pos->count = funcids.values_num;

		zbx_vector_uint64_append_array(functionids, funcids.values, funcids.values_num);
		zbx_vector_trigger_func_position_append(triggers_func_pos, tr_func_pos);

		zbx_vector_uint64_clear(&funcids);
	}

	zbx_vector_uint64_destroy(&funcids);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() triggers_func_pos_num:%d", __func__, triggers_func_pos->values_num);
}

/******************************************************************************
 *                                                                            *
 * Purpose: mark triggers that use one of the items in problem expression     *
 *          with ZBX_DC_TRIGGER_PROBLEM_EXPRESSION flag.                      *
 *                                                                            *
 * Parameters: trigger_order - [IN/OUT] pointer to the list of triggers       *
 *             itemids       - [IN] array of item IDs                         *
 *             item_num      - [IN] number of items                           *
 *                                                                            *
 ******************************************************************************/
void	zbx_determine_items_in_expressions(zbx_vector_dc_trigger_t *trigger_order, const zbx_uint64_t *itemids,
		int item_num)
{
	zbx_vector_trigger_func_position_t	triggers_func_pos;
	zbx_vector_uint64_t			functionids, itemids_sorted;
	zbx_dc_function_t			*functions = NULL;
	int					*errcodes = NULL, t, f;

	zbx_vector_uint64_create(&itemids_sorted);
	zbx_vector_uint64_append_array(&itemids_sorted, itemids, item_num);

	zbx_vector_trigger_func_position_create(&triggers_func_pos);
	zbx_vector_trigger_func_position_reserve(&triggers_func_pos, trigger_order->values_num);

	zbx_vector_uint64_create(&functionids);
	zbx_vector_uint64_reserve(&functionids, item_num);

	zbx_link_triggers_with_functions(&triggers_func_pos, &functionids, trigger_order);

	functions = (zbx_dc_function_t *)zbx_malloc(functions, sizeof(zbx_dc_function_t) * functionids.values_num);
	errcodes = (int *)zbx_malloc(errcodes, sizeof(int) * functionids.values_num);

	zbx_dc_config_history_sync_get_functions_by_functionids(functions, functionids.values, errcodes,
			(size_t)functionids.values_num);

	for (t = 0; t < triggers_func_pos.values_num; t++)
	{
		zbx_trigger_func_position_t	*func_pos = triggers_func_pos.values[t];

		for (f = func_pos->start_index; f < func_pos->start_index + func_pos->count; f++)
		{
			if (SUCCEED == errcodes[f] && FAIL != zbx_vector_uint64_bsearch(&itemids_sorted,
					functions[f].itemid, ZBX_DEFAULT_UINT64_COMPARE_FUNC))
			{
				func_pos->trigger->flags |= ZBX_DC_TRIGGER_PROBLEM_EXPRESSION;
				break;
			}
		}
	}

	zbx_dc_config_clean_functions(functions, errcodes, functionids.values_num);
	zbx_free(errcodes);
	zbx_free(functions);

	zbx_vector_trigger_func_position_clear_ext(&triggers_func_pos,
			(zbx_trigger_func_position_free_func_t)zbx_ptr_free);
	zbx_vector_trigger_func_position_destroy(&triggers_func_pos);

	zbx_vector_uint64_clear(&functionids);
	zbx_vector_uint64_destroy(&functionids);

	zbx_vector_uint64_clear(&itemids_sorted);
	zbx_vector_uint64_destroy(&itemids_sorted);
}

typedef struct
{
	/* input data */
	zbx_uint64_t	itemid;
	char		*function;
	char		*parameter;
	zbx_timespec_t	timespec;
	unsigned char	type;

	/* output data */
	zbx_variant_t	value;
	char		*error;
}
zbx_func_t;

typedef struct
{
	zbx_uint64_t	functionid;
	zbx_func_t	*func;
}
zbx_ifunc_t;

static zbx_hash_t	func_hash_func(const void *data)
{
	const zbx_func_t	*func = (const zbx_func_t *)data;
	zbx_hash_t		hash;

	hash = ZBX_DEFAULT_UINT64_HASH_FUNC(&func->itemid);
	hash = ZBX_DEFAULT_STRING_HASH_ALGO(func->function, strlen(func->function), hash);
	hash = ZBX_DEFAULT_STRING_HASH_ALGO(func->parameter, strlen(func->parameter), hash);
	hash = ZBX_DEFAULT_HASH_ALGO(&func->timespec.sec, sizeof(func->timespec.sec), hash);
	hash = ZBX_DEFAULT_HASH_ALGO(&func->timespec.ns, sizeof(func->timespec.ns), hash);

	return hash;
}

static int	func_compare_func(const void *d1, const void *d2)
{
	const zbx_func_t	*func1 = (const zbx_func_t *)d1;
	const zbx_func_t	*func2 = (const zbx_func_t *)d2;
	int			ret;

	ZBX_RETURN_IF_NOT_EQUAL(func1->itemid, func2->itemid);

	if (0 != (ret = strcmp(func1->function, func2->function)))
		return ret;

	if (0 != (ret = strcmp(func1->parameter, func2->parameter)))
		return ret;

	ZBX_RETURN_IF_NOT_EQUAL(func1->timespec.sec, func2->timespec.sec);
	ZBX_RETURN_IF_NOT_EQUAL(func1->timespec.ns, func2->timespec.ns);

	return 0;
}

static void	func_clean(void *ptr)
{
	zbx_func_t	*func = (zbx_func_t *)ptr;

	zbx_free(func->function);
	zbx_free(func->parameter);
	zbx_free(func->error);

	zbx_variant_clear(&func->value);
}

/******************************************************************************
 *                                                                            *
 * Purpose: prepare hashset of functions to evaluate.                         *
 *                                                                            *
 * Parameters: functionids - [IN] function identifiers                        *
 *             funcs       - [OUT] functions indexed by itemid, name,         *
 *                                 parameter, timestamp                       *
 *             ifuncs      - [OUT] function index by functionid               *
 *             triggers    - [IN] vector of triggers, sorted by triggerid     *
 *                                                                            *
 ******************************************************************************/
static void	zbx_populate_function_items(const zbx_vector_uint64_t *functionids, zbx_hashset_t *funcs,
		zbx_hashset_t *ifuncs, const zbx_vector_dc_trigger_t *triggers)
{
	int			i, j;
	zbx_dc_trigger_t	*tr;
	zbx_dc_function_t	*functions = NULL;
	int			*errcodes = NULL;
	zbx_ifunc_t		ifunc_local;
	zbx_func_t		*func, func_local;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() functionids_num:%d", __func__, functionids->values_num);

	zbx_variant_set_none(&func_local.value);
	func_local.error = NULL;

	functions = (zbx_dc_function_t *)zbx_malloc(functions, sizeof(zbx_dc_function_t) * functionids->values_num);
	errcodes = (int *)zbx_malloc(errcodes, sizeof(int) * functionids->values_num);

	zbx_dc_config_history_sync_get_functions_by_functionids(functions, functionids->values, errcodes,
			(size_t)functionids->values_num);

	for (i = 0; i < functionids->values_num; i++)
	{
		if (SUCCEED != errcodes[i])
			continue;

		func_local.itemid = functions[i].itemid;

		if (FAIL != (j = zbx_vector_ptr_bsearch((const zbx_vector_ptr_t *)triggers, &functions[i].triggerid,
				ZBX_DEFAULT_UINT64_PTR_COMPARE_FUNC)))
		{
			tr = triggers->values[j];
			func_local.timespec = tr->timespec;
		}
		else
		{
			func_local.timespec.sec = 0;
			func_local.timespec.ns = 0;
		}

		func_local.function = functions[i].function;
		func_local.parameter = functions[i].parameter;

		if (NULL == (func = (zbx_func_t *)zbx_hashset_search(funcs, &func_local)))
		{
			func = (zbx_func_t *)zbx_hashset_insert(funcs, &func_local, sizeof(func_local));
			func->function = zbx_strdup(NULL, func_local.function);
			func->parameter = zbx_strdup(NULL, func_local.parameter);
			func->type = functions[i].type;
			zbx_variant_set_none(&func->value);
		}

		ifunc_local.functionid = functions[i].functionid;
		ifunc_local.func = func;
		zbx_hashset_insert(ifuncs, &ifunc_local, sizeof(ifunc_local));
	}

	zbx_dc_config_clean_functions(functions, errcodes, functionids->values_num);

	zbx_free(errcodes);
	zbx_free(functions);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() ifuncs_num:%d", __func__, ifuncs->num_data);
}

static void	zbx_evaluate_item_functions(zbx_hashset_t *funcs, const zbx_vector_uint64_t *history_itemids,
		const zbx_history_sync_item_t *history_items, const int *history_errcodes,
		zbx_history_sync_item_t **items, int **items_err, int *items_num)
{
	char			*error = NULL;
	int			i;
	zbx_func_t		*func;
	zbx_vector_uint64_t	itemids;
	zbx_hashset_iter_t	iter;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() funcs_num:%d", __func__, funcs->num_data);

	zbx_vector_uint64_create(&itemids);

	zbx_hashset_iter_reset(funcs, &iter);
	while (NULL != (func = (zbx_func_t *)zbx_hashset_iter_next(&iter)))
	{
		if (FAIL == zbx_vector_uint64_bsearch(history_itemids, func->itemid, ZBX_DEFAULT_UINT64_COMPARE_FUNC))
			zbx_vector_uint64_append(&itemids, func->itemid);
	}

	if (0 != itemids.values_num)
	{
		zbx_vector_uint64_sort(&itemids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);
		zbx_vector_uint64_uniq(&itemids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);

		*items_num = itemids.values_num;
		*items = (zbx_history_sync_item_t *)zbx_malloc(NULL, sizeof(zbx_history_sync_item_t) *
				(size_t)itemids.values_num);
		*items_err = (int *)zbx_malloc(NULL, sizeof(int) * (size_t)itemids.values_num);

		zbx_dc_config_history_sync_get_items_by_itemids(*items, itemids.values, *items_err,
				(size_t)itemids.values_num, ZBX_ITEM_GET_SYNC);
	}

	zbx_hashset_iter_reset(funcs, &iter);
	while (NULL != (func = (zbx_func_t *)zbx_hashset_iter_next(&iter)))
	{
		int				errcode, ret;
		const zbx_history_sync_item_t	*item;
		char				*params;
		zbx_dc_evaluate_item_t		evaluate_item;

		/* avoid double copying from configuration cache if already retrieved when saving history */
		if (FAIL != (i = zbx_vector_uint64_bsearch(history_itemids, func->itemid,
				ZBX_DEFAULT_UINT64_COMPARE_FUNC)))
		{
			item = history_items + i;
			errcode = history_errcodes[i];
		}
		else
		{
			i = zbx_vector_uint64_bsearch(&itemids, func->itemid, ZBX_DEFAULT_UINT64_COMPARE_FUNC);
			item = *items + i;
			errcode = (*items_err)[i];
		}

		if (SUCCEED != errcode)
		{
			zbx_free(func->error);
			func->error = zbx_eval_format_function_error(func->function, NULL, NULL, func->parameter,
					"item does not exist");
			continue;
		}

		if (ITEM_VALUE_TYPE_BIN == item->value_type)
		{
			zbx_free(func->error);
			func->error = zbx_eval_format_function_error(func->function, item->host.host, item->key_orig,
					func->parameter, "binary-type items are not supported in functions");
			continue;
		}

		/* do not evaluate if the item is disabled or belongs to a disabled host */

		if (ITEM_STATUS_ACTIVE != item->status)
		{
			zbx_free(func->error);
			func->error = zbx_eval_format_function_error(func->function, item->host.host,
					item->key_orig, func->parameter, "item is disabled");
			continue;
		}

		if ((ZBX_FUNCTION_TYPE_HISTORY == func->type || ZBX_FUNCTION_TYPE_TIMER == func->type) &&
				0 == item->history)
		{
			zbx_free(func->error);
			func->error = zbx_eval_format_function_error(func->function, item->host.host,
					item->key_orig, func->parameter, "item history is disabled");
			continue;
		}

		if (ZBX_FUNCTION_TYPE_TRENDS == func->type)
		{
			if (ITEM_VALUE_TYPE_FLOAT != item->value_type && ITEM_VALUE_TYPE_UINT64 != item->value_type)
			{
				zbx_free(func->error);
				func->error = zbx_eval_format_function_error(func->function, item->host.host,
						item->key_orig, func->parameter, "trend functions are supported only "
						"for numeric types");
				continue;
			}
			else if (0 == item->trends)
			{
				zbx_free(func->error);
				func->error = zbx_eval_format_function_error(func->function, item->host.host,
						item->key_orig, func->parameter, "item trends are disabled");
				continue;
			}
		}

		if (HOST_STATUS_MONITORED != item->host.status)
		{
			zbx_free(func->error);
			func->error = zbx_eval_format_function_error(func->function, item->host.host,
					item->key_orig, func->parameter, "item belongs to a disabled host");
			continue;
		}

		if (ITEM_STATE_NOTSUPPORTED == item->state &&
				FAIL == zbx_evaluatable_for_notsupported(func->function))
		{
			/* set 'unknown' error value */
			zbx_variant_set_error(&func->value,
					zbx_eval_format_function_error(func->function, item->host.host,
							item->key_orig, func->parameter, "item is not supported"));
			continue;
		}

		params = zbx_dc_expand_user_macros_in_func_params(func->parameter, item->host.hostid);

		evaluate_item.itemid = item->itemid;
		evaluate_item.value_type = item->value_type;
		evaluate_item.proxyid = item->host.proxyid;
		evaluate_item.host = item->host.host;
		evaluate_item.key_orig = item->key_orig;

		ret = evaluate_function(&func->value, &evaluate_item, func->function, params, &func->timespec, &error);

		if (SUCCEED != ret)
		{
			/* compose and store error message for future use */
			zbx_variant_set_error(&func->value,
					zbx_eval_format_function_error(func->function, item->host.host,
							item->key_orig, params, error));
			zbx_free(error);
		}

		zbx_free(params);
	}

	zbx_vc_flush_stats();
	zbx_vector_uint64_destroy(&itemids);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

static int	substitute_expression_functions_results(zbx_hashset_t *ifuncs, zbx_eval_context_t *ctx, char **error)
{
	zbx_uint64_t		functionid;
	zbx_func_t		*func;
	zbx_ifunc_t		*ifunc;
	int			i;

	for (i = 0; i < ctx->stack.values_num; i++)
	{
		zbx_eval_token_t	*token = &ctx->stack.values[i];

		if (ZBX_EVAL_TOKEN_FUNCTIONID != token->type)
			continue;

		if (ZBX_VARIANT_UI64 != token->value.type)
		{
			/* functionids should be already extracted into uint64 vars */
			THIS_SHOULD_NEVER_HAPPEN;
			*error = zbx_dsprintf(*error, "Cannot parse function at: \"%s\"",
					ctx->expression + token->loc.l);
			return FAIL;
		}

		functionid = token->value.data.ui64;
		if (NULL == (ifunc = (zbx_ifunc_t *)zbx_hashset_search(ifuncs, &functionid)))
		{
			*error = zbx_dsprintf(*error, "Cannot obtain function"
					" and item for functionid: " ZBX_FS_UI64, functionid);
			return FAIL;
		}

		func = ifunc->func;

		if (NULL != func->error)
		{
			*error = zbx_strdup(*error, func->error);
			return FAIL;
		}

		if (ZBX_VARIANT_NONE == func->value.type)
		{
			*error = zbx_strdup(*error, "Unexpected error while processing a trigger expression");
			return FAIL;
		}

		zbx_variant_copy(&token->value, &func->value);
	}

	return SUCCEED;
}

static void	log_expression(const char *prefix, int index, const zbx_eval_context_t *ctx)
{
	if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG))
	{
		char	*expression = NULL;

		zbx_eval_compose_expression(ctx, &expression);
		zabbix_log(LOG_LEVEL_DEBUG, "%s() expression[%d]:'%s' => '%s'", prefix, index, ctx->expression,
				expression);
		zbx_free(expression);
	}
}

static void	zbx_substitute_functions_results(zbx_hashset_t *ifuncs, zbx_vector_dc_trigger_t *triggers)
{
	zabbix_log(LOG_LEVEL_DEBUG, "In %s() ifuncs_num:%d tr_num:%d",
			__func__, ifuncs->num_data, triggers->values_num);

	for (int i = 0; i < triggers->values_num; i++)
	{
		zbx_dc_trigger_t	*tr = triggers->values[i];

		if (NULL != tr->new_error)
			continue;

		if( SUCCEED != substitute_expression_functions_results(ifuncs, tr->eval_ctx, &tr->new_error))
		{
			tr->new_value = TRIGGER_VALUE_UNKNOWN;
			continue;
		}

		log_expression(__func__, i, tr->eval_ctx);

		if (TRIGGER_RECOVERY_MODE_RECOVERY_EXPRESSION == tr->recovery_mode)
		{
			if (SUCCEED != substitute_expression_functions_results(ifuncs, tr->eval_ctx_r, &tr->new_error))
			{
				tr->new_value = TRIGGER_VALUE_UNKNOWN;
				continue;
			}

			log_expression(__func__, i, tr->eval_ctx_r);
		}
	}

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

/******************************************************************************
 *                                                                            *
 * Purpose: substitute expression functions with their values.                *
 *                                                                            *
 * Comments: example: "({15}>10) or ({123}=1)" => "(26.416>10) or (0=1)"      *
 *                                                                            *
 ******************************************************************************/
static void	substitute_functions(zbx_vector_dc_trigger_t *triggers, const zbx_vector_uint64_t *history_itemids,
		const zbx_history_sync_item_t *history_items, const int *history_errcodes,
		zbx_history_sync_item_t **items, int **items_err, int *items_num)
{
	zbx_vector_uint64_t	functionids;
	zbx_hashset_t		ifuncs, funcs;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	zbx_vector_uint64_create(&functionids);
	zbx_extract_functionids(&functionids, triggers);

	if (0 == functionids.values_num)
		goto empty;

	zbx_hashset_create(&ifuncs, triggers->values_num, ZBX_DEFAULT_UINT64_HASH_FUNC,
			ZBX_DEFAULT_UINT64_COMPARE_FUNC);

	zbx_hashset_create_ext(&funcs, triggers->values_num, func_hash_func, func_compare_func, func_clean,
			ZBX_DEFAULT_MEM_MALLOC_FUNC, ZBX_DEFAULT_MEM_REALLOC_FUNC, ZBX_DEFAULT_MEM_FREE_FUNC);

	zbx_populate_function_items(&functionids, &funcs, &ifuncs, triggers);

	if (0 != ifuncs.num_data)
	{
		zbx_evaluate_item_functions(&funcs, history_itemids, history_items, history_errcodes, items, items_err,
				items_num);
		zbx_substitute_functions_results(&ifuncs, triggers);
	}

	zbx_hashset_destroy(&ifuncs);
	zbx_hashset_destroy(&funcs);
empty:
	zbx_vector_uint64_destroy(&functionids);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

static int	evaluate_expression(zbx_eval_context_t *ctx, const zbx_timespec_t *ts, double *result,
		char **error)
{
	zbx_variant_t	 value;

	if (SUCCEED != zbx_eval_execute(ctx, ts, &value, error))
		return FAIL;

	if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG))
	{
		char	*expression = NULL;

		zbx_eval_compose_expression(ctx, &expression);
		zabbix_log(LOG_LEVEL_DEBUG, "%s(): %s => %s", __func__, expression, zbx_variant_value_desc(&value));
		zbx_free(expression);
	}

	if (SUCCEED != zbx_variant_convert(&value, ZBX_VARIANT_DBL))
	{
		*error = zbx_dsprintf(*error, "Cannot convert expression result of type \"%s\" to"
				" floating point value", zbx_variant_type_desc(&value));
		zbx_variant_clear(&value);

		return FAIL;
	}

	*result = value.data.dbl;

	return SUCCEED;
}

static int	expand_expression_macros(zbx_eval_context_t *ctx, zbx_dc_um_handle_t *um_handle,
		const zbx_db_event *db_event, const zbx_uint64_t *hostids, int hostids_num, char **error)
{
	char	err[MAX_STRING_LEN];

	if (SUCCEED != expand_normal_trigger_macros(ctx, db_event, err, sizeof(err)))
	{
		*error = zbx_strdup(NULL, err);
		return FAIL;
	}

	return zbx_eval_expand_user_macros(ctx, hostids, hostids_num,
			(zbx_macro_expand_func_t)zbx_dc_expand_user_and_func_macros, um_handle, error);
}

static int	expand_trigger_macros(zbx_dc_trigger_t *tr, zbx_db_event *db_event, zbx_dc_um_handle_t *um_handle,
		const zbx_vector_uint64_t *hostids, char **error)
{
	db_event->value = tr->value;

	if (SUCCEED != expand_expression_macros(tr->eval_ctx, um_handle, db_event, hostids->values, hostids->values_num,
			error))
	{
		return FAIL;
	}

	if (TRIGGER_RECOVERY_MODE_RECOVERY_EXPRESSION == tr->recovery_mode)
	{
		return expand_expression_macros(tr->eval_ctx_r, um_handle, db_event, hostids->values,
					hostids->values_num, error);
	}

	return SUCCEED;
}

static int	dc_item_compare_by_itemid(const void *d1, const void *d2)
{
	zbx_uint64_t	itemid = *(const zbx_uint64_t *)d1;
	const zbx_history_sync_item_t	*item = (const zbx_history_sync_item_t *)d2;

	ZBX_RETURN_IF_NOT_EQUAL(itemid, item->itemid);
	return 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate trigger expressions.                                     *
 *                                                                            *
 * Parameters: triggers - [IN] vector of zbx_dc_trigger_t pointers, sorted by *
 *                             triggerids                                     *
 *                                                                            *
 ******************************************************************************/
void	zbx_evaluate_expressions(zbx_vector_dc_trigger_t *triggers, const zbx_vector_uint64_t *history_itemids,
		const zbx_history_sync_item_t *history_items, const int *history_errcodes)
{
	zbx_db_event		event;
	zbx_dc_trigger_t	*tr;
	zbx_history_sync_item_t	*items = NULL;
	int			i, *items_err, items_num = 0;
	double			expr_result;
	zbx_dc_um_handle_t	*um_handle;
	zbx_vector_uint64_t	hostids;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() tr_num:%d", __func__, triggers->values_num);

	event.object = EVENT_OBJECT_TRIGGER;

	zbx_vector_uint64_create(&hostids);
	um_handle = zbx_dc_open_user_macros();

	substitute_functions(triggers, history_itemids, history_items, history_errcodes, &items, &items_err,
			&items_num);

	for (i = 0; i < triggers->values_num; i++)
	{
		char	*error = NULL;
		int	j, k;

		tr = triggers->values[i];

		for (j = 0; j < tr->itemids.values_num; j++)
		{
			if (FAIL != (k = zbx_vector_uint64_bsearch(history_itemids, tr->itemids.values[j],
					ZBX_DEFAULT_UINT64_COMPARE_FUNC)))
			{
				if (SUCCEED != history_errcodes[k])
					continue;

				zbx_vector_uint64_append(&hostids, history_items[k].host.hostid);
			}
			else
			{
				zbx_history_sync_item_t	*item;

				if (NULL == items)
					continue;

				item = (zbx_history_sync_item_t *)bsearch(&tr->itemids.values[j], items,
						(size_t)items_num, sizeof(zbx_history_sync_item_t),
						dc_item_compare_by_itemid);

				if (NULL == item || SUCCEED != items_err[item - items])
					continue;

				zbx_vector_uint64_append(&hostids, item->host.hostid);
			}
		}

		zbx_vector_uint64_sort(&hostids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);
		zbx_vector_uint64_uniq(&hostids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);

		if (SUCCEED != expand_trigger_macros(tr, &event, um_handle, &hostids, &error))
		{
			tr->new_error = zbx_dsprintf(tr->new_error, "Cannot evaluate expression: %s", error);
			tr->new_value = TRIGGER_VALUE_UNKNOWN;
			zbx_free(error);
		}

		zbx_vector_uint64_clear(&hostids);
	}

	zbx_dc_close_user_macros(um_handle);
	zbx_vector_uint64_destroy(&hostids);

	if (0 != items_num)
	{
		zbx_dc_config_clean_history_sync_items(items, items_err, (size_t)items_num);
		zbx_free(items);
		zbx_free(items_err);
	}

	/* calculate new trigger values based on their recovery modes and expression evaluations */
	for (i = 0; i < triggers->values_num; i++)
	{
		tr = triggers->values[i];

		if (NULL != tr->new_error)
			continue;

		if (SUCCEED != evaluate_expression(tr->eval_ctx, &tr->timespec, &expr_result, &tr->new_error))
			continue;

		/* trigger expression evaluates to true, set PROBLEM value */
		if (SUCCEED != zbx_double_compare(expr_result, 0.0))
		{
			if (0 == (tr->flags & ZBX_DC_TRIGGER_PROBLEM_EXPRESSION))
			{
				/* trigger value should remain unchanged and no PROBLEM events should be generated if */
				/* problem expression evaluates to true, but trigger recalculation was initiated by a */
				/* time-based function or a new value of an item in recovery expression */
				tr->new_value = TRIGGER_VALUE_NONE;
			}
			else
				tr->new_value = TRIGGER_VALUE_PROBLEM;

			continue;
		}

		/* otherwise try to recover trigger by setting OK value */
		if (TRIGGER_VALUE_PROBLEM == tr->value && TRIGGER_RECOVERY_MODE_NONE != tr->recovery_mode)
		{
			if (TRIGGER_RECOVERY_MODE_EXPRESSION == tr->recovery_mode)
			{
				tr->new_value = TRIGGER_VALUE_OK;
				continue;
			}

			/* processing recovery expression mode */
			if (SUCCEED != evaluate_expression(tr->eval_ctx_r, &tr->timespec, &expr_result, &tr->new_error))
			{
				tr->new_value = TRIGGER_VALUE_UNKNOWN;
				continue;
			}

			if (SUCCEED != zbx_double_compare(expr_result, 0.0))
			{
				tr->new_value = TRIGGER_VALUE_OK;
				continue;
			}
		}

		/* no changes, keep the old value */
		tr->new_value = TRIGGER_VALUE_NONE;
	}

	if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_DEBUG))
	{
		for (i = 0; i < triggers->values_num; i++)
		{
			tr = triggers->values[i];

			if (NULL != tr->new_error)
			{
				zabbix_log(LOG_LEVEL_DEBUG, "%s():expression [%s] cannot be evaluated: %s",
						__func__, tr->expression, tr->new_error);
			}
		}

		zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
	}
}