/*
	Strings.js - A library of useful string functions for JavaScript
	2021 - 2025 by Mercury Thirteen, Creative Commons Attribution 4.0 International License

	2025-07-26
	- Moved the strings used by StringRandomGenerate() to a set of constants

	2025-01-17
	- Added .splitList(), which is basically an adaptation of my old WordSplit() function.

	2025-01-16
	- Added .replaceAll()

	2024-11-15
	- Fixed a slight bug in the .insertEvery() function where the main string length was not
		being recalculated on every pass, leading to the entire string not being processed

	2024-11-13
	- Added .insertAt() and .insertEvery()

	2024-10-30
	- Certain characters were not getting escaped in the .trimLeft and .trimRight functions;
		they have been modified to not use regular expressions.

	2022-08-12
	- Added a proper description comment header to all functions
	- Added GetElementTextWidth()
	- Added StringRandomGenerate()
	- Capitalized StringLengthMakeStart() and StringLengthMakeEnd()
	- Improved change log formatting

	2021-12-19
	- Added stringLengthMakeStart() and stringLengthMakeEnd()

	2021-12-14
	- Initial release
*/





// define the needed konstants
kStringListCharsLowerCase		= "abcdefghijklmnopqrstuvwxyz";
kStringListCharsUpperCase		= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
kStringListNumbers				= "0123456789";
kStringListSymbols				= "!@#$%^&*()-_=+[]{}|\;:'<>,.?/~`";
kStringRandomGenerateLowercase	= 1;
kStringRandomGenerateUppercase	= 2;
kStringRandomGenerateNumbers	= 4;
kStringRandomGenerateSymbols	= 8;





function GetElementTextWidth(element)
{
	/*
		Returns the width in pixels of the innerText of the specified element.

		Input:
			element 	The element whose innerText will be measured.

		Output:
			The width in pixels of the element's innerText.
	*/


	// create a temporary element
	tempText = document.createElement("span");
	document.body.appendChild(tempText);

	// set up the temporary element we just created with key attributes of the specified element
	tempText.innerText			= element.innerText;
	tempText.style.font			= element.style.font;
	tempText.style.fontSize		= element.textSize;
	tempText.style.height		= 'auto';
	tempText.style.position		= 'absolute';
	tempText.style.whiteSpace	= 'no-wrap';
	tempText.style.width		= 'auto';

	// calculate the width of the text
	formattedWidth = Math.ceil(tempText.clientWidth);

	// remove the temporary element and return!
	document.body.removeChild(tempText);
	return formattedWidth;
}





function StringLengthMakeStart(inputString, targetLength)
{
	/*
		Returns inputString either truncated or padded (from the beginning) to the length specified.

		Input:
			inputString 	The string which will be truncated or padded. If truncated, "..." will be added to the end of the string.
			targetLength 	The length to which inputString will be truncated or padded.

		Output:
			String			The original string padded or truncated to targetLength.
	*/


	if (inputString.length > targetLength)
	{
		return inputString.slice(0, targetLength - 3) + '...';
	}else{
		return inputString.padStart(targetLength);
	}
}





function StringLengthMakeEnd(inputString, targetLength)
{
	/*
		Returns inputString either truncated or padded (from the end) to the length specified.

		Input:
			inputString 	The string which will be truncated or padded. If truncated, "..." will be added to the end of the string.
			targetLength 	The length to which inputString will be truncated or padded.

		Output:
			String			The original string padded or truncated to targetLength.
	*/


	if (inputString.length > targetLength)
	{
		return inputString.slice(0, targetLength - 3) + '...';
	}else{
		return inputString.padEnd(targetLength);
	}
}





function StringRandomGenerate(length, options)
{
	/*
		Returns a string of random characters according to the options specified.

		Input:
			length		The length to which the random string will be built
			options		Options for building the random string:
						kStringRandomGenerateLowercase		Include lower case letters in the output string
						kStringRandomGenerateUppercase		Include upper case letters in the output string
						kStringRandomGenerateNumbers		Include numbers in the output string
						kStringRandomGenerateSymbols		Include symbols in the output string

		Output:
			String		A string containing random characters as specified
	*/


	var sourceString = "", randomString = "";

	// build the string from which the random string will be built
	if (options & kStringRandomGenerateLowercase) sourceString = sourceString + kStringListCharsLowerCase;
	if (options & kStringRandomGenerateUppercase) sourceString = sourceString + kStringListCharsUpperCase;
	if (options & kStringRandomGenerateNumbers) sourceString = sourceString + kStringListNumbers;
	if (options & kStringRandomGenerateSymbols) sourceString = sourceString + kStringListSymbols;

	// loop through length iterations and built the string
	for (index = 0; index <length; index++)
	{
		var position = Math.floor((Math.random() * sourceString.length));
		randomString = randomString + sourceString.charAt(position);
	}

	// and return!
	return randomString;
}





String.prototype.insertAt = function(insertString, position)
{
	/*
		Inserts insertString into the main string at "position"

		Input:
			insertString	The string to be inserted
			position		The character position at which insertString will be inserted into the main string

		Output:
			String			The original string with insertString inserted at "position".

		Example:
			Input			"Hello world".insertAt("beautiful ", 6)
			Output			"Hello beautiful world"
	*/


	if (this			=== undefined) return;
	if (insertString	=== undefined) return;

	var outputString = this;

	return outputString.slice(0, position) + insertString + outputString.slice(position);
};





String.prototype.insertEvery = function(insertString, spacing)
{
	/*
		Inserts insertString into the main string at every "spacing" characters

		Input:
			insertString	The string to be inserted
			spacing			The number of characters apart by which insertString is inserted

		Output:
			String			The original string with insertString inserted every "spacing" characters apart.

		Example:
			Input			"0010011100011101".insertEvery(" ", 4)
			Output			"0010 0111 0001 1101"
	*/

	if (this			=== undefined) return;
	if (insertString	=== undefined) return;

	var outputString = this;
	var nextInsert = spacing;

	while (nextInsert <= outputString.length)
	{
		outputString = outputString.insertAt(insertString, nextInsert);
		nextInsert = nextInsert + spacing + insertString.length;
	}

	return outputString;
};





String.prototype.replaceAll = function(searchValue, newValue)
{
	/*
		Extends JavaScript's built-in .replace() function to replace all occurrences of the string instead of just the first.

		Input:
			searchValue		The value or regular expression for which to search.
			newValue		The new value with which to replace searchValue.

		Output:
			String			A new string where all occurrences of the specified value has been replaced.
	*/

	if (this			=== undefined) return;
	if (searchValue		=== undefined) return;
	if (newValue		=== undefined) return;

	var inputString;
	var outputString	= this.replace(searchValue, newValue);

	while (inputString != outputString)
	{
		inputString = outputString;
		outputString = inputString.replace(searchValue, newValue);
	}
	return outputString;
};





String.prototype.splitList = function(separatorList)
{
	// Splits the string given by the separators in the string separatorList and returns the result in wordArray
	var currentChar = "", wordReturned = "";
	var lastType = 0, mainStrLen = 0, position = 0;
	var wordArray = [];

	const nonSeparator = 1, isSeparator = 2;

	// exit if the string is null or undefined
	if (this == '' || typeof this == 'undefined') return;

	// zero the output array

	// get the length of the input string
	mainStrLen = this.length;

	// if the string is empty, we don't need to do a thing!
	if (mainStrLen);
	{
		do
		{
			currentChar = this.charAt(position++);

			if (separatorList.includes(currentChar))
			{
				// this character is a separator

				// if the word we have is not null, add it to the list and clear wordReturned
				if (wordReturned != '') wordArray[wordArray.length] = wordReturned;
				wordReturned = "";

				// note that this char was a separator
				lastType = isSeparator;
			} else {
				// this character is not a separator

				// note that this char was not a separator
				lastType = nonSeparator;

				// add this character to wordReturned
				wordReturned = wordReturned + currentChar;
			}
		}
		while (position <= mainStrLen)
	}
	return wordArray;
};





String.prototype.trimRight = function(charList)
{
	/*
		Overrides JavaScript's built-in TrimLeft() function to provide more robust PHP-style features. From code by Aurelio De Rosa found at:
		https://www.sitepoint.com/trimming-strings-in-javascript/

		Input:
			charList	A string representing a list of individual characters to be trimmed from the left side of the string, if encountered. Trimming will stop when the first character is encountered which is not in this list.

		Output:
			String		The original string with the specified characters trimmed.
	*/

	if (this		=== undefined) return;
	if (charList	=== undefined) return;

	var removedFlag = true;
	var outputString = this;

	while(removedFlag)
	{
		var firstChar = outputString.charAt(0);
		if (charList.includes(firstChar))
		{
			// Yes, there are other ways to trim a letter from a
			// string, but this way has the shortest execution time
			outputString = outputString.substring(1);
			removedFlag = true;
		} else {
			removedFlag = false;
		}
	}
	return outputString;
};





String.prototype.trimRight = function(charList)
{
	/*
		Overrides JavaScript's built-in TrimLeft() function to provide more robust PHP-style features. From code by Aurelio De Rosa found at:
		https://www.sitepoint.com/trimming-strings-in-javascript/

		Input:
			charList	A string representing a list of individual characters to be trimmed from the right side of the string, if encountered. Trimming will stop when the first character is encountered which is not in this list.

		Output:
			String		The original string with the specified characters trimmed.
	*/


	if (this		=== undefined) return;
	if (charList	=== undefined) return;

	var removedFlag = true;
	var outputString = this;

	while(removedFlag)
	{
		var lastChar = outputString.charAt(outputString.length - 1);
		if (charList.includes(lastChar))
		{
			// Yes, there are other ways to trim a letter from a
			// string, but this way has the shortest execution time
			outputString = outputString.substring(0, outputString.length - 1);
			removedFlag = true;
		} else {
			removedFlag = false;
		}
	}
	return outputString;
};
