async function main(
) : Promise<void>
{
	// consts
	const version : string = "0.8";
	const workdir : string = __dirname;
	
	// init translations
	// TODO: use env language
	await lib_plankton.translate.initialize_promise(
		{
			"verbosity": 1,
			"order": ["de", "en"],
			"packages": await Promise.all(
				[
					{"identifier": "de", "path": (workdir + "/localization/de.json")},
					{"identifier": "en", "path": (workdir + "/localization/en.json")},
				]
				.map(
					(entry) => (
						lib_plankton.file.read(entry.path)
						.then(content => Promise.resolve(JSON.parse(content)))
						.then(tree => Promise.resolve({"meta": {"identifier": entry.identifier}, "tree": tree}))
					)
				)
			),
		}
	);
	
	// args
	const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler(
		{
			"order_path": new lib_plankton.args.class_argument(
				{
					"name": "order_path",
					"type": lib_plankton.args.enum_type.string,
					"kind": lib_plankton.args.enum_kind.positional,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": "monitoring.hmdl.json",
					"parameters": {
						"index": 0,
					},
					"info": lib_plankton.translate.get("help.args.order_path"),
				}
			),
			"show_help": new lib_plankton.args.class_argument(
				{
					"name": "help",
					"type": lib_plankton.args.enum_type.boolean,
					"kind": lib_plankton.args.enum_kind.volatile,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": false,
					"parameters": {
						"indicators_long": ["help"],
						"indicators_short": ["h"],
					},
					"info": lib_plankton.translate.get("help.args.show_help"),
				}
			),
			"show_version": new lib_plankton.args.class_argument(
				{
					"name": "version",
					"type": lib_plankton.args.enum_type.boolean,
					"kind": lib_plankton.args.enum_kind.volatile,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": false,
					"parameters": {
						"indicators_long": ["version"],
						"indicators_short": ["r"],
					},
					"info": lib_plankton.translate.get("help.args.show_version"),
				}
			),
			"show_schema": new lib_plankton.args.class_argument(
				{
					"name": "schema",
					"type": lib_plankton.args.enum_type.boolean,
					"kind": lib_plankton.args.enum_kind.volatile,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": false,
					"parameters": {
						"indicators_long": ["schema"],
						"indicators_short": ["s"],
					},
					"info": lib_plankton.translate.get("help.args.show_schema"),
				}
			),
			"expose_full_order": new lib_plankton.args.class_argument(
				{
					"name": "expose_full_order",
					"type": lib_plankton.args.enum_type.boolean,
					"kind": lib_plankton.args.enum_kind.volatile,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": false,
					"parameters": {
						"indicators_long": ["expose-full-order"],
						"indicators_short": ["e"],
					},
					"info": lib_plankton.translate.get("help.args.expose_full_order"),
				}
			),
			"erase_state": new lib_plankton.args.class_argument(
				{
					"name": "erase_state",
					"type": lib_plankton.args.enum_type.boolean,
					"kind": lib_plankton.args.enum_kind.volatile,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": false,
					"parameters": {
						"indicators_long": ["erase-state"],
						"indicators_short": ["x"],
					},
					"info": lib_plankton.translate.get("help.args.erase_state"),
				}
			),
			"database_path": new lib_plankton.args.class_argument(
				{
					"name": "database_path",
					"type": lib_plankton.args.enum_type.string,
					"kind": lib_plankton.args.enum_kind.volatile,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": null,
					"parameters": {
						"indicators_long": ["database-path"],
						"indicators_short": ["d"],
					},
					"info": lib_plankton.translate.get("help.args.database_path"),
				}
			),
			"mutex_path": new lib_plankton.args.class_argument(
				{
					"name": "mutex_path",
					"type": lib_plankton.args.enum_type.string,
					"kind": lib_plankton.args.enum_kind.volatile,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": "/tmp/heimdall.lock",
					"parameters": {
						"indicators_long": ["mutex-path"],
						"indicators_short": ["m"],
					},
					"info": lib_plankton.translate.get("help.args.mutex_path"),
				}
			),
			"send_ok_notifications": new lib_plankton.args.class_argument(
				{
					"name": "send_ok_notifications",
					"type": lib_plankton.args.enum_type.boolean,
					"kind": lib_plankton.args.enum_kind.volatile,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": false,
					"parameters": {
						"indicators_long": ["send-ok-notifications"],
						"indicators_short": ["y"],
					},
					"info": lib_plankton.translate.get(
						"help.args.send_ok_notifications",
						{
							"condition_name": lib_plankton.translate.get("conditions.ok"),
						}
					),
				}
			),
			"language": new lib_plankton.args.class_argument(
				{
					"name": "language",
					"type": lib_plankton.args.enum_type.string,
					"kind": lib_plankton.args.enum_kind.volatile,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": null,
					"parameters": {
						"indicators_long": ["language"],
						"indicators_short": ["l"],
					},
					"info": lib_plankton.translate.get("help.args.language"),
				}
			),
			"time_to_live": new lib_plankton.args.class_argument(
				{
					"name": "time_to_live",
					"type": lib_plankton.args.enum_type.float,
					"kind": lib_plankton.args.enum_kind.volatile,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": (60 * 60 * 24 * 7),
					"parameters": {
						"indicators_long": ["time-to-live"],
						"indicators_short": ["t"],
					},
					"info": lib_plankton.translate.get("help.args.time_to_live"),
				}
			),
			"verbosity": new lib_plankton.args.class_argument(
				{
					"name": "verbosity",
					"type": lib_plankton.args.enum_type.integer,
					"kind": lib_plankton.args.enum_kind.volatile,
					"mode": lib_plankton.args.enum_mode.replace,
					"default": 1,
					"parameters": {
						"indicators_long": ["verbosity"],
						"indicators_short": ["v"],
					},
					"info": lib_plankton.translate.get("help.args.verbosity"),
				}
			),
		}
	);
	const args : Record<string, any> = arg_handler.read(
		lib_plankton.args.enum_environment.cli,
		process.argv.slice(2).join(" ")
	);
	
	// setup logging
	lib_plankton.log.conf_push(
		[
			new lib_plankton.log.class_channel_minlevel(
				new lib_plankton.log.class_channel_stdout(),
				[
					lib_plankton.log.enum_level.debug,
					lib_plankton.log.enum_level.info,
					lib_plankton.log.enum_level.notice,
					lib_plankton.log.enum_level.warning,
					lib_plankton.log.enum_level.error,
				][Math.min(4, args["verbosity"])]
			),
		]
	);
	
	// exec
	if (args.show_help) {
		process.stdout.write(
			arg_handler.generate_help(
				{
					"programname": "heimdall",
					"description": lib_plankton.translate.get("help.title"),
					"executable": "heimdall",
				}
			)
		);
	}
	else {
		if (args["show_version"]) {
			process.stdout.write(version + "\n");
		}
		else {
			const notification_kind_implementations : Record<string, _heimdall.notification_kinds.type_notification_kind> = {
				"console": _heimdall.notification_kinds.console.notification_kind_implementation(),
				"email": _heimdall.notification_kinds.email.notification_kind_implementation(),
				"libnotify": _heimdall.notification_kinds.libnotify.notification_kind_implementation(),
			};
			const check_kind_implementations : Record<string, _heimdall.check_kinds.type_check_kind> = {
				"script": _heimdall.check_kinds.script.check_kind_implementation(),
				"http_request": _heimdall.check_kinds.http_request.check_kind_implementation(),
				"file_state": _heimdall.check_kinds.file_state.check_kind_implementation(),
				"tls_certificate": _heimdall.check_kinds.tls_certificate.check_kind_implementation(),
				"generic_remote": _heimdall.check_kinds.generic_remote.check_kind_implementation(),
			};
			if (args["show_schema"]) {
				process.stdout.write(
					lib_plankton.json.encode(
						_heimdall.order.schema_root(
							check_kind_implementations,
							notification_kind_implementations
						),
						true
					)
					+
					"\n"
				)
			}
			else {
				const nm_path = require("path");
				const nm_fs = require("fs");
				if (! nm_fs.existsSync(args["order_path"])) {
					process.stderr.write(
						lib_plankton.translate.get(
							"misc.order_file_not_found",
							{
								"path": args["order_path"],
							}
						)
						+
						"\n"
					);
				}
				else {
					// get order data
					const order = await _heimdall.order.load(
						check_kind_implementations,
						notification_kind_implementations,
						nm_path.normalize(args["order_path"])
					);
					
					if (args["expose_full_order"]) {
						process.stdout.write(lib_plankton.json.encode(order, true) + "\n");
					}
					else {
						const database_path : string = (
							(args["database_path"] !== null)
							? args["database_path"]
							: 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(
											args["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,
							}
						);
						
						// mutex check
						if (nm_fs.existsSync(args["mutex_path"])) {
							lib_plankton.log.error(
								lib_plankton.translate.get("misc.still_running"),
								{
									"mutex_path": args["mutex_path"],
								}
							);
							process.exit(2);
						}
						else {
							await _heimdall.state_repository.setup(database_path);
							const clean_count : int = await _heimdall.state_repository.clean(
								database_path,
								args["time_to_live"],
								args["erase_state"]
							);
							
							// create mutex file
							await lib_plankton.file.write(args["mutex_path"], "");
							
							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(
										database_path,
										check.name
									);
									
									const rows : Array<
										{
											timestamp : int;
											condition : _heimdall.enum_condition;
											notification_sent : boolean;
										}
									> = await _heimdall.state_repository.probe(
										database_path,
										check.name,
										(check.threshold + 1),
									);
									if (rows.length <= 0) {
										old_item_state = null;
									}
									else {
										let count : int = 1;
										rows.slice(1).some(
											(row) => {
												if (row.condition === rows[0].condition) {
													count += 1;
													return true;
												}
												else {
													return false;
												}
											}
										);
										if (count > check.threshold) {
											count = null;
										}
										else {
											// do nothing
										}
										old_item_state = {
											"timestamp": rows[0].timestamp,
											"condition": rows[0].condition,
											"count": count,
											"last_notification_timestamp": last_notification_timestamp,
										}
										
										const timestamp : int = _heimdall.get_current_timestamp();
										const due : boolean = (
											(old_item_state === null)
											||
											(old_item_state.condition !== _heimdall.enum_condition.ok)
											||
											((timestamp - old_item_state.timestamp) >= check.schedule.regular_interval)
											||
											(
												(! (old_item_state.count === null))
												&&
												((timestamp - old_item_state.timestamp) >= check.schedule.attentive_interval)
											)
										);
										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;
											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 = (
												(
													(
														(! (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)
													||
													args["send_ok_notifications"]
												)
											)
											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(
												database_path,
												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
															)
														)
													}
												);
											}
										}
									}
								}
							}
							
							// drop mutex file
							await (
								new Promise<void>(
									(resolve, reject) => {
										nm_fs.unlink(
											args["mutex_path"],
											() => {
												resolve(undefined);
											}
										);
									}
								)
							);
						}							
					}
				}
			}
		}
	}
}

main();
