namespace _heimdall.master
{
	
	/**
	 * @todo automated tests
	 */
	export function determine_due(
		check : _heimdall.type_check,
		old_item_state : (null | _heimdall.type_item_state),
		options : {
			timestamp ?: int;
		} = {}
	) : boolean
	{
		options = Object.assign(
			{
				"timestamp": _heimdall.get_current_timestamp(),
			},
			options
		);
		
		return (
			check.active
			&&
			(
				(old_item_state === null)
				||
				((options.timestamp - old_item_state.timestamp) >= check.schedule.regular_interval)
				||
				(
					(old_item_state.count !== null)
					&&
					(old_item_state.condition !== _heimdall.enum_condition.ok)
					&&
					((options.timestamp - old_item_state.timestamp) >= check.schedule.attentive_interval)
				)
			)
		);
	}


	/**
	 * @todo automated tests
	 */
	export function determine_shall_send_notification(
		check : _heimdall.type_check,
		send_ok_notifications : boolean,
		count : (null | int),
		timestamp : int,
		old_item_state : (null | _heimdall.type_item_state),
		result : _heimdall.type_result
	) : boolean
	{
		return (
			(
				(
					(! (count === null))
					&&
					(count === check.threshold)
				)
				||
				(
					(count === null)
					&&
					check.annoy
				)
				||
				(
					(count === null)
					&&
					(
						(! (old_item_state === null))
						&&
						(! (old_item_state.last_notification_timestamp === null))
						&&
						(! (check.schedule.reminding_interval === null))
						&&
						(
							(timestamp - old_item_state.last_notification_timestamp)
							>=
							check.schedule.reminding_interval
						)
					)
				)
			)
			&&
			(
				(result.condition !== _heimdall.enum_condition.ok)
				||
				send_ok_notifications
			)
		);
	}


	/**
	 */
	export async function clean(
		options : {
			time_to_live ?: int;
			erase_state ?: boolean;
		} = {}
	) : Promise<void>
	{
		options = Object.assign(
			{
				"time_to_live": (7 * 24 * 60 * 60),
				"erase_state": false,
			},
			options
		);
		
		const count : int = await _heimdall.state_repository.clean(
			options.time_to_live,
			options.erase_state
		);
		if (count <= 0) {
			// do nothing
		}
		else {
			lib_plankton.log.info(
				lib_plankton.translate.get(
					"misc.cleanup_info",
					{
						"count": count.toFixed(0),
					}
				),
				{
				}
			);
		}
	}


	/**
	 */
	export async function wrap_database(
		database_path_raw : (null | string),
		order_path : string,
		procedure : (() => Promise<void>)
	) : Promise<void>
	{
		const nm_path = require("path");
		const database_path : string = (
			(database_path_raw !== null)
			? database_path_raw
			: nm_path.join(
				// TODO get path from fs or path module?
				"/tmp",
				lib_plankton.string.coin(
					"monitoring-state-{{id}}.sqlite",
					{
						"id": lib_plankton.call.convey(
							order_path,
							[
								nm_path.normalize,
								// encode(ascii),
								x => lib_plankton.sha256.get(x),
								x => x.slice(0, 8),
							]
						),
					}
				)
			)
		);
		lib_plankton.log.info(
			lib_plankton.translate.get("misc.state_file_path"),
			{
				"database_path": database_path,
			}
		);
		await _heimdall.state_repository.init(database_path);
		await procedure();
		await _heimdall.state_repository.kill();
	}
	
	
	/**
	 * @todo outsource?
	 */
	export async function wrap_mutex(
		mutex_path : string,
		procedure : (() => Promise<void>)
	) : Promise<void>
	{
		const nm_fs = require("fs");
		if (nm_fs.existsSync(mutex_path)) {
			lib_plankton.log.error(
				lib_plankton.translate.get("misc.still_running"),
				{
					"mutex_path": mutex_path,
				}
			);
			return Promise.reject();
		}
		else {
			lib_plankton.log.info("mutex_setting", {});
			await lib_plankton.file.write(mutex_path, "");
			await procedure();
			await lib_plankton.file.delete_(mutex_path);
			lib_plankton.log.info("mutex_released", {});
		}
	}


	/**
	 * central processing procedure
	 */
	export async function run(
		order : _heimdall.type_order,
		options : {
			send_ok_notifications ?: boolean;
		} = {}
	) : Promise<void>
	{
		options = Object.assign(
			{
				"send_ok_notifications": false
			},
			options
		);
		
		const notification_kind_implementations : Record<string, _heimdall.notification_kinds.type_notification_kind> = _heimdall.notification_kinds.get_implementations();
		const check_kind_implementations : Record<string, _heimdall.check_kinds.type_check_kind> = _heimdall.check_kinds.get_implementations();
		for await (const check of order.checks) {
			if (! check.active) {
				// do nothing
			}
			else {
				let old_item_state : (null | _heimdall.type_item_state);
				
				const last_notification_timestamp : (null | int) = await _heimdall.state_repository.get_last_notification_timestamp(
					check.name
				);
				
				const rows : Array<
					{
						timestamp : int;
						condition : _heimdall.enum_condition;
						notification_sent : boolean;
					}
				> = await _heimdall.state_repository.probe(
					check.name,
					(check.threshold + 1)
				);
				if (rows.length <= 0) {
					old_item_state = null;
				}
				else {
					let index : int = 1;
					while (index < rows.length) {
						if (rows[index].condition === rows[0].condition) {
							index += 1;
						}
						else {
							break;
						}
					}
					old_item_state = {
						"timestamp": rows[0].timestamp,
						"condition": rows[0].condition,
						"count": ((index < check.threshold) ? index : null),
						"last_notification_timestamp": last_notification_timestamp,
					};
				}
				const timestamp : int = _heimdall.get_current_timestamp();
				const due : boolean = determine_due(
					check,
					old_item_state,
					{
						"timestamp": timestamp,
					}
				);
				if (! due) {
					// do nothing
				}
				else {
					process.stderr.write(
						lib_plankton.string.coin(
							"-- {{check_name}}\n",
							{
								"check_name": check.name,
							}
						)
					);
					
					// execute check and set new state
					let result : _heimdall.type_result;
					try {
						result = await check_kind_implementations[check.kind].run(check.parameters);
					}
					catch (error) {
						result = {
							"condition": _heimdall.enum_condition.unknown,
							"info": {
								"cause": lib_plankton.translate.get("misc.check_procedure_failed"),
								"error": error.toString(),
							},
						};
					}
					const count : (null | int) = (
						(
							(old_item_state === null)
							||
							(old_item_state.condition !== result.condition)
						)
						? 1
						: (
							(
								(! (old_item_state.count === null))
								&&
								((old_item_state.count + 1) <= check.threshold)
							)
							? (old_item_state.count + 1)
							: null
						)
					);
					const shall_send_notification : boolean = determine_shall_send_notification(
						check,
						options.send_ok_notifications,
						count,
						timestamp,
						old_item_state,
						result
					);
					const new_item_state : _heimdall.type_item_state = {
						"timestamp": timestamp,
						"condition": result.condition,
						"count": count,
						"last_notification_timestamp": (
							shall_send_notification
							? timestamp
							: (
								(old_item_state === null)
								? null
								: old_item_state.last_notification_timestamp
							)
						),
					}
					await _heimdall.state_repository.feed(
						check.name,
						timestamp,
						result.condition,
						shall_send_notification,
						result.info
					);
					
					// send notifications
					if (! shall_send_notification) {
						// do nothing
					}
					else {
						check.notifications.forEach(
							notification => {
								notification_kind_implementations[notification.kind].notify(
									notification.parameters,
									check.name,
									check,
									new_item_state,
									Object.assign(
										(
											(check.custom === null)
											? {}
											: {"custom": check.custom}
										),
										result.info
									)
								)
							}
						);
					}
				}
			}
		}
	}
	
}
