
// helpers provides miscellaneous functions

package helpers

import "seekia/internal/encoding"
import "seekia/internal/identity"
import "seekia/internal/translation"

import "errors"
import "math"
import "strconv"
import "strings"
import "time"
import "slices"
import "maps"

import cryptoRand "crypto/rand"
import mathRand "math/rand/v2"

//Outputs:
//	-int: Feet
//	-float64: Inches
//	-error
func ConvertCentimetersToFeetInches(centimeters float64) (int, float64, error) {

	if (centimeters < 0){
		return 0, 0, errors.New("ConvertCentimetersToFeetInches called with negative centimeters.")
	}

	totalInches := centimeters / 2.54

	feetFloored := math.Floor(totalInches/12)
	inches := totalInches - (feetFloored * 12)

	feetInt := int(feetFloored)

	return feetInt, inches, nil
}


//Outputs:
//	-string: Example: 1 foot, 10 centimeters
//	-error
func ConvertCentimetersToFeetInchesTranslatedString(centimeters float64)(string, error){

	inputFeet, inputInches, err := ConvertCentimetersToFeetInches(centimeters)
	if (err != nil) {
		return "", errors.New("ConvertCentimetersToFeetInchesTranslatedString called with invalid centimeters.")		
	}

	getInchUnits := func()string{

		if (inputInches == 1){
			result := translation.TranslateTextFromEnglishToMyLanguage("inch")
			return result
		}

		inchesTranslated := translation.TranslateTextFromEnglishToMyLanguage("inches")

		return inchesTranslated
	}		

	inchUnits := getInchUnits()

	inputInchesString := ConvertFloat64ToStringRounded(inputInches, 1)

	if (inputFeet == 0){

		formattedResult := inputInchesString + " " + inchUnits

		return formattedResult, nil
	}

	getFeetUnits := func()string{

		if (inputFeet <= 1){
			result := translation.TranslateTextFromEnglishToMyLanguage("foot")

			return result
		}

		feetTranslated := translation.TranslateTextFromEnglishToMyLanguage("feet")
		
		return feetTranslated
	}

	feetUnits := getFeetUnits()

	inputFeetString := ConvertIntToString(inputFeet)

	formattedResult := inputFeetString + " " + feetUnits + ", " + inputInchesString + " " + inchUnits

	return formattedResult, nil
}

func ConvertFeetInchesToCentimeters(feet int, inches float64)(float64, error) {
	
	if (feet < 0 || inches < 0){
		return 0, errors.New("ConvertFeetInchesToCentimeters called with negative feet/inches.")
	}

	totalInches := float64(feet*12) + inches
	centimeters := totalInches * 2.54

	return centimeters, nil
}


func ConvertKilometersToMiles(kilometers float64)(float64, error){

	if (kilometers < 0){
		return 0, errors.New("ConvertKilometersToMiles called with negative kilometers.")
	}

	miles := kilometers * 0.621371

	return miles, nil
}

func ConvertMilesToKilometers(miles float64)(float64, error){

	if (miles < 0){
		return 0, errors.New("ConvertMilesToKilometers called with negative miles.")
	}

	kilometers := miles * 1.609344

	return kilometers, nil
}

func VerifyLatitude(latitude float64)bool{

	if (latitude >= -90 && latitude <= 90){
		return true
	}

	return false
}

func VerifyLongitude(longitude float64)bool{

	if (longitude >= -180 && longitude <= 180){
		return true
	}

	return false
}

func VerifyStringIsFloat(input string)bool{

	_, err := ConvertStringToFloat64(input)
	if (err != nil){
		return false
	}

	return true
}

func VerifyStringIsIntWithinRange(inputString string, targetMin int64, targetMax int64)(bool, error){

	if (targetMin >= targetMax){
		return false, errors.New("VerifyStringIsIntWithinRange called with min greater than or equal to max")
	}

	num, err := ConvertStringToInt64(inputString)
	if (err != nil) {
		return false, nil
	}

	if (num < targetMin || num > targetMax){
		return false, nil
	}

	return true, nil
}

func VerifyStringIsFloatWithinRange(inputString string, targetMin float64, targetMax float64)(bool, error){
	
	if (targetMin >= targetMax){
		return false, errors.New("VerifyStringIsFloatWithinRange called with min greater than or equal to max")
	}

	num, err := ConvertStringToFloat64(inputString)
	if (err != nil) {
		return false, nil
	}
	if (num < targetMin || num > targetMax){
		return false, nil
	}

	return true, nil
}

func CheckIfFloat64IsInteger(float float64)bool{

	floatRounded := float64(int64(float))
	if (float == floatRounded){
		return true
	}

	return false
}

func FloorFloat64ToInt(input float64)(int, error){

	flooredFloat := math.Floor(input)

	if (flooredFloat < -2147483648 || flooredFloat > 2147483647){
		return 0, errors.New("FloorFloat64ToInt called with float out of range.")
	}

	newInt := int(flooredFloat)

	return newInt, nil
}

func FloorFloat64ToInt64(input float64)(int64, error){

	flooredFloat := math.Floor(input)

	if (flooredFloat < -9223372036854775808 || flooredFloat > 9223372036854775807){
		return 0, errors.New("FloorFloat64ToInt64 called with float out of range.")
	}

	newInt := int64(flooredFloat)

	return newInt, nil
}

func CeilFloat64ToInt64(input float64)(int64, error){

	ceiledFloat := math.Ceil(input)

	if (ceiledFloat < -9223372036854775808 || ceiledFloat > 9223372036854775807){
		return 0, errors.New("CeilFloat64ToInt64 called with float out of range.")
	}

	ceiledInt := int64(ceiledFloat)

	return ceiledInt, nil
}

func CeilFloat64ToInt(input float64)(int, error){

	ceiledFloat := math.Ceil(input)

	if (ceiledFloat < -2147483648 || ceiledFloat > 2147483647){
		return 0, errors.New("CeilFloat64ToInt called with float out of range.")
	}

	ceiledInt := int(ceiledFloat)

	return ceiledInt, nil
}

func ConvertFloat32ToString(input float32) string{

	result := strconv.FormatFloat(float64(input), 'f', 5, 32)

	return result
}

func ConvertFloat64ToString(input float64) string{

	result := strconv.FormatFloat(input, 'f', 5, 64)

	return result
}

func ConvertFloat64ToStringRounded(input float64, outputPrecision int)string{

	if (outputPrecision < 0){
		outputPrecision = 0
	}

	converted := strconv.FormatFloat(input, 'f', outputPrecision, 64)

	if (outputPrecision == 0){
		return converted
	}

	// If all numbers after the decimal are zero, we will remove them
	// Example: "5.000" -> "5"

	beforeDecimal, afterDecimal, decimalFound := strings.Cut(converted, ".")
	if (decimalFound == false){
		return converted
	}

	for _, number := range afterDecimal{
		if (number != '0'){
			return converted
		}
	}

	return beforeDecimal
}

func ConvertStringToFloat64(input string)(float64, error){

	num, err := strconv.ParseFloat(input, 64)
	if (err != nil){
		return 0, errors.New("ConvertStringToFloat64 called with invalid input: " + input)
	}

	return num, nil
}

func ConvertInt64ToInt(input int64)(int, error){

	if (input < -2147483648 || input > 2147483647){
		return 0, errors.New("ConvertInt64ToInt called with input out of range.")
	}

	result := int(input)

	return result, nil
}


func ConvertByteToString(input byte) string{

	result := ConvertIntToString(int(input))

	return result
}

func ConvertStringToByte(input string)(byte, error){

	resultInt, err := strconv.Atoi(input)
	if (err != nil) {
		return 0, errors.New("ConvertStringToByte called with invalid input: " + input)
	}

	if (resultInt < 0 || resultInt > 255){
		return 0, errors.New("ConvertStringToByte called with invalid input: " + input)
	}

	result := byte(resultInt)

	return result, nil
}

func ConvertNetworkTypeStringToByte(input string)(byte, error){

	if (input == "1"){
		return 1, nil
	}
	if (input == "2"){
		return 2, nil
	}

	return 0, errors.New("ConvertNetworkTypeStringToByte called with invalid input: " + input)
}

func VerifyNetworkType(input byte)bool{

	if (input != 1 && input != 2){
		return false
	}

	return true
}

func ConvertIntToString(input int) string{

	str := strconv.Itoa(input)
	
	return str
}

func ConvertInt64ToString(input int64)string{

	str := strconv.FormatInt(input, 10)
	
	return str
}

func ConvertStringToInt(input string)(int, error){

	result, err := strconv.Atoi(input)
	if (err != nil) {
		return 0, errors.New("ConvertStringToInt called with invalid input: " + input)
	}

	return result, nil
}

func ConvertStringToInt64(input string) (int64, error) {

	result, err := strconv.ParseInt(input, 10, 64)
	if (err != nil){
		return 0, errors.New("ConvertStringToInt64 called with invalid input: " + input)
	}

	return result, nil
}

func ConvertCreationTimeStringToInt64(input string)(int64, error){

	inputInt64, err := ConvertStringToInt64(input)
	if (err != nil) {
		return 0, errors.New("ConvertCreationTimeStringToInt64 called with invalid input: " + input)
	}

	isValid := VerifyCreationTime(inputInt64)
	if (isValid == false){
		return 0, errors.New("ConvertCreationTimeStringToInt64 called with invalid input. Too early: " + input)
	}

	return inputInt64, nil
}

func VerifyCreationTime(input int64)bool{

	if (input < 1717000000){
		return false
	}

	//TODO: Add upper limit

	return true
}

func ConvertIntToStringWithCommas(num int)string{

	numAsString := ConvertIntToString(num)

	if (num <= 999){
		return numAsString
	}

	resultWithCommas := ""

	finalIndex := len(numAsString)-1

	counter := 0

	for i := finalIndex; i >= 0; i--{

		currentCharacter := string(numAsString[i])

		if (counter == 3){
			resultWithCommas = currentCharacter + "," + resultWithCommas
			counter = 1
			continue
		}

		resultWithCommas = currentCharacter + resultWithCommas		
		counter += 1
	}

	return resultWithCommas
}

//Outputs:
//	-string: Content Type ("Profile"/"Attribute"/"Message"/"Review"/"Report"/"Parameters")
//	-error
func GetContentTypeFromContentHash(contentHash []byte)(string, error){

	hashLength := len(contentHash)

	switch hashLength{

		case 26:{

			return "Message", nil
		}
		case 27:{

			metadataByte := contentHash[26]

			if (metadataByte >= 1 && metadataByte <= 6){

				return "Attribute", nil
			}
		}
		case 28:{

			metadataByte := contentHash[27]

			if (metadataByte >= 1 && metadataByte <= 6){

				return "Profile", nil
			}
		}
		case 29:{

			metadataByte := contentHash[28]

			if (metadataByte >= 1 && metadataByte <= 4){

				return "Review", nil	
			}
		}
		case 30:{

			metadataByte := contentHash[29]

			if (metadataByte >= 1 && metadataByte <= 4){

				return "Report", nil
			}
		}
		case 31:{

			return "Parameters", nil
		}
	}

	contentHashHex := encoding.EncodeBytesToHexString(contentHash)

	return "", errors.New("GetContentTypeFromContentHash called with invalid contentHash: " + contentHashHex)
}

//Outputs:
//	-[]byte: Reported hash bytes
//	-string: Reported hash type (Identity, Profile, Message, or Attribute)
//	-error
func ReadReportedHashString(reportedHash string)([]byte, string, error){

	reportedHashBytes, reportedHashType, err := ReadReviewedHashString(reportedHash)
	if (err != nil){
		return nil, "", errors.New("ReadReportedHashString called with invalid reportedHash: " + err.Error())
	}	

	return reportedHashBytes, reportedHashType, nil
}

//Outputs:
//	-[]byte: Reviewed hash bytes
//	-string: Reviewed hash type (Identity, Profile, Message, or Attribute)
//	-error
func ReadReviewedHashString(reviewedHash string)([]byte, string, error){

	if (len(reviewedHash) == 27){

		// Must be identity hash

		identityHashBytes, _, err := identity.ReadIdentityHashString(reviewedHash)
		if (err != nil) {
			return nil, "", errors.New("ReadReviewedHashString called with invalid reviewedHash: " + reviewedHash)
		}

		return identityHashBytes[:], "Identity", nil
	}

	// All other reviewedHash types are encoded in Hex

	reviewedHashBytes, err := encoding.DecodeHexStringToBytes(reviewedHash)
	if (err != nil) {
		return nil, "", errors.New("ReadReviewedHashString called with invalid reviewedHash: " + reviewedHash)
	}

	reviewedType, err := GetReviewedTypeFromReviewedHash(reviewedHashBytes)
	if (err != nil){
		return nil, "", errors.New("ReadReviewedHashString called with invalid reviewedHash: " + reviewedHash)
	}

	return reviewedHashBytes, reviewedType, nil
}

func EncodeReportedHashBytesToString(reportedHash []byte)(string, error){

	result, err := EncodeReviewedHashBytesToString(reportedHash)

	return result, err
}

func EncodeReviewedHashBytesToString(reviewedHash []byte)(string, error){

	reviewedType, err := GetReviewedTypeFromReviewedHash(reviewedHash)
	if (err != nil){
		return "", errors.New("EncodeReviewedHashToString called with invalid reviewedHashBytes: " + err.Error())
	}

	if (reviewedType == "Identity"){

		if (len(reviewedHash) != 16){
			return "", errors.New("GetReviewedTypeFromReviewedHash returning Identity reviewedType when bytes length is not 16.")
		}

		reviewedHashArray := [16]byte(reviewedHash)

		identityHash, _, err := identity.EncodeIdentityHashBytesToString(reviewedHashArray)
		if (err != nil) { return "", err }
		
		return identityHash, nil	
	}

	reviewedHashString := encoding.EncodeBytesToHexString(reviewedHash)

	return reviewedHashString, nil
}

func GetReportedTypeFromReportedHash(reportedHash []byte)(string, error){

	reportedType, err := GetReviewedTypeFromReviewedHash(reportedHash)
	if (err != nil) { return "", err }

	return reportedType, nil
}

func GetReviewedTypeFromReviewedHash(reviewedHash []byte)(string, error){

	hashLength := len(reviewedHash)

	if (hashLength == 16){

		metadataByte := reviewedHash[15]

		if (metadataByte >= 1 && metadataByte <= 3){

			return "Identity", nil
		}

	} else if (hashLength == 26){

		return "Message", nil

	} else if (hashLength == 27){

		metadataByte := reviewedHash[26]

		if (metadataByte >= 1 && metadataByte <= 6){
		
			return "Attribute", nil
		}

	} else if (hashLength == 28){

		metadataByte := reviewedHash[27]

		// We must make sure profile is not disabled
		// Disabled profiles cannot be reviewed

		if (metadataByte == 2 || metadataByte == 4 || metadataByte == 6){

			return "Profile", nil
		}
	}

	reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)

	return "", errors.New("GetReviewedTypeFromReviewedHashBytes called with invalid reviewedHash: " + reviewedHashHex)
}


func GetNewRandomProfileHash(profileTypeProvided bool, profileType string, isDisabledProvided bool, isDisabled bool)([28]byte, error){

	if (profileTypeProvided == true){
		if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){
			return [28]byte{}, errors.New("GetNewRandomProfileHash called with invalid profileType: " + profileType)
		}
	}

	getMetadataByte := func()(byte, error){

		if (profileTypeProvided == false && isDisabledProvided == false){

			randomByte, err := GetRandomByteWithinRange(1, 6)
			if (err != nil) { return 0, err }

			return randomByte, nil
		}

		if (profileTypeProvided == true && isDisabledProvided == false){

			randomByte, err := GetRandomByteWithinRange(0, 1)
			if (err != nil) { return 0, err }

			if (profileType == "Mate"){

				result := 1 + randomByte
				return result, nil
			}
			if (profileType == "Host"){

				result := 3 + randomByte
				return result, nil
			}
			if (profileType == "Moderator"){

				result := 5 + randomByte
				return result, nil
			}
		}
		if (profileTypeProvided == false && isDisabledProvided == true){

			randomInt := GetRandomIntWithinRange(1, 3)

			if (randomInt == 1){

				// ProfileType == "Mate"

				if (isDisabled == true){
					return 1, nil
				}

				return 2, nil
			}
			if (randomInt == 2){

				// ProfileType == "Host"

				if (isDisabled == true){
					return 3, nil
				}

				return 4, nil
			}

			// ProfileType == "Moderator"

			// randomInt == 3
			if (isDisabled == true){
				return 5, nil
			}

			return 6, nil
		}

		//profileTypeProvided == true && isDisabledProvided == true

		if (profileType == "Mate"){

			if (isDisabled == true){
				return 1, nil
			}

			return 2, nil
		}
		if (profileType == "Host"){

			if (isDisabled == true){
				return 3, nil
			}

			return 4, nil
		}
		// profileType == "Moderator"

		if (isDisabled == true){
			return 5, nil
		}

		return 6, nil
	}

	metadataByte, err := getMetadataByte()
	if (err != nil){ return [28]byte{}, err }

	var profileHash [28]byte
	_, err = cryptoRand.Read(profileHash[:])
	if (err != nil) { return [28]byte{}, err }

	profileHash[27] = metadataByte

	return profileHash, nil
}

func GetNewRandomAttributeHash(profileTypeProvided bool, profileType string, isCanonicalProvided bool, isCanonical bool)([27]byte, error){

	if (profileTypeProvided == true){

		if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){
			return [27]byte{}, errors.New("GetNewRandomAttributeHash called with invalid profileType: " + profileType)
		}
	}

	getMetadataByte := func()(byte, error){

		if (profileTypeProvided == false && isCanonicalProvided == false){

			randomByte, err := GetRandomByteWithinRange(1, 6)
			if (err != nil) { return 0, err }

			return randomByte, nil
		}

		if (profileTypeProvided == true && isCanonicalProvided == false){

			randomByte, err := GetRandomByteWithinRange(0, 1)
			if (err != nil) { return 0, err }

			if (profileType == "Mate"){

				result := 1 + randomByte
				return result, nil
			}
			if (profileType == "Host"){

				result := 3 + randomByte
				return result, nil
			}
			if (profileType == "Moderator"){

				result := 5 + randomByte
				return result, nil
			}
		}
		if (profileTypeProvided == false && isCanonicalProvided == true){

			randomInt := GetRandomIntWithinRange(1, 3)

			if (randomInt == 1){
	
				// ProfileType == "Mate"

				if (isCanonical == true){
					return 1, nil
				}

				return 2, nil
			}
			if (randomInt == 2){

				// ProfileType == "Host"
				
				if (isCanonical == true){
					return 3, nil
				}

				return 4, nil
			}

			// ProfileType == "Moderator"

			// randomInt == 3
			if (isCanonical == true){
				return 5, nil
			}

			return 6, nil
		}

		//profileTypeProvided == true && isCanonicalProvided == true

		if (profileType == "Mate"){

			if (isCanonical == true){
				return 1, nil
			}

			return 2, nil
		}
		if (profileType == "Host"){

			if (isCanonical == true){
				return 3, nil
			}

			return 4, nil
		}
		// profileType == "Moderator"

		if (isCanonical == true){
			return 5, nil
		}

		return 6, nil
	}

	metadataByte, err := getMetadataByte()
	if (err != nil) { return [27]byte{}, err }

	var attributeHash [27]byte
	_, err = cryptoRand.Read(attributeHash[:])
	if (err != nil) { return [27]byte{}, err }

	attributeHash[26] = metadataByte

	return attributeHash, nil
}

func GetNewRandomMessageHash()([26]byte, error){

	var messageHash [26]byte

	_, err := cryptoRand.Read(messageHash[:])
	if (err != nil) { return [26]byte{}, err }

	return messageHash, nil
}

func GetNewRandomReviewHash(reviewTypeProvided bool, reviewType string)([29]byte, error){

	getMetadataByte := func()(byte, error){

		if (reviewTypeProvided == false){

			randomByte, err := GetRandomByteWithinRange(1, 4)
			if (err != nil) { return 0, err }

			return randomByte, nil
		}
		if (reviewType == "Identity"){
			return 1, nil
		}
		if (reviewType == "Profile"){
			return 2, nil
		}
		if (reviewType == "Attribute"){
			return 3, nil
		}
		if (reviewType == "Message"){
			return 4, nil
		}

		return 0, errors.New("GetNewRandomReviewHash called with invalid reviewType: " + reviewType)
	}

	metadataByte, err := getMetadataByte()
	if (err != nil){ return [29]byte{}, err }

	var reviewHash [29]byte

	_, err = cryptoRand.Read(reviewHash[:])
	if (err != nil) { return [29]byte{}, err }

	reviewHash[28] = metadataByte
	
	return reviewHash, nil
}

func GetNewRandomReportHash(reportTypeProvided bool, reportType string)([30]byte, error){

	getMetadataByte := func()(byte, error){

		if (reportTypeProvided == false){

			randomByte, err := GetRandomByteWithinRange(1, 4)
			if (err != nil) { return 0, err }

			return randomByte, nil
		}
		if (reportType == "Identity"){
			return 1, nil
		}
		if (reportType == "Profile"){
			return 2, nil
		}
		if (reportType == "Attribute"){
			return 3, nil
		}
		if (reportType == "Message"){
			return 4, nil
		}

		return 0, errors.New("GetNewRandomReportHash called with invalid reportType: " + reportType)
	}

	metadataByte, err := getMetadataByte()
	if (err != nil){ return [30]byte{}, err }

	var reportHash [30]byte

	_, err = cryptoRand.Read(reportHash[:])
	if (err != nil) { return [30]byte{}, err }

	reportHash[29] = metadataByte

	return reportHash, nil
}

func GetNewRandomParametersHash()([31]byte, error){

	var parametersHash [31]byte

	_, err := cryptoRand.Read(parametersHash[:])
	if (err != nil) { return [31]byte{}, err }

	return parametersHash, nil
}


func GetNewRandomBytes(lengthInBytes int)([]byte, error){

	if (lengthInBytes == 0){
		return nil, errors.New("GetNewRandomBytes called with 0 lengthInBytes.")
	}

	randomBytes := make([]byte, lengthInBytes)

	_, err := cryptoRand.Read(randomBytes[:])
	if (err != nil) { return nil, err }

	return randomBytes, nil
}

func GetNewRandomDeviceIdentifier()([11]byte, error){

	var newArray [11]byte

	_, err := cryptoRand.Read(newArray[:])
	if (err != nil) { return [11]byte{}, err }

	return newArray, nil
}

func GetNewRandom16ByteArray()([16]byte, error){

	var newArray [16]byte

	_, err := cryptoRand.Read(newArray[:])
	if (err != nil) { return [16]byte{}, err }

	return newArray, nil
}

func GetNewRandom32ByteArray()([32]byte, error){

	var newArray [32]byte

	_, err := cryptoRand.Read(newArray[:])
	if (err != nil) { return [32]byte{}, err }

	return newArray, nil
}

func GetNewRandom24ByteArray()([24]byte, error){

	var newArray [24]byte

	_, err := cryptoRand.Read(newArray[:])
	if (err != nil) { return [24]byte{}, err }

	return newArray, nil
}

func GetNewRandomHexString(lengthInBytes int)(string, error){

	if (lengthInBytes == 0){
		return "", errors.New("GetNewRandomHexString called with 0 lengthInBytes.")
	}

	randomBytes := make([]byte, lengthInBytes)
	_, err := cryptoRand.Read(randomBytes[:])
	if (err != nil) { return "", err }

	randomString := encoding.EncodeBytesToHexString(randomBytes)

	return randomString, nil
}

func VerifyHexString(expectedLengthInBytes uint32, stringToVerify string)bool{

	inputStringBytes, err := encoding.DecodeHexStringToBytes(stringToVerify)
	if (err != nil) {
		return false
	}

	if (len(inputStringBytes) != int(expectedLengthInBytes)){
		return false
	}

	return true
}


func ConvertBoolToYesOrNoString(input bool)string{

	if (input == true){
		return "Yes"
	}

	return "No"
}

func ConvertYesOrNoStringToBool(input string)(bool, error){

	if (input == "Yes"){
		return true, nil
	}
	if (input == "No"){
		return false, nil
	}

	return false, errors.New("ConvertYesOrNoStringToBool called with invalid yes/no: " + input)
}


func DeleteIndexFromStringList(inputList []string, indexToDelete int)([]string, error){

	if (len(inputList) == 0){
		return nil, errors.New("DeleteIndexFromStringList called with empty list.")
	}

	if (indexToDelete < 0){
		return nil, errors.New("DeleteIndexFromStringList called with negative indexToDelete.")
	}

	finalIndex := len(inputList) - 1

	if (indexToDelete > finalIndex){
		return nil, errors.New("DeleteIndexFromStringList called with indexToDelete which is too large.")
	}

	newList := slices.Delete(inputList, indexToDelete, indexToDelete+1)

	return newList, nil
}


//Outputs:
//	-[]string: New list
//	-bool: Deleted any items
func DeleteAllMatchingItemsFromList[E comparable](inputList []E, itemToDelete E)([]E, bool){

	listCopy := slices.Clone(inputList)

	deletionFunction := func(input E)bool{
		if (input == itemToDelete){
			return true
		}
		return false
	}

	newList := slices.DeleteFunc(listCopy, deletionFunction)

	if (len(newList) == len(inputList)){
		return newList, false
	}

	return newList, true
}

func GetSharedItemsOfTwoStringLists(listA []string, listB []string)[]string{

	sharedItemsList := make([]string, 0)

	for _, element := range listA{

		containsItem := slices.Contains(listB, element)
		if (containsItem == true){
			sharedItemsList = append(sharedItemsList, element)
		}
	}

	return sharedItemsList
}

// This function will compare two lists. It does not care about item order.
func CheckIfTwoListsContainIdenticalItems[E comparable](listA []E, listB []E)bool{

	if (len(listA) != len(listB)){
		return false
	}

	itemCountMapA := make(map[E]int)

	for _, item := range listA{
		itemCountMapA[item] += 1
	}

	itemCountMapB := make(map[E]int)

	for _, item := range listB{
		itemCountMapB[item] += 1
	}

	areEqual := maps.Equal(itemCountMapA, itemCountMapB)
	if (areEqual == false){
		return false
	}

	return true
}

// This function will split a list into sublists
func SplitListIntoSublists[E any](inputList []E, maximumItemsPerSublist int)([][]E, error){

	if (maximumItemsPerSublist <= 0){
		return nil, errors.New("maximumItemsPerSublist is <= 0")
	}

	lengthOfInputList := len(inputList)

	if (lengthOfInputList <= maximumItemsPerSublist){

		result := [][]E{inputList}

		return result, nil
	}

	numberOfSublists := float64(lengthOfInputList)/float64(maximumItemsPerSublist)

	numberOfSublistsCeil := math.Ceil(numberOfSublists)

	itemsPerSublist := float64(lengthOfInputList)/numberOfSublistsCeil

	itemsPerSublistCeiled := math.Ceil(itemsPerSublist)

	if (itemsPerSublistCeiled > 2147483647){
		return nil, errors.New("Items per sublist is out of range.")
	}

	sizeOfEachSublist := int(itemsPerSublistCeiled)

	maximumIndex := lengthOfInputList-1

	listOfSublists := make([][]E, 0)

	currentSublist := make([]E, 0)

	for index, element := range inputList{

		currentSublist = append(currentSublist, element)

		currentSublistLength := len(currentSublist)

		if (currentSublistLength == sizeOfEachSublist || index == maximumIndex){

			listOfSublists = append(listOfSublists, currentSublist)
			currentSublist = make([]E, 0)
		}
	}

	return listOfSublists, nil
}


func AddItemToStringListAndAvoidDuplicate(inputList []string, newItem string)[]string{

	for _, element := range inputList{

		if (element == newItem){
			return inputList
		}
	}

	inputList = append(inputList, newItem)

	return inputList
}


// Will combine two lists, and avoid any duplicate entries
func CombineTwoListsAndAvoidDuplicates[E comparable](listA []E, listB []E)[]E{

	combinedListsMap := make(map[E]struct{})

	for _, element := range listA{
		combinedListsMap[element] = struct{}{}
	}
	for _, element := range listB{
		combinedListsMap[element] = struct{}{}
	}

	combinedList := GetListOfMapKeys(combinedListsMap)

	return combinedList
}


func RemoveDuplicatesFromStringList(inputList []string)[]string{

	if (len(inputList) <= 1){

		listCopy := slices.Clone(inputList)

		return listCopy
	}

	listAsMap := make(map[string]struct{})

	for _, element := range inputList{
		listAsMap[element] = struct{}{}
	}

	newList := GetListOfMapKeys(listAsMap)

	return newList
}

// Outputs:
//	-bool: Duplicate exists
//	-E: The first duplicate we found
func CheckIfListContainsDuplicates[E comparable](inputList []E)(bool, E){

	if (len(inputList) <= 1){
		var emptyItem E
		return false, emptyItem
	}

	listMap := make(map[E]struct{})

	for index, element := range inputList{

		if (index != 0){
			_, exists := listMap[element]
			if (exists == true){
				return true, element
			}
		}

		listMap[element] = struct{}{}
	}

	var emptyItem E
	return false, emptyItem
}


// Uses weak randomness
func GetRandomItemFromList[E any](inputList []E)(E, error){

	lengthOfList := len(inputList)
	if (lengthOfList == 0){
		var emptyItem E
		return emptyItem, errors.New("GetRandomItemFromList called with empty list.")
	}
	if (lengthOfList == 1){
		result := inputList[0]
		return result, nil
	}

	randomIndex := mathRand.IntN(lengthOfList)

	result := inputList[randomIndex]

	return result, nil
}

// This function will return a list containing a map's keys
func GetListOfMapKeys[M map[K]V, K comparable, V any](inputMap M)[]K{

	newList := make([]K, 0, len(inputMap))

	for key, _ := range inputMap{

		newList = append(newList, key)
	}

	return newList
}

// This function will return a list containing a map's values
func GetListOfMapValues[M map[K]V, K comparable, V any](inputMap M)[]V{

	newList := make([]V, 0, len(inputMap))

	for _, value := range inputMap{

		newList = append(newList, value)
	}

	return newList
}

// This function will return a deep copy of a list of string->string maps
func DeepCopyStringToStringMapList(inputMapList []map[string]string)[]map[string]string{

	mapListCopy := make([]map[string]string, 0, len(inputMapList))

	for _, element := range inputMapList{

		elementCopy := maps.Clone(element)

		mapListCopy = append(mapListCopy, elementCopy)
	}

	return mapListCopy
}

//Uses weak randomness
func RandomizeListOrder[E any](inputList []E){

	mathRand.Shuffle(len(inputList), func(i int, j int){
		inputList[i], inputList[j] = inputList[j], inputList[i]
	})
}


func SortIdentityHashListToUnicodeOrder(inputList [][16]byte)error{

	// Map Structure: Identity hash -> Identity hash string
	identityHashStringsMap := make(map[[16]byte]string)

	for _, identityHash := range inputList{

		identityHashString, _, err := identity.EncodeIdentityHashBytesToString(identityHash)
		if (err != nil){
			identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
			return errors.New("SortIdentityHashListToUnicodeOrder called with invalid identityHash: " + identityHashHex)
		}

		identityHashStringsMap[identityHash] = identityHashString
	}

	compareFunction := func(identityHash1 [16]byte, identityHash2 [16]byte)int{

		if (identityHash1 == identityHash2){
			return 0
		}

		identityHash1String, exists := identityHashStringsMap[identityHash1]
		if (exists == false){
			panic("identityHash1 is missing from identityHashStringsMap.")
		}

		identityHash2String, exists := identityHashStringsMap[identityHash2]
		if (exists == false){
			panic("identityHash2 is missing from identityHashStringsMap.")
		}

		if (identityHash1String < identityHash2String){
			return -1
		}

		return 1
	}

	slices.SortFunc(inputList, compareFunction)

	return nil
}


func CopyAndSortStringListToUnicodeOrder(inputList []string)[]string{

	listCopy := slices.Clone(inputList)

	slices.Sort(listCopy)

	return listCopy
}

func SortStringListToUnicodeOrder(inputList []string){

	slices.Sort(inputList)
}


func GetRandomByteWithinRange(minimum byte, maximum byte)(byte, error){

	if (maximum < minimum){
		return 0, errors.New("GetRandomByteWithinRange called with maximum < minimum")
	}

	randomInt := GetRandomIntWithinRange(int(minimum), int(maximum))

	if (randomInt < 0 || randomInt > 255){
		return 0, errors.New("GetRandomIntWithinRange returning out of bounds result.")
	}

	randomByte := byte(randomInt)

	return randomByte, nil
}

// Will return random int between two input numbers
// Uses weak randomness
func GetRandomIntWithinRange(numA int, numB int)int{

	if (numA == numB){
		return numA
	}

	lesserValue := min(numA, numB)
	greaterValue := max(numA, numB)

	differenceBetweenValues := greaterValue - lesserValue

	randomValue := mathRand.IntN(differenceBetweenValues + 1)

	resultValue := lesserValue + randomValue

	return resultValue
}

// This function will return a bool with the provided probabilityOfTrue
// For example, if the probabilityOfTrue is 0.6, then 60% of the time, this function will return true
func GetRandomBoolWithProbability(probabilityOfTrue float64)(bool, error){

	if (probabilityOfTrue < 0 || probabilityOfTrue > 1){

		probabilityOfTrueString := ConvertFloat64ToString(probabilityOfTrue)
		return false, errors.New("GetRandomBoolWithProbability called with invalid probabilityOfTrue: " + probabilityOfTrueString)
	}

	if (probabilityOfTrue == 0){
		return false, nil
	}
	if (probabilityOfTrue == 1){
		return true, nil
	}

	// This function returns a random float between 0-1
	randomFloat := mathRand.Float64()

	if (randomFloat <= probabilityOfTrue){
		return true, nil
	}

	return false, nil
}

// Will return random int64 between two input numbers
// Uses weak randomness
func GetRandomInt64WithinRange(numA int64, numB int64)int64{

	if (numA == numB){
		return numA
	}

	lesserValue := min(numA, numB)
	greaterValue := max(numA, numB)

	differenceBetweenValues := greaterValue - lesserValue

	randomValue := mathRand.Int64N(differenceBetweenValues + 1)

	resultValue := lesserValue + randomValue

	return resultValue
}

func GetRandomBool()bool{

	randomNumber := mathRand.IntN(2)

	if (randomNumber == 1){
		return true
	}

	return false
}

// This will replace newline characters with the "⁋" character
// It also counts tabs as being 5 characters long
//Outputs:
//	-string: Trimmed/flattened string (or original string if no changes were made)
//	-bool: Any trimming/flattening occurred
//	-error
func TrimAndFlattenString(inputString string, maximumCharacters int)(string, bool, error){

	if (maximumCharacters < 1) {
		return "", false, errors.New("TrimAndFlattenString called with maximumCharacters which is less than 1.")
	}

	// Note that some characters are multiple runes in length.
	// This is not a perfect estimate, but it will only overestimate the length
	// All english characters are 1 byte long, so they are also 1 rune long

	// We treat tabs as being 5 characters long
	tabsReplacedString := strings.ReplaceAll(inputString, "\t", "     ")

	getFlattenedString := func()string{

		containsNewlines := strings.Contains(tabsReplacedString, "\n")
		if (containsNewlines == true){

			// String must be flattened

			newlinesReplacedString := strings.ReplaceAll(tabsReplacedString, "\n", "⁋")

			return newlinesReplacedString
		}

		return tabsReplacedString
	}

	flattenedString := getFlattenedString()

	numberOfCharacters := len([]rune(flattenedString))

	if (numberOfCharacters <= maximumCharacters){
		// No trimming is needed.
		return flattenedString, false, nil
	}

	stringCharactersList := []rune(flattenedString)

	outputString := string(stringCharactersList[:maximumCharacters])

	outputStringWithEllipsis := outputString + "..."

	return outputStringWithEllipsis, true, nil
}


func TranslateAndJoinStringListItems(inputList []string, delimiter string)string{

	if (len(inputList) == 0){
		return ""
	}

	// We use this to build the output
	var outputBuilder strings.Builder

	finalIndex := len(inputList) - 1

	for index, element := range inputList{

		elementTranslated := translation.TranslateTextFromEnglishToMyLanguage(element)

		outputBuilder.WriteString(elementTranslated)

		if (index != finalIndex){
			outputBuilder.WriteString(delimiter)
		}
	}

	result := outputBuilder.String()

	return result
}

// Show remainder will include two units, such as "5 days 10 hours" or "3 months 2 weeks"
func ConvertUnixTimeDurationToUnitsTimeTranslated(unixDuration int64, showRemainder bool)(string, error){

	// TODO: Add translation

	getDurationWithRemainder := func()(string, int64){

		if (unixDuration <= 0) {
			return "0 seconds", 0
		}

		minuteUnix := int64(60)

		if (unixDuration < minuteUnix){
			numSeconds := unixDuration

			durationSecondsString := ConvertInt64ToString(numSeconds)
			if (durationSecondsString == "1"){
				return durationSecondsString + " Second", 0
			}
			return durationSecondsString + " Seconds", 0
		}

		hourUnix := int64(3600)

		if (unixDuration < hourUnix){
			numMinutes := unixDuration/minuteUnix
			remainderTime := unixDuration - (numMinutes * minuteUnix)

			durationMinutesString := ConvertInt64ToString(numMinutes)
			if (durationMinutesString == "1"){
				return durationMinutesString + " Minute", remainderTime
			}
			return durationMinutesString + " Minutes", remainderTime
		}

		dayUnix := int64(86400)

		if (unixDuration < dayUnix){
			numHours := unixDuration/hourUnix
			remainderTime := unixDuration - (numHours * hourUnix)

			if (remainderTime < minuteUnix){
				remainderTime = 0
			}

			durationHoursString := ConvertInt64ToString(numHours)
			if (durationHoursString == "1"){
				return durationHoursString + " Hour", remainderTime
			}
			return durationHoursString + " Hours", remainderTime
		}

		weekUnix := int64(604800)

		if (unixDuration < weekUnix){
			numDays := unixDuration/dayUnix
			remainderTime := unixDuration - (numDays * dayUnix)

			if (remainderTime < hourUnix){
				remainderTime = 0
			}

			durationDaysString := ConvertInt64ToString(numDays)
			if (durationDaysString == "1"){
				return durationDaysString + " Day", remainderTime
			}
			return durationDaysString + " Days", remainderTime
		}

		monthUnix := int64(2629743)

		if (unixDuration < monthUnix){
			numWeeks := unixDuration/weekUnix
			remainderTime := unixDuration - (numWeeks * weekUnix)

			if (remainderTime < dayUnix){
				remainderTime = 0
			}
		
			durationWeeksString := ConvertInt64ToString(numWeeks)
			if (durationWeeksString == "1"){
				return durationWeeksString + " Week", remainderTime
			}
			return durationWeeksString + " Weeks", remainderTime
		}

		yearUnix := int64(31556926)

		if (unixDuration < yearUnix) {
			numMonths := unixDuration/monthUnix
			remainderTime := unixDuration - (numMonths * monthUnix)

			if (remainderTime < dayUnix){
				remainderTime = 0
			}

			durationMonthsString := ConvertInt64ToString(numMonths)
			if (durationMonthsString == "1"){
				return durationMonthsString + " Month", remainderTime
			}
			return durationMonthsString + " Months", remainderTime
		}

		numYears := unixDuration/yearUnix
		remainderTime := unixDuration - (numYears * yearUnix)

		if (remainderTime < dayUnix || numYears > 1){
			remainderTime = 0
		}

		durationYearsString := ConvertInt64ToString(numYears)
		if (durationYearsString == "1"){
			return durationYearsString + " Year", remainderTime
		}

		return durationYearsString + " Years", remainderTime
	}

	timeTranslated, remainder := getDurationWithRemainder()
	if (showRemainder == false){
		return timeTranslated, nil
	}
	if (remainder == 0){
		return timeTranslated, nil
	}

	remainderString, err := ConvertUnixTimeDurationToUnitsTimeTranslated(remainder, false)
	if (err != nil) { return "", err } 	

	andTranslated := translation.TranslateTextFromEnglishToMyLanguage("and")

	// Example of resultWithRemainder: "1 week and 5 days"

	resultWithRemainder := timeTranslated + " " + andTranslated + " " + remainderString

	return resultWithRemainder, nil
}


func ConvertUnixTimeToTimeAgoTranslated(inputUnixTime int64, showRemainderTime bool)(string, error){

	currentTime := time.Now().Unix()

	if (inputUnixTime > currentTime){
		return "", errors.New("ConvertUnixTimeToTimeAgoTranslated called with invalid date: Time is not in the past.")
	}

	durationUnix := currentTime - inputUnixTime

	durationString, err := ConvertUnixTimeDurationToUnitsTimeTranslated(durationUnix, showRemainderTime)
	if (err != nil) { return "", err }

	agoTranslated := translation.TranslateTextFromEnglishToMyLanguage("Ago")

	durationStringAgo := durationString + " " + agoTranslated

	return durationStringAgo, nil
}

func ConvertUnixTimeToTimeFromNowTranslated(inputUnixTime int64, showRemainderTime bool)(string, error){

	currentTime := time.Now().Unix()

	getDurationUnixAndSuffix := func()(int64, string){

		if (currentTime >= inputUnixTime){

			// inputUnixTime is in the past

			durationUnix := currentTime - inputUnixTime

			return durationUnix, "Ago"
		}

		// inputUnixTime is in the future

		durationUnix := inputUnixTime - currentTime

		return durationUnix, "In The Future"
	}

	durationUnix, suffix := getDurationUnixAndSuffix()

	durationString, err := ConvertUnixTimeDurationToUnitsTimeTranslated(durationUnix, showRemainderTime)
	if (err != nil) { return "", err }

	suffixTranslated := translation.TranslateTextFromEnglishToMyLanguage(suffix)

	timeFromNowTranslated := durationString + " " + suffixTranslated

	return timeFromNowTranslated, nil	
}

// This will return time in the the following format: February 14, 2023 at 2:14.
// It returns 24 hour time
func ConvertUnixTimeToTranslatedTime(inputTime int64)string{

	timeObject := time.Unix(inputTime, 0)

	timeYear, timeMonth, timeDay := timeObject.Date()

	timeYearString := ConvertIntToString(timeYear)

	timeMonthString := translation.TranslateTextFromEnglishToMyLanguage(timeMonth.String())

	timeDayString := ConvertIntToString(timeDay)

	atTranslated := translation.TranslateTextFromEnglishToMyLanguage("at")

	timeHour, timeMinute, _ := timeObject.Clock()

	timeHourString := ConvertIntToString(timeHour)

	getTimeMinuteStringFormatted := func()string{

		timeMinuteString := ConvertIntToString(timeMinute)

		// We have to convert times such as 11:0 -> 11:00

		if (timeMinute < 10){

			result := "0" + timeMinuteString

			return result
		}

		return timeMinuteString
	}

	timeMinuteStringFormatted := getTimeMinuteStringFormatted()

	timeString := timeMonthString + " " + timeDayString + ", " + timeYearString + " " + atTranslated + " " + timeHourString + ":" + timeMinuteStringFormatted

	return timeString
}


func ConvertFloat64ToRoundedStringWithTranslatedUnits(inputFloat float64)(string, error){

	if (inputFloat < 0){
		return "", errors.New("ConvertFloat64ToRoundedStringWithTranslatedUnits called with negative input.")
	}

	if (inputFloat < 1000){
		result := ConvertInt64ToString(int64(inputFloat))
		return result, nil
	}

	getUnits := func()(float64, string){

		millionFloat := float64(1000000)

		if (inputFloat < millionFloat){
			divided := inputFloat / 1000
			return divided, "thousand"
		}

		billionFloat := float64(1000000000)

		if (inputFloat < billionFloat){
			divided := inputFloat / millionFloat
			return divided, "million"
		}

		trillionFloat := float64(1000000000000)

		if (inputFloat < trillionFloat){
			divided := inputFloat / billionFloat
			return divided, "billion"
		}

		quadrillionFloat := float64(1000000000000000)

		if (inputFloat < quadrillionFloat){
			divided := inputFloat / trillionFloat
			return divided, "trillion"
		}

		quintillionFloat := float64(1000000000000000000)

		if (inputFloat < quintillionFloat){
			divided := inputFloat / quadrillionFloat
			return divided, "quadrillion"
		}

		sextillionFloat := float64(1000000000000000000000)

		if (inputFloat < sextillionFloat){
			divided := inputFloat / quintillionFloat
			return divided, "quintillion"
		}

		septillionFloat := float64(1000000000000000000000000)

		if (inputFloat < septillionFloat){
			divided := inputFloat / sextillionFloat
			return divided, "sextillion"
		}
		divided := inputFloat / septillionFloat
		return divided, "septillion"
	}

	dividedResult, unitsString := getUnits()

	unitsTranslated := translation.TranslateTextFromEnglishToMyLanguage(unitsString)

	resultString := ConvertFloat64ToStringRounded(dividedResult, 1)

	hasSuffix := strings.HasSuffix(resultString, ".0")
	if (hasSuffix == true){

		resultTrimmed := strings.TrimSuffix(resultString, ".0")

		resultWithUnits := resultTrimmed + " " + unitsTranslated

		return resultWithUnits, nil
	}

	resultWithUnits := resultString + " " + unitsTranslated

	return resultWithUnits, nil
}


// This function takes an int and the min and max range of that int
// It returns an int scaled between a new min and max
func ScaleIntProportionally(ascending bool, input int, inputMin int, inputMax int, newMin int, newMax int)(int, error){

	if (inputMin == inputMax) {
		return inputMin, nil
	}
	if (inputMin > inputMax) {
		return 0, errors.New("ScaleIntProportionally error: InputMin is greater than inputMax")
	}

	if (input < inputMin) {
		return 0, errors.New("ScaleIntProportionally error: Input is less than inputMin")
	}
	if (input > inputMax) {
		return 0, errors.New("ScaleIntProportionally error: Input is greater than inputMax")
	}

	if (newMin == newMax) {
		return newMin, nil
	}
	if (newMin > newMin){
		return 0, errors.New("ScaleIntProportionally error: newMin is greater than newMin.")
	}

	inputRangePortionLength := input - inputMin

	inputRangeDistance := inputMax - inputMin

	inputRangePortion := float64(inputRangePortionLength)/float64(inputRangeDistance)

	// This represents the portion of our output range that we want to travel across
	getOutputRangePortion := func()float64{
		if (ascending == true){
			return inputRangePortion
		}

		outputRangePortion := 1 - inputRangePortion
		return outputRangePortion
	}

	outputRangePortion := getOutputRangePortion()

	outputRangeDistance := newMax - newMin

	outputRangePortionLength := float64(outputRangeDistance) * outputRangePortion

	resultFloat64 := float64(newMin) + outputRangePortionLength

	result := int(math.Floor(resultFloat64))

	return result, nil
}

// This function takes an float64 and the min and max range of that float64
// It returns a float64 scaled between a new min and max
func ScaleFloat64Proportionally(ascending bool, input float64, inputMin float64, inputMax float64, newMin float64, newMax float64)(float64, error){

	if (inputMin == inputMax) {
		return inputMin, nil
	}
	if (inputMin > inputMax) {
		return 0, errors.New("ScaleFloat64Proportionally error: InputMin is greater than inputMax")
	}

	if (input < inputMin) {
		return 0, errors.New("ScaleFloat64Proportionally error: Input is less than inputMin")
	}
	if (input > inputMax) {
		return 0, errors.New("ScaleFloat64Proportionally error: Input is greater than inputMax")
	}

	if (newMin == newMax) {
		return newMin, nil
	}
	if (newMin > newMin){
		return 0, errors.New("ScaleFloat64Proportionally error: newMin is greater than newMin.")
	}

	inputRangePortionLength := input - inputMin

	inputRangeDistance := inputMax - inputMin

	inputRangePortion := inputRangePortionLength/inputRangeDistance

	// This represents the portion of our output range that we want to travel across
	getOutputRangePortion := func()float64{
		if (ascending == true){
			return inputRangePortion
		}

		outputRangePortion := 1 - inputRangePortion
		return outputRangePortion
	}

	outputRangePortion := getOutputRangePortion()

	outputRangeDistance := newMax - newMin

	outputRangePortionLength := outputRangeDistance * outputRangePortion

	result := newMin + outputRangePortionLength

	return result, nil
}

func CheckIfStringContainsTabsOrNewlines(inputString string)bool{

	for _, element := range inputString{
		if (element == '\r' || element == '\n' || element == '\t') {
			return true
		}
	}
	return false
}

func JoinTwo16ByteArrays(inputArray1 [16]byte, inputArray2 [16]byte)[32]byte{

	arraysJoinedSlice := slices.Concat(inputArray1[:], inputArray2[:])

	arraysJoined := [32]byte(arraysJoinedSlice)

	return arraysJoined
}

func Split32ByteArrayInHalf(inputArray [32]byte)([16]byte, [16]byte){

	piece1 := [16]byte(inputArray[:16])
	piece2 := [16]byte(inputArray[16:])

	return piece1, piece2
}


// This function takes a list of ints and a target value, and returns the int in the list that is the closest to that value
// If there is a tie, the function returns the earliest item in the list of the tied elements
func GetClosestIntInList(inputList []int, targetValue int)(int, error){

	if (len(inputList) == 0){
		return 0, errors.New("GetClosestIntInList called with empty inputList.")
	}

	closestValue := 0
	closestValueDistance := float64(0)

	for index, element := range inputList{

		if (element == targetValue){
			return element, nil
		}

		distance := math.Abs(float64(element - targetValue))

		if (index == 0 || distance < closestValueDistance){
			closestValue = element
			closestValueDistance = distance
		}
	}

	return closestValue, nil
}


func CountMatchingElementsInSlice[E comparable](inputSlice []E, inputElement E)int{

	counter := 0

	for _, element := range inputSlice{

		if (element == inputElement){
			counter += 1
		}
	}

	return counter
}


func CheckIfAllItemsInSliceAreIdentical[E comparable](inputSlice []E)bool{

	if (len(inputSlice) <= 1){
		return true
	}

	initialElement := inputSlice[0]

	for index, element := range inputSlice{

		if (index == 0){
			continue
		}
		if (element != initialElement){
			return false
		}
	}

	return true
}


func GetAverageOfValuesInSliceOfFloat64s(inputSlice []float64)(float64, error){

	if (inputSlice == nil){
		return 0, errors.New("GetAverageOfValuesInSliceOfFloat64s called with nil inputSlice.")
	}
	if (len(inputSlice) == 0){
		return 0, errors.New("GetAverageOfValuesInSliceOfFloat64s called with empty inputSlice.")
	}

	quantityOfValues := float64(len(inputSlice))
	sum := float64(0)

	for _, element := range inputSlice{

		sum += element
	}

	average := sum/quantityOfValues

	return average, nil
}

