/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß

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 3 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, see 
<https://www.gnu.org/licenses/>. 
 */


namespace _espe.helper.password
{
	
	/**
	 */
	type char = string;
	
	
	/**
	 */
	type type_settings = {
		minimum_length : (null | int);
		maximum_length : (null | int);
		must_contain_letter : boolean;
		must_contain_number : boolean;
		must_contain_special_character : boolean;
	};
	
	
	/**
	 */
	enum enum_character_class {
		number_ = "number",
		letter = "letter",
		special = "special",
	}
	
	
	/**
	 */
	const character_classification : Record<enum_character_class, Array<int>> = {
		[enum_character_class.number_]: [
			0x30,
			0x31,
			0x32,
			0x33,
			0x34,
			0x35,
			0x36,
			0x37,
			0x38,
			0x39,
		],
		[enum_character_class.letter]: [
			0x41,
			0x42,
			0x43,
			0x44,
			0x45,
			0x46,
			0x47,
			0x48,
			0x49,
			0x4A,
			0x4B,
			0x4C,
			0x4D,
			0x4E,
			0x4F,
			0x50,
			0x51,
			0x52,
			0x53,
			0x54,
			0x55,
			0x56,
			0x56,
			0x58,
			0x59,
			0x5A,
			0x61,
			0x62,
			0x63,
			0x64,
			0x65,
			0x66,
			0x67,
			0x68,
			0x69,
			0x6A,
			0x6B,
			0x6C,
			0x6D,
			0x6E,
			0x6F,
			0x70,
			0x71,
			0x72,
			0x73,
			0x74,
			0x75,
			0x76,
			0x77,
			0x78,
			0x79,
			0x7A,
		],
		[enum_character_class.special]: [
			0x20,
			0x21,
			0x22,
			0x23,
			0x24,
			0x25,
			0x26,
			0x27,
			0x28,
			0x29,
			0x2A,
			0x2B,
			0x2C,
			0x2D,
			0x2E,
			0x2F,
			0x3A,
			0x3B,
			0x3C,
			0x3D,
			0x3E,
			0x3F,
			0x40,
			0x5B,
			0x5C,
			0x5D,
			0x5E,
			0x5F,
			0x60,
			0x7B,
			0x7C,
			0x7D,
			0x7E,
		],
	};
	
	
	/**
	 */
	function character_is_number(
		character : char
	) : boolean
	{
		if (character.length !== 1) {
			throw (new Error("not a character"));
		}
		else {
			const code : int = character.charCodeAt(0);
			return character_classification[enum_character_class.number_].includes(code);
		}
	}
	
	
	/**
	 */
	function character_is_letter(
		character : char
	) : boolean
	{
		if (character.length !== 1) {
			throw (new Error("not a character"));
		}
		else {
			const code : int = character.charCodeAt(0);
			return character_classification[enum_character_class.letter].includes(code);
		}
	}
	
	
	/**
	 */
	function character_is_special(
		character : char
	) : boolean
	{
		if (character.length !== 1) {
			throw (new Error("not a character"));
		}
		else {
			const code : int = character.charCodeAt(0);
			return character_classification[enum_character_class.special].includes(code);
		}
	}

	
	/**
	 */
	export function validate(
		settings : type_settings,
		password : string
	) : Array<{incident : string; details : Record<string, any>}>
	{
		let flaws : Array<{incident : string; details : Record<string, any>}> = [];
		const characters : Array<char> = password.split("");
		if (
			(settings.minimum_length !== null)
			&&
			(password.length < settings.minimum_length)
		) {
			flaws.push(
				{
					"incident": "too_short",
					"details": {
						"minimum_length": settings.minimum_length,
						"actual_length": password.length,
					}
				}
			);
		}
		if (
			(settings.maximum_length !== null)
			&&
			(password.length > settings.maximum_length)
		) {
			flaws.push(
				{
					"incident": "too_long",
					"details": {
						"maximum_length": settings.maximum_length,
						"actual_length": password.length,
					}
				}
			);
		}
		if (
			settings.must_contain_letter
			&&
			(! characters.some(character => character_is_letter(character)))
		) {
			flaws.push(
				{
					"incident": "lacks_letter",
					"details": {
					}
				}
			);
		}
		if (
			settings.must_contain_number
			&&
			(! characters.some(character => character_is_number(character)))
		) {
			flaws.push(
				{
					"incident": "lacks_number",
					"details": {
					}
				}
			);
		}
		if (
			settings.must_contain_special_character
			&&
			(! characters.some(character => character_is_special(character)))
		) {
			flaws.push(
				{
					"incident": "lacks_special_character",
					"details": {
					}
				}
			);
		}
		return flaws;
	}
	
	
	/**
	 */
	export function generate(
		settings : type_settings
	) : string
	{
		const count_number : int = (settings.must_contain_number ? 1 : 0);
		const count_special : int = (settings.must_contain_special_character ? 1 : 0);
		const count_letter_raw : int = ((settings.minimum_length ?? 8) - count_number - count_special);
		const count_letter : int = ((settings.must_contain_letter && (count_letter_raw <= 0)) ? 1 : count_letter_raw);
		if (
			(settings.maximum_length !== null)
			&&
			((count_letter + count_number + count_special) > settings.maximum_length)
		) {
			throw (new Error("impossible"));
		}
		else {
			return lib_plankton.call.convey(
				(
					([] as Array<int>)
					.concat(
						lib_plankton.list.sequence(count_letter)
						.map(x => lib_plankton.random.choose_uniformly<int>(character_classification[enum_character_class.letter]))
					)
					.concat(
						lib_plankton.list.sequence(count_number)
						.map(x => lib_plankton.random.choose_uniformly<int>(character_classification[enum_character_class.number_]))
					)
					.concat(
						lib_plankton.list.sequence(count_special)
						.map(x => lib_plankton.random.choose_uniformly<int>(character_classification[enum_character_class.special]))
					)
				),
				[
					lib_plankton.random.shuffle<int>,
					(x : Array<int>) => x.map(y => String.fromCharCode(y)),
					(x : Array<string>) => x.join(""),
				]
			);
		}
	}
		
}
