UserInput.psm1

<#
    .SYNOPSIS
        Shows the list of properties for user to confirm

    .DESCRIPTION
        Shows the list of properties for user to confirm

    .EXAMPLE
        $Settings = @{
            Prop1 = 'Value1'
            Prop2 = 'Value2'
        }

        Confirm-Selection -Selections $Settings

    .PARAMETER Selections
        Hashtable of settings
#>

function Confirm-Selection {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [Hashtable]$Selections
    )
    $Prompt = "Confirm the following selections by entering y: `n"

    foreach ($selection in $Selections.GetEnumerator()) {
        $Prompt += "$($selection.Name): $($selection.Value)`n"
    }

    Read-ValidInput -Prompt $Prompt  -Regex '^y$' -ErrorMessage 'Either enter y or exit the script to start over' | Out-Null
}

<#
    .SYNOPSIS
        Gets user input that matches a regex pattern

    .DESCRIPTION
        Gets user input that matches a regex pattern

    .EXAMPLE
        Read-ValidInput -Prompt 'Choose a 2 character name' -Regex '^\w{2}$'

    .PARAMETER Prompt
        Prompt for the user when getting input.

    .PARAMETER Regex
        Regex to use when matching the input.

    .PARAMETER ErrorMessage
        Error message to show the user if the regex doesn't match. Will default to showing the regex if not provided.
#>

function Read-ValidInput {
    [CmdletBinding()]
    [OutputType('System.String')]
    param(
        [Parameter(Mandatory = $true)]
        [String]$Prompt,
        [Parameter(Mandatory = $true)]
        [String]$Regex,
        [Parameter(Mandatory = $False)]
        [String]$ErrorMessage = "The value provided does not match the regex: $Regex - Please try again!",
        [Parameter(Mandatory = $False)]
        [String]$DefaultValue = ''
    )

    # Check for valid default value
    if(($DefaultValue -ne '') -and ($DefaultValue -match $Regex)){
        return $DefaultValue
    }elseif($DefaultValue -ne ''){
        Write-Warning "The provided value '$DefaultValue' is not valid."
    }

    do {
        $Response = Read-Host $Prompt
        if($Response -match $Regex){
            break #exit loop
        } else {
            Write-Warning $ErrorMessage
        }
    } while ($true)

    return $Response
}

<#
    .SYNOPSIS
        Allows the user to select from a list of objects

    .DESCRIPTION
        Allows the user to select from a list of objects and returns the specified object.

    .EXAMPLE
        $Items = Get-ChildItem
        Select-FromObjectList -Objects $Items -PropertyName Name

    .PARAMETER Objects
        Array of Objects

    .PARAMETER PropertyName
        The name of the property to use when displaying the objects to the user. Defaults to Name.
#>

function Select-FromObjectList {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [Object[]]$Objects,
        [Parameter(Mandatory = $false)]
        [String]$PropertyName = 'Name',
        [Parameter(Mandatory = $false)]
        [String]$DefaultValue = ''
    )
    $selected = Select-FromList -List $Objects.$PropertyName -DefaultValue $DefaultValue

    Return $Objects[$selected.ID]
}

<#
    .SYNOPSIS
        Allows the user to select from a list

    .DESCRIPTION
        Allows the user to select from a list. Returns the selected item and item location.

    .EXAMPLE
        Select-FromList -List ('a','b')

    .PARAMETER List
        Array of strings
#>

function Select-FromList {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [String[]]$List,
        [Parameter(Mandatory = $false)]
        [String]$DefaultValue = ''
    )

    # Select Default if Available
    if($DefaultValue -ne ''){
        try{
            $found = Select-DefaultFromList -List $List -DefaultValue $DefaultValue
            return $found
        }catch{
            # Continue and allow the user to select from the list since the default is not present
            Write-Warning "The option '$DefaultValue' is not available."
        }
    }

    # Check if there is only one option. If using defaults prompt, since it didn't match.
    if(($List.Count -eq 1) -and ($DefaultValue -eq '')){
        Return [PSCustomObject]@{
            ID = 0
            Value = $List[0]
        }
    }

    $Prompt = ''
    for ($i = 0; $i -lt $List.Count; $i++) {
        $Prompt += "$i - $($List[$i])`n"
    }

    $Response = Read-Host "$Prompt Choose from the list above by entering in the number of the item you want"

    $Number = $null
    if([Int32]::TryParse($Response,[ref]$Number)){
        if($Number -ge 0 -and $Number -lt $List.Count){
            return [PSCustomObject]@{
                ID = $Number
                Value = $List[$Number]
            }
        } else {
            Write-Warning 'The number selected was not a valid option! Please try again.'
            Select-FromList $List
        }
    } else {
        Write-Warning 'The input provided was not a number! Please try again.'
        Select-FromList $List
    }
}

<#
    .SYNOPSIS
        Validates and selects the default from the list.

    .DESCRIPTION
        Validates and selects the default from the list. Throws an error if not found in list.

    .EXAMPLE
        Select-DefaultFromList -List ('a','b') -DefaultValue 'c'

    .PARAMETER List
        Array of strings

    .PARAMETER DefaultValue
        String value to select
#>

function Select-DefaultFromList(){
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [String[]]$List,
        [Parameter(Mandatory = $true)]
        [String]$DefaultValue
    )
    $position = [array]::indexof($List,$DefaultValue)

    if($position -ge 0){
        Return [PSCustomObject]@{
            ID = $position
            Value = $DefaultValue
        }
    }else{
        Throw "The option: $DefaultValue is not available."
    }
}