/*  Copyright (C) CZ.NIC, z.s.p.o. and contributors
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  For more information, see <https://www.knot-dns.cz/>
 */

#pragma once

#include <pthread.h>
#include <stdbool.h>
#include <sys/time.h>

#include "knot/conf/conf.h"
#include "knot/common/evsched.h"
#include "knot/worker/pool.h"
#include "libknot/db/db.h"

struct zone;

typedef enum zone_event_type {
	ZONE_EVENT_INVALID = -1,
	// supported event types
	ZONE_EVENT_LOAD = 0,
	ZONE_EVENT_REFRESH,
	ZONE_EVENT_UPDATE,
	ZONE_EVENT_EXPIRE,
	ZONE_EVENT_PURGE,
	ZONE_EVENT_FLUSH,
	ZONE_EVENT_BACKUP,
	ZONE_EVENT_NOTIFY,
	ZONE_EVENT_DNSSEC,
	ZONE_EVENT_VALIDATE,
	ZONE_EVENT_UFREEZE,
	ZONE_EVENT_UTHAW,
	ZONE_EVENT_DS_CHECK,
	ZONE_EVENT_DS_PUSH,
	ZONE_EVENT_DNSKEY_SYNC,
	// terminator
	ZONE_EVENT_COUNT,
} zone_event_type_t;

typedef struct zone_events {
	pthread_mutex_t mx;		//!< Mutex protecting the struct.
	pthread_mutex_t reschedule_lock;//!< Prevent concurrent reschedule() making mess.

	zone_event_type_t type;		//!< Type of running event.
	time_t running;			//!< Some zone event is being run (since this time).
	pthread_cond_t *run_end;	//!< Notify this one after finishing a job.

	bool frozen;			//!< Terminated, don't schedule new events.
	bool ufrozen;			//!< Updates to the zone temporarily frozen by user.
	bool answering;			//!< Server is answering.

	event_t *event;			//!< Scheduler event.
	worker_pool_t *pool;		//!< Server worker pool.

	worker_task_t task;		//!< Event execution context.
	time_t time[ZONE_EVENT_COUNT];	//!< Event execution times.
	bool forced[ZONE_EVENT_COUNT];  //!< Flag that the event was invoked by user ctl.
	pthread_cond_t *blocking[ZONE_EVENT_COUNT];       //!< For blocking events: dispatching cond.
	int result[ZONE_EVENT_COUNT];   //!< Event return values (in blocking operations).
} zone_events_t;

/*!
 * \brief Initialize zone events.
 *
 * The function will not set up the scheduling, use \ref zone_events_setup
 * to do that.
 *
 * \param zone  Pointer to zone (context of execution).
 *
 * \return KNOT_E*
 */
int zone_events_init(struct zone *zone);

/*!
 * \brief Set up zone events execution.
 *
 * \param zone       Zone to setup.
 * \param workers    Worker thread pool.
 * \param scheduler  Event scheduler.
 *
 * \return KNOT_E*
 */
int zone_events_setup(struct zone *zone, worker_pool_t *workers,
                      evsched_t *scheduler);

/*!
 * \brief Deinitialize zone events.
 *
 * \param zone  Zone whose events we want to deinitialize.
 */
void zone_events_deinit(struct zone *zone);

/*!
 * \brief Enqueue event type for asynchronous execution.
 *
 * \note This is similar to the scheduling an event for NOW, but it can
 *       bypass the event scheduler if no event is running at the moment.
 *
 * \param zone  Zone to schedule new event for.
 * \param type  Type of event.
 */
void zone_events_enqueue(struct zone *zone, zone_event_type_t type);

/*!
 * \brief Schedule new zone event.
 *
 * The function allows to set multiple events at once.
 *
 * The function interprets time values (t) as follows:
 *
 *   t > 0: schedule timer for a given time
 *   t = 0: cancel the timer
 *   t < 0: ignore change in the timer
 *
 * If the event is already scheduled, the new time will be set only if the
 * new time is earlier than the currently scheduled one. To override the
 * check, cancel and schedule the event in a single function call.
 *
 * \param zone  Zone to schedule new event for.
 * \param ...   Sequence of zone_event_type_t and time_t terminated with
 *              ZONE_EVENT_INVALID.
 */
void _zone_events_schedule_at(struct zone *zone, ...);

#define zone_events_schedule_at(zone, events...) \
	_zone_events_schedule_at(zone, events, ZONE_EVENT_INVALID)

#define zone_events_schedule_now(zone, type) \
	zone_events_schedule_at(zone, type, time(NULL))

/*!
 * \brief Schedule zone event to now, with forced flag.
 */
void zone_events_schedule_user(struct zone *zone, zone_event_type_t type);

/*!
 * \brief Schedule new zone event as soon as possible and wait for it's
 * completion (end of task run), with optional forced flag.
 *
 * \param zone  Zone to schedule new event for.
 * \param type  Zone event type.
 * \param user  Forced flag indication.
 *
 * \return KNOT_E*
 */
int zone_events_schedule_blocking(struct zone *zone, zone_event_type_t type, bool user);

/*!
 * \brief Freeze all zone events and prevent new events from running.
 *
 * \param zone  Zone to freeze events for.
 */
void zone_events_freeze(struct zone *zone);

/*!
 * \brief Freeze zone events and wait for running event to finish.
 *
 * \param zone  Zone to freeze events for.
 */
void zone_events_freeze_blocking(struct zone *zone);

/*!
 * \brief Set "answering" bit to true.
 */
void zone_events_start_answering(struct zone *zone);

/*!
 * \brief ufreeze_applies
 * \param type Type of event to be checked
 * \return true / false if user freeze applies
 */
bool ufreeze_applies(zone_event_type_t type);

/*!
 * \brief Events that require answering server.
 */
inline static bool only_started(zone_event_type_t type)
{
	return type == ZONE_EVENT_NOTIFY;
}

/*!
 * \brief Start the events processing.
 *
 * \param zone  Zone to start processing for.
 */
void zone_events_start(struct zone *zone);

/*!
 * \brief Return time of the occurrence of the given event.
 *
 * \param zone  Zone to get event time from.
 * \param type  Event type.
 *
 * \retval time of the event when event found
 * \retval 0 when the event is not planned
 * \retval negative value if event is invalid
 */
time_t zone_events_get_time(const struct zone *zone, zone_event_type_t type);

/*!
 * \brief Return text name of the event.
 *
 * \param type  Type of event.
 *
 * \retval String with event name if it exists.
 * \retval NULL if the event does not exist.
 */
const char *zone_events_get_name(zone_event_type_t type);

/*!
 * \brief Return time and type of the next event.
 *
 * \param zone  Zone to get next event from.
 * \param type  [out] Type of the next event will be stored in the parameter.
 *
 * \return time of the next event or an error (negative number)
 */
time_t zone_events_get_next(const struct zone *zone, zone_event_type_t *type);
