/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program 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 2 of the License, or
** (at your option) any later version.
**
** 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; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

#include "vmware.h"
#include "zbxnix.h"
#include "zbxself.h"
#include "zbxtime.h"

#if defined(HAVE_LIBXML2) && defined(HAVE_LIBCURL)

extern int				CONFIG_VMWARE_FREQUENCY;
extern int				CONFIG_VMWARE_PERF_FREQUENCY;

#define ZBX_VMWARE_CACHE_UPDATE_PERIOD	CONFIG_VMWARE_FREQUENCY
#define ZBX_VMWARE_PERF_UPDATE_PERIOD	CONFIG_VMWARE_PERF_FREQUENCY
#define ZBX_VMWARE_SERVICE_TTL		SEC_PER_HOUR

extern zbx_vmware_t			*vmware;

/******************************************************************************
 *                                                                            *
 * Purpose: return string value of vmware job types                           *
 *                                                                            *
 * Parameters: job - [IN] the vmware job                                      *
 *                                                                            *
 * Return value: job type string                                              *
 *                                                                            *
 ******************************************************************************/
static const char	*vmware_job_type_string(zbx_vmware_job_t *job)
{
	switch (job->type)
	{
		case ZBX_VMWARE_UPDATE_CONF:
			return "update_conf";
		case ZBX_VMWARE_UPDATE_PERFCOUNTERS:
			return "update_perf_counters";
		case ZBX_VMWARE_UPDATE_REST_TAGS:
			return "update_tags";
		default:
			return "unknown_job";
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: pick the next job from the queue and service ttl check            *
 *                                                                            *
 * Parameters: vmw      - [IN] the vmware object                              *
 *             time_now - [IN] the current time                               *
 *                                                                            *
 * Return value: job for object or NULL                                       *
 *                                                                            *
 ******************************************************************************/
static zbx_vmware_job_t	*vmware_job_get(zbx_vmware_t *vmw, time_t time_now)
{
	zbx_binary_heap_elem_t	*elem;
	zbx_vmware_job_t	*job = NULL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() queue:%d", __func__, vmw->jobs_queue.elems_num);

	zbx_vmware_lock();

	if (SUCCEED == zbx_binary_heap_empty(&vmw->jobs_queue))
		goto unlock;

	elem = zbx_binary_heap_find_min(&vmw->jobs_queue);
	job = (zbx_vmware_job_t *)elem->data;

	if (time_now < job->nextcheck)
	{
		job = NULL;
		goto unlock;
	}

	zbx_binary_heap_remove_min(&vmw->jobs_queue);
	job->nextcheck = 0;

	if (0 != job->service->lastaccess && time_now - job->service->lastaccess > ZBX_VMWARE_SERVICE_TTL)
		job->expired = SUCCEED;
unlock:
	zbx_vmware_unlock();
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() queue:%d type:%s", __func__, vmw->jobs_queue.elems_num,
			NULL == job ? "none" : vmware_job_type_string(job));

	return job;
}

/******************************************************************************
 *                                                                            *
 * Purpose: execute task of job                                               *
 *                                                                            *
 * Parameters: job - [IN] the job object                                      *
 *                                                                            *
 * Return value: count of successfully executed jobs                          *
 *                                                                            *
 ******************************************************************************/
static int	vmware_job_exec(zbx_vmware_job_t *job)
{
	int	ret = FAIL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() type:%s", __func__, vmware_job_type_string(job));

	if (ZBX_VMWARE_UPDATE_CONF != job->type && 0 == (job->service->state & ZBX_VMWARE_STATE_READY))
		goto out;

	switch (job->type)
	{
		case ZBX_VMWARE_UPDATE_CONF:
			ret = zbx_vmware_service_update(job->service);
			break;
		case ZBX_VMWARE_UPDATE_PERFCOUNTERS:
			ret = zbx_vmware_service_update_perf(job->service);
			break;
		case ZBX_VMWARE_UPDATE_REST_TAGS:
			ret = zbx_vmware_service_update_tags(job->service);
			break;
		default:
			ret = FAIL;
	}
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() type:%s ret:%s", __func__, vmware_job_type_string(job),
			zbx_result_string(ret));

	return SUCCEED == ret ? 1 : 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: set time of next job execution and return job to the queue        *
 *                                                                            *
 * Parameters: vmw      - [IN] the vmware object                              *
 *             job      - [IN] the job object                                 *
 *             time_now - [IN] the current time                               *
 *                                                                            *
 ******************************************************************************/
static void	vmware_job_schedule(zbx_vmware_t *vmw, zbx_vmware_job_t *job, time_t time_now)
{
	zbx_binary_heap_elem_t	elem_new = {0, job};

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() queue:%d type:%s", __func__, vmw->jobs_queue.elems_num,
			vmware_job_type_string(job));

	switch (job->type)
	{
	case ZBX_VMWARE_UPDATE_CONF:
		job->nextcheck = time_now + ZBX_VMWARE_CACHE_UPDATE_PERIOD;
		break;
	case ZBX_VMWARE_UPDATE_PERFCOUNTERS:
		job->nextcheck = time_now + ZBX_VMWARE_PERF_UPDATE_PERIOD;
		break;
	case ZBX_VMWARE_UPDATE_REST_TAGS:
		job->nextcheck = time_now + ZBX_VMWARE_CACHE_UPDATE_PERIOD;
		break;
	}

	zbx_vmware_lock();
	zbx_binary_heap_insert(&vmw->jobs_queue, &elem_new);
	zbx_vmware_unlock();

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() type:%s nextcheck:%s", __func__, vmware_job_type_string(job),
			zbx_time2str(job->nextcheck, NULL));
}

#endif

/******************************************************************************
 *                                                                            *
 * Purpose: the vmware collector main loop                                    *
 *                                                                            *
 ******************************************************************************/
ZBX_THREAD_ENTRY(vmware_thread, args)
{
#if defined(HAVE_LIBXML2) && defined(HAVE_LIBCURL)
	int			services_updated = 0, services_removed = 0;
	double			time_now, time_stat, time_idle = 0;
	const zbx_thread_info_t	*info = &((zbx_thread_args_t *)args)->info;
	int			server_num = ((zbx_thread_args_t *)args)->info.server_num;
	int			process_num = ((zbx_thread_args_t *)args)->info.process_num;
	unsigned char		process_type = ((zbx_thread_args_t *)args)->info.process_type;

	zabbix_log(LOG_LEVEL_INFORMATION, "%s #%d started [%s #%d]", get_program_type_string(info->program_type),
			server_num, get_process_type_string(process_type), process_num);

	zbx_update_selfmon_counter(info, ZBX_PROCESS_STATE_BUSY);

#define JOB_TIMEOUT	1
#define STAT_INTERVAL	5	/* if a process is busy and does not sleep then update status not faster than */
				/* once in STAT_INTERVAL seconds */

	time_stat = zbx_time();

	while (ZBX_IS_RUNNING())
	{
		zbx_vmware_job_t	*job;

		time_now = zbx_time();
		zbx_update_env(get_process_type_string(process_type), time_now);

		if (STAT_INTERVAL < time_now - time_stat)
		{
			zbx_setproctitle("%s #%d [updated %d, removed %d VMware services, idle " ZBX_FS_DBL
					" sec during " ZBX_FS_DBL " sec]", get_process_type_string(process_type),
					process_num, services_updated, services_removed,
					time_idle, time_now - time_stat);

			time_stat = time_now;
			time_idle = 0;
			services_updated = 0;
			services_removed = 0;
		}

		while (NULL != (job = vmware_job_get(vmware, (int)time_now)))
		{
			if (SUCCEED == job->expired)
			{
				services_removed += zbx_vmware_job_remove(job);
				continue;
			}

			services_updated += vmware_job_exec(job);
			vmware_job_schedule(vmware, job, (time_t)time_now);
		}

		if (zbx_time() - time_now <= JOB_TIMEOUT)
		{
			time_idle += JOB_TIMEOUT;
			zbx_update_selfmon_counter(info, ZBX_PROCESS_STATE_IDLE);
			zbx_sleep_loop(info, JOB_TIMEOUT);
			zbx_update_selfmon_counter(info, ZBX_PROCESS_STATE_BUSY);
		}
	}

	zbx_setproctitle("%s #%d [terminated]", get_process_type_string(process_type), process_num);

	while (1)
		zbx_sleep(SEC_PER_MIN);
#undef STAT_INTERVAL
#undef JOB_TIMEOUT
#else
	ZBX_UNUSED(args);
	THIS_SHOULD_NEVER_HAPPEN;
	zbx_thread_exit(EXIT_SUCCESS);
#endif
}