namespace _heimdall.order
{
	
	/**
	 */
	function schema_active(
	) : _heimdall.helpers.json_schema.type_schema
	{
		return {
			"description": "whether the check shall be executed",
			"type": "boolean",
			"default": true,
		};
	}
	
	
	/**
	 */
	function schema_threshold(
	) : _heimdall.helpers.json_schema.type_schema
	{
		return {
			"description": "how often a condition has to occur in order to be reported",
			"type": "integer",
			"minimum": 1,
			"default": 3,
		};
	}
	
	
	/**
	 */
	function schema_annoy(
	) : _heimdall.helpers.json_schema.type_schema
	{
		return {
			"description": "whether notifications about non-ok states shall be kept sending after the threshold has been surpassed",
			"type": "boolean",
			"default": false,
		};
	}
		
	
	/**
	 */
	function schema_interval(
		allow_null : boolean,
		default_
	) : _heimdall.helpers.json_schema.type_schema
	{
		return {
			"anyOf": (
				[]
				.concat(
					allow_null
					? [
						{
							"type": "null",
						}
					]
					: []
				)
				.concat(
					[
						{
							"description": "in seconds",
							"type": "integer",
							"exclusiveMinimum": 0,
						},
						{
							"description": "as text",
							"type": "string",
							"enum": [
								"minute",
								"hour",
								"day",
								"week",
							]
						},
					]
				)
			),
			"default": default_,
		};
	}
	
	
	/**
	 */
	function schema_schedule(
	) : _heimdall.helpers.json_schema.type_schema
	{
		return {
			"type": "object",
			"additionalProperties": false,
			"properties": {
				"regular_interval": schema_interval(false, (60 * 60)),
				"attentive_interval": schema_interval(false, (60 * 2)),
				"reminding_interval": schema_interval(true, (60 * 60 * 24)),
			},
			"required": [
			],
		};
	}
	
	
	/**
	 */
	function schema_notifications(
	) : _heimdall.helpers.json_schema.type_schema
	{
		const notification_kind_implementations = _heimdall.notification_kinds.get_implementations();
		return {
			"type": "array",
			"items": {
				"anyOf": (
					Object.entries(notification_kind_implementations)
					.map(
						([key, value]) => ({
							"title": ("notification channel '" + key + "'"),
							"type": "object",
							"unevaluatedProperties": false,
							"properties": {
								"kind": {
									"type": "string",
									"enum": [key]
								},
								"parameters": value.parameters_schema(),
							},
							"required": [
								"kind",
								"parameters"
							],
						})
					)
				)
			},
			"default": [
				{
					"kind": "console",
					"parameters": {
					}
				},
			],
		};
	}
	
	
	/**
	 */
	export function schema_root(
	) : _heimdall.helpers.json_schema.type_schema
	{
		const check_kind_implementations = _heimdall.check_kinds.get_implementations();
		return {
			"type": "object",
			"additionalProperties": false,
			"properties": {
				"defaults": {
					"description": "default values for checks",
					"type": "object",
					"additionalProperties": false,
					"properties": {
						"active": schema_active(),
						"threshold": schema_threshold(),
						"annoy": schema_annoy(),
						"schedule": schema_schedule(),
						"notifications": schema_notifications(),
					},
					"required": [
					],
				},
				"includes": {
					"type": "array",
					"items": {
						"type": "string"
					},
					"default": [],
					"description": "list of relative or absolute paths to other hmdl files on the local machine, which shall be subsumed in the overall monitoring task"
				},
				"checks": {
					"type": "array",
					"items": {
						"allOf": [
							{
								"description": "should represent a specific check",
								"type": "object",
								"unevaluatedProperties": false,
								"properties": {
									"name": {
										"type": "string"
									},
									"title": {
										"type": "string"
									},
									"active": schema_active(),
									"threshold": schema_threshold(),
									"annoy": schema_annoy(),
									"schedule": schema_schedule(),
									"notifications": schema_notifications(),
								},
								"required": [
									"name",
								],
							},
							{
								"anyOf": (
									Object.entries(check_kind_implementations)
									.map(
										([key, value]) => ({
											"title": ("check '" + key + "'"),
											"type": "object",
											"unevaluatedProperties": false,
											"properties": {
												"kind": {
													"type": "string",
													"enum": [key]
												},
												"parameters": value.parameters_schema(),
												"custom": {
													"type": "any",
													"description": "custom data, which shall be attached to notifications",
													"default": null,
												},
											},
											"required": [
												"kind",
												"parameters",
											]
										})
									)
								),
							},
						]
					}
				}
			},
			"required": [
			]
		};
	}
		
	
	/**
	 */
	function normalize_interval(
		interval_raw
	) : (null | _heimdall.type_interval)
	{
		if (interval_raw === null) {
			return null;
		}
		else {
			if (typeof(interval_raw) === "number") {
				return interval_raw;
			}
			else if (typeof(interval_raw) === "string") {
				const map_ : Record<("minute" | "hour" | "day" | "week"), int> = {
					"minute": (60),
					"hour": (60 * 60),
					"day": (60 * 60 * 24),
					"week": (60 * 60 * 24 * 7),
				};
				if (! (interval_raw in map_)) {
					throw new Error("invalid string interval value: " + interval_raw);
				}
				else {
					return map_[interval_raw];
				}
			}
			else {
				throw new Error("invalid type for interval value");
			}
		}
	}
			
	
	/**
	 */
	function normalize_schedule(
		node
	) : _heimdall.type_schedule
	{
		const node_ = Object.assign(
			{
				"regular_interval": (60 * 60),
				"attentive_interval": (60 * 2),
				"reminding_interval": (60 * 60 * 24),
			},
			node
		);
		return {
			"regular_interval": normalize_interval(node_["regular_interval"]),
			"attentive_interval": normalize_interval(node_["attentive_interval"]),
			"reminding_interval": normalize_interval(node_["reminding_interval"]),
		};
	}
	
	
	/**
	 */
	function normalize_notification(
		node : any
	) : type_notification
	{
		const notification_kind_implementations = _heimdall.notification_kinds.get_implementations();
		if (! (node["kind"] in notification_kind_implementations)) {
			throw new Error("invalid notification kind: " + node["kind"]);
		}
		else {
			return {
				"kind": node["kind"],
				"parameters": notification_kind_implementations[node["kind"]].normalize_order_node(node["parameters"]),
			};
		}
	}
	
	
	/**
	 */
	function normalize_defaults(
		node : any
	) : type_check_common
	{
		const node_ = Object.assign(
			{
				"active": true,
				"threshold": 3,
				"annoy": false,
				"schedule": {
					"regular_interval": (60 * 60),
					"attentive_interval": (60 * 2),
					"reminding_interval": (60 * 60 * 24),
				},
				"notifications": [
				],
			},
			node
		)
		return {
			"active": node_["active"],
			"threshold": node_["threshold"],
			"annoy": node_["annoy"],
			"schedule": node_["schedule"],
			"notifications": (
				node_["notifications"]
				.map(x => normalize_notification(x))
			),
		};
	}
	
	
	/**
	 */
	function normalize_check(
		defaults : type_check_common,
		node : {
			name : string;
			kind : string;
		}
	) : type_check
	{
		const check_kind_implementations = _heimdall.check_kinds.get_implementations();
		const notification_kind_implementations = _heimdall.notification_kinds.get_implementations();
		if (! ("name" in node)) {
			throw new Error("missing mandatory field in 'check' node: 'name'");
		}
		else {
			if (! ("kind" in node)) {
				throw new Error("missing mandatory field in 'check' node: 'kind'");
			}
			else {
				if (! (node.kind in check_kind_implementations)) {
					throw new Error("invalid check kind: " + node.kind);
				}
				else {
					const node_ : type_check = Object.assign(
						lib_plankton.object.patched(
							defaults,
							{
								"title": node.name,
								"parameters": {},
								"custom": null,
							},
						),
						node
					) as type_check;
					let check : type_check = {
						"name": node_.name,
						"title": node_.title,
						"kind": node_.kind,
						"parameters": check_kind_implementations[node_.kind].normalize_order_node(node_.parameters),
						"custom": node_.custom,
					};
					if ("active" in node_) {
						check.active = node_["active"];
					}
					if ("threshold" in node_) {
						check.threshold = node_["threshold"];
					}
					if ("annoy" in node_) {
						check.annoy = node_["annoy"];
					}
					if ("schedule" in node_) {
						check.schedule = normalize_schedule(node_["schedule"]);
					}
					if ("notifications" in node_) {
						check.notifications = (
							node_["notifications"]
							.map(
								x => normalize_notification(x),
							)
						);
					}
					if ("custom" in node_) {
						check.custom = node_["custom"];
					}
					return check;
				}
			}
		}
	}
	
	
	/**
	 */
	function normalize_root(
		node : any,
		options : {
			use_implicit_default_values ?: boolean;
		} = {}
	) : type_order
	{
		options = Object.assign(
			{
				"use_implicit_default_values": true,
			},
			options
		);
		
		let counts : Record<string, int> = {};
		const checks_raw : Array<any> = (
			("checks" in node)
			? node["checks"]
			: []
		);
		checks_raw.forEach(
			node_ => {
				if (! (node_["name"] in counts)) {
					counts[node_["name"]] = 0;
				}
				counts[node_["name"]] += 1;
			}
		);
		const fails : Array<[string, int]> = (
			Object.entries(counts)
			.filter(
				([key, value]) => (value > 1)
			)
		);
		if (fails.length > 0) {
			throw new Error(
				lib_plankton.string.coin(
					"ambiguous check names: {{names}}",
					{
						"names": fails.map(([key, value]) => key).join(","),
					}
				)
			);
		}
		else {
			const defaults_raw : any = (
				("defaults" in node)
				? node["defaults"]
				: {}
			);
			const defaults : type_check_common = (
				options.use_implicit_default_values
				? normalize_defaults(
					defaults_raw
				)
				: defaults_raw
			);
			const includes : Array<string> = (
				("includes" in node)
				? node["includes"]
				: []
			);
			return {
				"defaults": defaults,
				"includes": includes,
				"checks": (
					checks_raw
					.map(
						node_ => normalize_check(
							defaults,
							node_
						)
					)
				)
			};
		}
	}
		
	
	/**
	 */
	export async function load(
		path : string,
		options : {
			root ?: boolean;
			already_included ?: Array<string>;
		} = {}
	) : Promise<type_order>
	{
		const nm_path = require("path");
		
		options = Object.assign(
			{
				"root": true,
				"already_included": [],
			},
			options
		);
		
		if (options.already_included.includes(path)) {
			throw new Error("circular dependency detected")
		}
		else {
			const order_raw : {
				includes ?: Array<string>;
				checks ?: Array<any>;
			} = lib_plankton.json.decode(await lib_plankton.file.read(path));
			let includes : Array<string> = (
				("includes" in order_raw)
				? order_raw.includes
				: []
			);
			for (let index = 0; index < includes.length; index += 1) {
				const path_ : string = includes[index];
				const sub_order : type_order = await load(
					(
						nm_path.isAbsolute(path_)
						? path_
						: nm_path.join(nm_path.dirname(path), path_)
					),
					{
						"root": false,
						"already_included": options.already_included.concat([path]),
					}
				)
				if (! ("checks" in order_raw)) {
					order_raw.checks = [];
				}
				order_raw.checks = order_raw.checks.concat(
					sub_order.checks
					.map(
						check => Object.assign(
							check,
							{
								"name": lib_plankton.string.coin(
									"{{prefix}}.{{original_name}}",
									{
										"prefix": nm_path.basename(path_).split(".")[0],
										"original_name": check["name"],
									}
								),
							}
						),
						sub_order.checks
					)
				);
				order_raw.includes = [];
			}
			return normalize_root(
				order_raw,
				{
					"use_implicit_default_values": options["root"],
				}
			);
		}
	}
	
}
