# HRZones.ps1
# PowerShell script to calculate training heart rate zones using the Karvonen method.
# ----------------------------------------------------------------------
# Copyright (c) 2016-2022 Richard L. Mueller
# Hilltop Lab web site - http://www.rlmueller.net
# Version 1.0 - October 4, 2016
# Version 1.1 - October 8, 2016 - Made $Formula Script scope to support PowerShell V5.
# Version 1.2 - November 27, 2016 - Improve help for unrecognized parameters.
# Version 2.0 - January 21, 2022 - Added help feature to the textboxes on the form,
#                   removed Sex as a parameter, revised formulas for maximum heart rate.
# Version 2.1 - January 29, 2022 - Added form that compares the formulas for maximum HR.
#
# You have a royalty-free right to use, modify, reproduce, and
# distribute this script file in any way you find useful, provided that
# you agree that the copyright owner above has no warranty, obligations,
# or liability for such use.

# Specify the supported parameters. The first one (or several) letters of each parameter can
# be used as an alias. They can also be specified in order without names. The [CmdletBinding]
# attribute is not supported in PowerShell V1. We also cannot specify any parameters as
# mandatory in PowerShell V1. The Param statement must come before any executable statements.
Param(
    [Int16]$RestingHR,
    [Int16]$MaxHR,
    [Int16]$Age,
    [String]$Formula,
    [Switch]$Estimate,
    [Switch]$Help,
    [Switch]$NoGUI,
    [Switch]$Black
)

# Script version and date.
$Version = "Version 2.1 - January 29, 2022"
$Today = Get-Date

# Flag any parameters not recognized and abort the script. Any parameters that do not match
# the supported parameters (specified by the Param statement above) will populate the $Args
# collection, an automatic variable. If all of the parameters supplied are recognized, then
# $Args will be empty.
$Abort = $False
ForEach ($Arg In $Args)
{
    If ($Arg -Like "-*")
    {
        Write-Host "Argument not recognized: $Arg" -ForegroundColor Red -BackgroundColor Black
    }
    Else
    {
        Write-Host "Value not recognized:    $Arg" -ForegroundColor Red -BackgroundColor Black
    }
    $Abort = $True
}
# Breaking out of the above ForEach would not break out of the script. Breaking out
# of the If statment will.
If ($Abort -eq $True)
{
    # Display a brief help listing the supported parameters and their data types.
    Write-Host "Syntax: HRZones.ps1 [[-RestingHR] <Int16>] [[-MaxHR] <Int16>] [[-Age] <Int16>]`r`n" `
        "    [[-Formula] <String>] [-Estimate] [-Help] [-NoGUI] [-Black]" `
        -ForegroundColor Yellow -BackgroundColor Black
    Write-Host "For help use HRZones.ps1 -Help" -ForegroundColor yellow -BackgroundColor Black
    Break
}

# Constants, to validate parameters.
# Allowed range for resting heart rate, in bpm.
$RestingL = 28
$RestingU = 115
# Allowed range for maximum heart rate, in bpm.
$MaxL = 135
$MaxU = 230
# Allowed range for age, in years.
$AgeL = 5
$AgeU = 105
# Allowed values for the formula to use to estimate maximum heart rate.
# The allowed values are case insensitive, but this array should be all lower case so the
# value provided by the user can be easily compared.
$Formulas = @("oakland1", "oakland2", "haskell", "cooper", "tanaka", `
    "gellish", "robergs", "nes")
# Maximum heart rate less resting heart rate is called the heart rate reserve.
# Heart rate reserve must be at least this amount (in bpm).
$MaxRestingDiff = 30

Function Estimate-MaxHR ($UserAge, $FormulaSelected)
{
    # Function to estimate the maximum heart rate from age.
    # Returns an integer.
    # Reference: https://en.wikipedia.org/wiki/Heart-rate
    Switch ($FormulaSelected)
    {
        "Oakland1"
        {
            # Per Oakland University, 2007.
            Return [int16](206.9 - (0.67 * $UserAge))
        }
        "Oakland2"
        {
            # Per Oakland University, 2007.
            Return [int16](192 - (0.007 * $UserAge * $UserAge))
        }
        "Haskell"
        {
            # Per Haskell and Fox (ca. 1970).
            Return [int16](220 - $UserAge)
        }
        "Cooper"
        {
            # Per Kenneth Cooper, 1988.
            Return [int16](205 - (0.5 * $UserAge))
        }
        "Tanaka"
        {
            # Per Tanaka, Monaham, and Seals (2001).
            Return [int16](208 - (0.7 * $UserAge))
        }
        "Gellish"
        {
            # Per Wohlfart and Farazdaghi (2003).
            Return [int16](203.7 /( 1 + [Math]::Exp(0.033 * ($UserAge - 104.3))))
        }
        "Robergs"
        {
            # Per Robergs and Landwehr (2002).
            Return [int16](205.8 - (0.685 * $UserAge))
        }
        "Nes"
        {
            # Nes, et al. (2013).
            Return [int16](211 - (0.64 * $UserAge))
        }
    }
} # End of Function Calculate-MaxHR.

Function Calculate-Zones ($UserResting, $UserMax)
{
    # Function to calculate training heart rate zones - Karvonen method.
    # Per Karvonen, Vuorimaa (1988).
    # Returns an array of 12 integers.
    # $AdjHR is called the heart rate reserve.
    $AdjHR = $UserMax - $UserResting
    $Level = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    $Level[0] = [Int16](0.50 * $AdjHR) + $UserResting
    $Level[1] = [Int16](0.60 * $AdjHR) + $UserResting
    $Level[2] = $Level[1] + 1
    $Level[3] = [Int16](0.70 * $AdjHR) + $UserResting
    $Level[4] = $Level[3] + 1
    $Level[5] = [Int16](0.75 * $AdjHR) + $UserResting
    $Level[6] = $Level[5] + 1
    $Level[7] = [Int16](0.80 * $AdjHR) + $UserResting
    $Level[8] = $Level[7] + 1
    $Level[9] = [Int16](0.90 * $AdjHR) + $UserResting
    $Level[10] = $Level[9] + 1
    $Level[11] = $UserMax
    Return $Level
} # End of Function Calculate-Zones.

Function Get-Help ($Topic)
{
    # Function to display a help form for the requested topic.
    # Specify the form.
    $Font1 = New-Object System.Drawing.Font("Times New Roman", 12, `
        [System.Drawing.FontStyle]::Regular)
    $Font2 = New-Object System.Drawing.Font("Courier New", 10, `
        [System.Drawing.FontStyle]::Regular)

    $HRZHelp = New-Object System.Windows.Forms.Form
    $HRZHelp.Text = "Help for $Topic"
    $HRZHelp.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
    $HRZHelp.Width = 650
    $HRZHelp.Height = 600
    # $HRZHelp.Font = $Font1
    $HRZHelp.KeyPreview = $True

    $HRZHelp.Add_KeyDown({
        If ($_.KeyCode -eq "Enter")
        {
            # User pressed the Enter key (same as Return).
            $HRZHelp.Close()
        }
        If ($_.KeyCode -eq "Escape")
        {
            # User pressed the Esc key (same as Cancel).
            $HRZHelp.Close()
        }
    })

    # Specify the controls on the form.
    $HelpLabel = New-Object Windows.Forms.Label
    $HelpLabel.Top = 20
    $HelpLabel.Left = 20
    $HelpLabel.Width = 600
    $HelpLabel.Height = 190
    $HelpLabel.Font = $Font1

    $FormulaLabel = New-Object Windows.Forms.Label
    $FormulaLabel.Top = 210
    $FormulaLabel.Left = 10
    $FormulaLabel.Width = 660
    $FormulaLabel.Height = 290
    $FormulaLabel.Font = $Font2

    $FooterLabel = New-Object Windows.Forms.Label
    $FooterLabel.Top = 500
    $FooterLabel.Left = 20
    $FooterLabel.Width = 600
    $FooterLabel.Height = 40
    $FooterLabel.Font = $Font1

    Switch ($Topic)
    {
        "Resting Heart Rate"
        {
            $HelpLabel.Text = "Your resting heart rate should be entered in beats per minute (bpm). " `
            + "The value should be an integer between $RestingL and $RestingU bpm. If you have a heart rate " `
            + "monitor, you can measure your resting heart rate first thing in the morning, " `
            + "before you get out of bed. You can average readings over several days. " `
            + "Resting heart rate can be lowered, temporarily, after aerobic training at altitude. " `
            + "Also, as aerobic fitness improves, resting heart rate generally declines. This increases " `
            + "the heart rate reserve, which is maximum heart rate less resting heart rate. " `
            + "Resting heart rate is increased by illness and some drugs. " `
            + "The table of normal resting heart rates below is based on the Wikipedia article: " `
            + "https://en.wikipedia.org/wiki/Heart_rate#Resting_heart_rate"

            $FormulaLabel.Text = "Age (years)                  Range for Resting Heart Rate (bpm)`r`n" `
            + "------------------------     ----------------------------------`r`n" `
            + " 5-6                          75-115`r`n" `
            + " 7-9                          70-110`r`n" `
            + " > 10                         60-100`r`n" `
            + "trained adult athletes        40- 60`r`n" `
            + "endurance athletes            33- 50"

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "Maximum Heart Rate"
        {
            $HelpLabel.Text = "If you know your maximum heart rate, enter the value in beats per minute (bpm). " `
            + "It should be an integer between $MaxL and $MaxU bpm. The best way to measure your maximum " `
            + "heart rate is to take a cardic stress test on a treadmill (under medical supervision). " `
            + "Otherwise, if you wear a heart rate monitor during workouts, and you are in excellent health, " `
            + "you may base you maximum rate on the reading after a run of about 2 miles (or a similar aerobic " `
            + "activity) where you sprint at the end. An average of several such runs is best. " `
            + "If you do not know your maximum heart rate, this program can estimate a maximum " `
            + "based on your age. Simply check the box to estimate maximum heart rate from age on the form. " `
            + "You then can enter your age. In addition, select one of the 8 formulas to be used to " `
            + "estimate your maximum heart rate."

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "Age"
        {
            $HelpLabel.Text = "Enter your age in whole years. The value should be an integer between $AgeL and " `
            + "$AgeU years."

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "Oakland1"
        {
            $HelpLabel.Text = "Oakland University researchers in 2007 developed two formulas to estimate maximum " `
            + "heart rate. They studied 132 individuals over 25 years. Their first formula, similar to " `
            + "the Tanaka formula, has a confidence interval of plus or minus 5-8 bpm. " `
            + "In the formula below, the expression ""[int16]"" means that the result is rounded to the " `
            + "nearest whole 16-bit integer. The formula is:"

            $FormulaLabel.Text = "HR_max = [int16](206.9 - (0.67 * Age))"

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "Oakland2"
        {
            $HelpLabel.Text = "Oakland University researchers developed two formulas to estimate maximum " `
            + "heart rate in 2007. They studied 132 individuals over 25 years. Their second formula " `
            + "has a confidence interval of plus or minus 2-5 bpm. In the formula below, the ""^"" " `
            + "character is the symbol to raise to a power. So ""Age^2"" is the square of the Age. " `
            + "The expression ""[int16]"" means that the result is rounded to the " `
            + "nearest whole 16-bit integer. The resulting nonlinear formula is:"

            $FormulaLabel.Text = "HR_max = [int16](192 - (0.007 * Age^2))"

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "Haskell"
        {
            $HelpLabel.Text = "Dr. William Haskell and Dr. Samual Fox are believed to have developed their " `
            + "formula in 1970. It seems to have been based on several scientific papers, rather than " `
            + "original research. It is the most commonly known formula, and the easiest to remember and " `
            + "calculate. However much research reveals it has large errors. It is not considered as " `
            + "accurate as the alternatives. " `
            + "In the formula below, the expression ""[int16]"" means that the result is rounded to the " `
            + "nearest whole 16-bit integer. The formula is:"

            $FormulaLabel.Text = "HR_max = [int16](220 - Age)"

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "Cooper"
        {
            $HelpLabel.Text = "Dr. Kenneth Cooper published his formula on page 253 of his book " `
            + """Controlling Cholesterol"", copyright 1988. I personally find it most closely matches " `
            + "my own experience. " `
            + "In the formula below, the expression ""[int16]"" means that the result is rounded to the " `
            + "nearest whole 16-bit integer. The formula is:"

            $FormulaLabel.Text = "HR_max = [int16](205 - (0.5 * Age))"

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "Tanaka"
        {
            $HelpLabel.Text = "Tanaka, Monahan, and Seals in 2001 studied some 19,000 subjects to come up with " `
            + "their formula. They reported the formula correlated with age, regardless of gender, with a " `
            + "standard deviation of 10 bpm. " `
            + "In the formula below, the expression ""[int16]"" means that the result is rounded to the " `
            + "nearest whole 16-bit integer. The formula is:"

            $FormulaLabel.Text = "HR_max = [int16](208 - (0.7 * Age))"

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "Gellish"
        {
            $HelpLabel.Text = "The Gellish formula is attributed to B. Wohlfart and G. R. Farazdaghi. " `
            + "It is based on a 2003 study of bicycle ergometry in Lund, Sweden. In the formula " `
            + "below, e = 2.71828 (Euler's number, the base of natural logarithms). The ""^"" symbol means " `
            + "to raise to the power. In this case, ""e"" is raised to the power of the " `
            + "expression ""(0.033 * (Age - 104.3))"". " `
            + "The expression ""[int16]"" means that the result is rounded to the " `
            + "nearest whole 16-bit integer. The formula is:"

            $FormulaLabel.Text = "HR_max = [int16](203.7 / (1 + e^(0.033 * (Age - 104.3))))"

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "Robergs"
        {
            $HelpLabel.Text = "Robergs and Landwehr studied 43 formulas in 2002 and concluded that the best " `
            + "formula has a standard deviation of 6.4 bpm. " `
            + "In the formula below, the expression ""[int16]"" means that the result is rounded to the " `
            + "nearest whole 16-bit integer. The formula is:"

            $FormulaLabel.Text = "HR_max = [int16](205.8 - (0.685 * Age))"

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "Nes"
        {
            $HelpLabel.Text = "Nes et al. studied 3,320 healthy men and women between 19 and 89 years of " `
            + "age. They determined their formula held regardless of gender, body mass index, or activity level. " `
            + "They reported a standard deviation of 10.8 bpm. " `
            + "In the formula below, the expression ""[int16]"" means that the result is rounded to the " `
            + "nearest whole 16-bit integer. The formula is:"

            $FormulaLabel.Text = "HR_max = [int16](211 - (0.64 * Age))"

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "Karvonen"
        {
            $HelpLabel.Text = "The Karvonen method is used to calculate training heart rate zones. " `
            + "The method is described on pages 58-69 of the book ""Serious Training for Serious " `
            + "Athletes"", copyright 1989, by Rob Sleamaker. The method requires knowing the resting " `
            + "and maximum heart rates. The difference between these values is called the heart rate reserve.`r`n" `
            + "HR_Reserve = HR_Max - HR_Resting`r`n" `
            + "Percentages of heart rate reserve correspond to percentages of VO2Max, maximal oxygen uptake " `
            + "capacity. The suggested training heart rate zones are defined by the following table (from " `
            + "table 5.1 on page 60 of Rob Sleamaker's book):"

            $FormulaLabel.Text = "                 Intensity`r`n" `
            + "Level  % VO2Max  (% HR_Max)  Description`r`n" `
            + "-----  --------  ---------   ----------------`r`n" `
            + "                   50-60     Warmup`r`n" `
            + " 1      55-65      61-70     Over Distance`r`n" `
            + " 2      66-75      71-75     Endurance / Easy Speed`r`n" `
            + " 3      76-80      76-80     Endurance / Long Races`r`n" `
            + " 4      81-90      81-90     Anaerobic Threshold / Intervals`r`n" `
            + " 5      91-100     91-100    Peaking / Racing"

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
        "All Formulas"
        {
            $HelpLabel.Text = "Results from all of the formulas are shown in the table below. This may help " `
            + "you decide which formula is best for your situation.`r`n" `
            + "Notice that the Haskell formula results in the estimated maximum heart rate dropping off " `
            + "the fastest with age. The Cooper formula drops off more slowly with age, so the maximum is higher " `
            + "for older people. The differences between all of the formulas is less for middle ages, " `
            + "between 25 and 45 years of age."

            $FormulaLabel.Text = "Age Oakland1 Oakland2 Haskell   Cooper   Tanaka  Gellish   Roberg    Nes`r`n" `
            + "--- -------- -------- -------- -------- -------- -------- -------- -------`r`n" `
            + "  5   204      192      215      202      204      196      202      208`r`n" `
            + " 10   200      191      210      200      201      195      199      205`r`n" `
            + " 15   197      190      205      198      198      194      196      201`r`n" `
            + " 20   194      189      200      195      194      192      192      198`r`n" `
            + " 25   190      188      195      192      190      190      189      195`r`n" `
            + " 30   187      186      190      190      187      188      185      192`r`n" `
            + " 35   183      183      185      188      184      185      182      189`r`n" `
            + " 40   180      181      180      185      180      182      178      185`r`n" `
            + " 45   177      178      175      182      176      178      175      182`r`n" `
            + " 50   173      174      170      180      173      175      172      179`r`n" `
            + " 55   170      171      165      178      170      170      168      176`r`n" `
            + " 60   167      167      160      175      166      165      165      173`r`n" `
            + " 65   163      162      155      172      162      160      161      169`r`n" `
            + " 70   160      158      150      170      159      154      158      166`r`n" `
            + " 75   157      153      145      168      156      148      154      163`r`n" `
            + " 80   153      147      140      165      152      141      151      160`r`n" `
            + " 85   150      141      135      162      148      133      148      157"

            $FooterLabel.Text = "To return to the main screen, press the ""Enter"" or ""Esc"" keys, " `
            + "or click the ""X"" in the upper right to close this Help."
        }
    }

    $HRZHelp.Controls.Add($HelpLabel)
    $HRZHelp.Controls.Add($FormulaLabel)
    $HRZHelp.Controls.Add($FooterLabel)

    $HRZHelp.Add_Load({
        $HRZHelp.Activate()

    })

    # Display the form to the user.
    $HRZHelp.ShowDialog()

} # End of Function Get-Help

Function Validate-Form()
{
    # Function to validate values on the form.
    # Returns $True if all values are valid, otherwise returns $False.

    # Check for request for help.
    $Continue = $True
    If ($Resting.Text -eq "?")
    {
        Get-Help "Resting Heart Rate"
        $Resting.Text = ""
        $Continue = $False
    }
    If ($Max.Text -eq "?")
    {
        Get-Help "Maximum Heart Rate"
        $Max.Text = ""
        $Continue = $False
    }
    If ($AgeBox.Text -eq "?")
    {
        Get-Help "Age"
        $AgeBox.Text = ""
        $Continue = $False
    }

    If ($Continue -eq $True)
    {
        # Retrieve values from the form.
        $RestingHR = [Int16]$Resting.Text
        $MaxHR = [Int16]$Max.Text
        $Age = [Int16]$AgeBox.Text
        If ($EstimateChk.CheckState -eq "Checked") {$Estimate = $True}
        Else {$Estimate = $False}

        # Initialize controls on the form.
        $RestingFlag.Visible = $False
        $MaxFlag.Visible = $False
        $AgeFlag.Visible = $False
        $Warning.Visible = $False
        $FormulaLabel.Visible = $False
        $Formula1.Visible = $False
        $Formula2.Visible = $False
        $Formula3.Visible = $False
        $Formula4.Visible = $False
        $Formula5.Visible = $False
        $Formula6.Visible = $False
        $Formula7.Visible = $False
        $Formula8.Visible = $False
        $FHelpLabel.Visible = $False
        $FHelp.Visible = $False
        $AllHelp.Visible = $False
        $RestingLabel.ForeColor = "Black"
        $MaxLabel.ForeColor = "Black"
        $AgeLabel.ForeColor = "Black"
        $ReportLabel.Visible = $False
        $ZonesHelp.Visible = $False
        $L0Label.Visible = $False
        $L1Label.Visible = $False
        $L2Label.Visible = $False
        $L3Label.Visible = $False
        $L4Label.Visible = $False
        $L5Label.Visible = $False
        $L0BPM.Text = ""
        $L1BPM.Text = ""
        $L2BPM.Text = ""
        $L3BPM.Text = ""
        $L4BPM.Text = ""
        $L5BPM.Text = ""

        # If variable $Go is $True, the function will return with the value $True.
        # Otherwise the function will return with the value $False.
        $Go = $True

        # Validate resting heart rate.
        If (($RestingHR) -And (($RestingHR -lt $RestingL) -Or ($RestingHR -gt $RestingU)))
        {
            $RestingLabel.ForeColor = "Red"
            $RestingFlag.Visible = $True
            $Go = $False
        }

        # Validate maximum heart rate, if needed.
        If ((-Not $Estimate) -And ($MaxHR) -And (($MaxHR -lt $MaxL) -Or ($MaxHR -gt $MaxU)))
        {
            $MaxLabel.ForeColor = "Red"
            $MaxFlag.Visible = $True
            $Go = $False
        }

        # Validate age, if needed.
        If (($Age) -And ($Estimate) -And (($Age -lt $AgeL) -Or ($Age -gt $AgeU)))
        {
            $AgeLabel.ForeColor = "Red"
            $AgeFlag.Visible = $True
            $Go = $False
        }

        If ($Estimate -eq $True)
        {
            $MaxLabel.Visible = $False
            $Max.Visible = $False
            $AgeLabel.Visible = $True
            $AgeBox.Visible = $True
            $EstimateMaxLabel.Visible = $False
            $EstimateMax.Visible = $False
            $FormulaLabel.Visible = $True
            $Formula1.Visible = $True
            $Formula2.Visible = $True
            $Formula3.Visible = $True
            $Formula4.Visible = $True
            $Formula5.Visible = $True
            $Formula6.Visible = $True
            $Formula7.Visible = $True
            $Formula8.Visible = $True
            $FHelpLabel.Visible = $True
            $FHelp.Visible = $True
            $AllHelp.Visible = $True
            If ($Go -eq $True)
            {
                If ($AgeFlag.Visible -eq $False)
                {
                    If ($Age)
                    {
                        # $Formula must have Script scope in PowerShell Version 5, so the value
                        # can be assigned by the RadioButtons.
                        $EstimateMax.Text = Estimate-MaxHR $Age $Script:Formula
                        $EstimateMaxLabel.Visible = $True
                        $EstimateMax.Visible = $True
                        If (-Not $RestingHR) {$Go = $False}
                    }
                }
                If (-Not $Age)
                {
                    $EstimateMax.Text = ""
                    $EstimateMaxLabel.Visible = $False
                    $EstimateMax.Visible = $False
                    $Report.Visible = $False
                    $Go = $False
                }
            }
        }

        If (-Not $Estimate)
        {
            $MaxLabel.Visible = $True
            $Max.Visible = $True
            $AgeLabel.Visible = $False
            $AgeBox.Visible = $False
            $EstimateMaxLabel.Visible = $False
            $EstimateMax.Visible = $False
            $FormulaLabel.Visible = $False
            $Formula1.Visible = $False
            $Formula2.Visible = $False
            $Formula3.Visible = $False
            $Formula4.Visible = $False
            $Formula5.Visible = $False
            $Formula6.Visible = $False
            $Formula7.Visible = $False
            $Formula8.Visible = $False
            $FHelpLabel.Visible = $False
            $FHelp.Visible = $False
            $AllHelp.Visible = $False
            If ((-Not $RestingHR) -Or (-Not $MaxHR)) {$Go = $False}
        }

        If (($Go -eq $True) -And ($Estimate))
        {
            # $Formula must have Script scope in PowerShell Version 5, so the value
            # can be assigned by the RadioButtons.
            $MaxHR = Estimate-MaxHR $Age $Script:Formula
        }

        If (($Go -eq $True) -And (($RestingHR + $MaxRestingDiff) -gt $MaxHR))
        {
            $Warning.Visible = $True
            $RestingFlag.Visible = $True
            $RestingLabel.ForeColor = "Red"
            $MaxFlag.Visible = $True
            $MaxLabel.ForeColor = "Red"
            $Go = $False
        }

        If ($Go -eq $True)
        {
            $ReportLabel.Visible = $True
            $ZonesHelp.Visible = $True
            $L0Label.Visible = $True
            $L1Label.Visible = $True
            $L2Label.Visible = $True
            $L3Label.Visible = $True
            $L4Label.Visible = $True
            $L5Label.Visible = $True
            $L0BPM.Visible = $True
            $L1BPM.Visible = $True
            $L2BPM.Visible = $True
            $L3BPM.Visible = $True
            $L4BPM.Visible = $True
            $L5BPM.Visible = $True
            # $L is an array of 12 integers returned by Function Calculate-Zones.
            $L = Calculate-Zones $RestingHR $MaxHR
            $L0BPM.Text = $L[0].ToString() + " - " + $L[1].ToString() + " bpm"
            $L1BPM.Text = $L[2].ToString() + " - " + $L[3].ToString() + " bpm"
            $L2BPM.Text = $L[4].ToString() + " - " + $L[5].ToString() + " bpm"
            $L3BPM.Text = $L[6].ToString() + " - " + $L[7].ToString() + " bpm"
            $L4BPM.Text = $L[8].ToString() + " - " + $L[9].ToString() + " bpm"
            $L5BPM.Text = $L[10].ToString() + " - " + $L[11].ToString() + " bpm"
            $Report.Visible = $True
        }
        Else {$Report.Visible = $False}
    } # End If ($Continue -eq $True).
    Else {$Go = $False}

    Return $Go

} # End of Function Validate-Form.

If ($Help)
{
    # User has requested help information.
    "HRZones.ps1"
    "$Version"
    "PowerShell script to calculate training heart rate zones using the Karvonen method."
    "Parameters"
    "    -RestingHR: Resting heart rate in beats per minute. An integer between $RestingL"
    "        and $RestingU. If no value is provided you will be prompted for a value."
    "    -MaxHR: Maximum heart rate in beats per minute. An integer between $MaxL and $MaxU."
    "        If no value is provided, an estimate can be calculated from your age."
    "    -Age: Your age in years. An integer between $AgeL and $AgeU. Only needed if you"
    "        do not provide a maximum heart rate, or you use the -Estimate parameter."
    "    -Formula: The formula to use to estimate the maximum heart rate (if needed)."
    "        Options: Oakland1 (default), Oakland2, Haskell, Cooper, Tanaka, Gellish,"
    "        Robergs, or Nes."
    "    -Estimate: A switch indicating that you want the script to calculate an estimated"
    "        maximum heart rate. You are prompted for your age (if you did not provide"
    "        the -Age parameter)."
    "    -NoGUI: A switch that requests that the GUI form not be used. Data input is accepted"
    "        and all results are displayed at the command line."
    "    -Black: A switch to output results without using the Write-Host cmdlet. The output"
    "        will not have colors, but it can be redirected to a text file."
    "    -Help: A switch that outputs this help."
    "Note: The first one (or several) letters of each parameter can be used as aliases."
    "    In addition, the parameters -RestingHR, -MaxHR, -Age, and -Formula can be"
    "    listed in order without the parameter names. See the first example below."
    "Some usage examples:"
    ".\HRZones.ps1 52 180"
    "    Calculate heart rate zones based on a resting HR of 52 and a maximum HR of 180."
    ".\HRZones.ps1 -MaxHR 180 -RestingHR 52"
    "    Same as the previous."
    ".\HRZones.ps1 52 180 40 Cooper -est"
    "    Calculate zones based on a resting HR of 52 and estimated maximum using the Cooper"
    "    formula and a 40 year old. The 180 maximum HR will not be used."
    ".\HRZone.ps1 -Estimate -Age 39 -RestingHR 52"
    "    Calculate heart rate zones based on a resting HR of 52 and an estimated"
    "    maximum. The maximum heart rate will be calculated base on a 39 year old."
    "    The default formula, Oakland1, is used to estimate the maximum heart rate."
    ".\HRZones.ps1 -r 52 -a 39 -e -n -b > Report.txt"
    "    Same as the previous, but no GUI is selected and black is selected. The output is"
    "    redirected to the text file Report.txt in the current directory."
    ".\HRZones.ps1 -R 52"
    "    You are prompted for either your maximum heart rate or your age."
    ".\HRZones.ps1"
    "    You are prompted for resting heart rate and either maximum heart rate or age."
    "Example output:"
    "-----"
    "HRZones.ps1"
    "Version 2.1 - January 29, 2022"
    "Age: 39"
    "Resting heart rate: 52 bpm"
    "Maximum heart rate: 181 bpm (estimate, Oakland1)"
    "Calculated training heart rate zones (1/31/2022 13:32:17):"
    "Level  Description             Intensity    bpm"
    "-----  ----------------------  ---------  ---------"
    "       warmup                   50-60%    116 - 129"
    "   1   over distance            61-70%    130 - 142"
    "   2   endurance / easy speed   71-75%    143 - 149"
    "   3   endurance / long races   76-80%    150 - 155"
    "   4   anaerobic threshold      81-90%    156 - 168"
    "   5   peaking / racing         91-100%   169 - 181"
    Break
}

If (($ExecutionContext.SessionState.LanguageMode -eq "ConstrainedLanguage") -Or ($NoGUI))
{
    # PowerShell is constrained. Possibly Windows 8.1 RT, or the user has requested no GUI.
    # Cannot use Windows forms.

    If ($ExecutionContext.SessionState.LanguageMode -eq "ConstrainedLanguage")
    {
        Write-Host "PowerShell constrained on this computer. Windows forms not used." `
            -ForegroundColor yellow -BackgroundColor Black
    }

    # Validate the input parameters

    # Default formula for estimated maximum heart rate.
    # $Formula must have Script scope in PowerShell Version 5, so the value
    # can be assigned by the RadioButtons.
    If (-Not $Script:Formula) {$Script:Formula = "Oakland1"}

    # Check for a valid formula.
    If ($Formulas -NotContains $Script:Formula.ToLower())
    {
        # Formula to calculate estimated maximum heart rate not recognized.
        Do
        {
            # PowerShell V1 does not support the -Join PowerShell operator.
            # Use the .NET Framework function instead.
            $ValidValues = [String]::Join(", ", $Formulas)
            Write-Host "Formula to estimate maximum heart rate ($Script:Formula) not" `
                "recognized.`r`n" `
                "Valid values: $ValidValues" -ForegroundColor Red -BackgroundColor Black
            $Script:Formula = Read-Host "Enter formula to estimate maximum heart rate"
        } Until ($Formulas -Contains $Script:Formula.ToLower())
    }

    # If no maximum HR specfied, but Age is, calculate an estimated maximum.
    If ((-Not $MaxHR) -And $Age) {$Estimate = $True}

    If (-Not $RestingHR)
    {
        # Resting heart rate not provided.
        $RestingHR = Read-Host "Enter your resting heart rate (in beats per minute)"
    }

    If (($RestingHR -lt $RestingL) -Or ($RestingHR -gt $RestingU))
    {
        # Resting heart rate is out of range.
        Do
        {
            Write-Host "Resting heart rate ($RestingHR) must be between $RestingL and" `
                "$RestingU" -ForegroundColor Red -BackgroundColor Black
            $RestingHR = Read-Host "Enter your resting heart rate (in beats per minute)"
        } Until (($RestingHR -ge $RestingL) -And ($RestingHR -le $RestingU))
    }

    If ((-Not $MaxHR) -And (-Not $Estimate) -And (-Not $Age))
    {
        # User did not specify a maximum HR or an estimated maximum or Age.
        $Ans = Read-Host "Enter your maximum heart rate (in beats per minute)`r`n" `
            "Or enter ""0"" to have it estimated from your age"
        If ($Ans -eq 0) {$Estimate = $True}
        Else {$MaxHr = $Ans}
    }

    If (($MaxHR) -And (-Not $Estimate) -And (($MaxHR -lt $MaxL) -Or ($MaxHR -gt $MaxU)))
    {
        # Specified maximum heart rate is out of range.
        Do
        {
            Write-Host "Maximum heart rate ($MaxHR) must be between $MaxL and $MaxU" `
                -ForegroundColor Red -BackgroundColor Black
            $MaxHR = Read-Host "Enter your maximum heart rate (in beats per minute)"
        } Until (($MaxHR -ge $MaxL) -And ($MaxHR -le $MaxU))
    }

    # If maximum heart rate not provided, estimate based on age.
    If ((-Not $MaxHR) -Or ($Estimate))
    {
        If ($MaxHR)
        {
            # User specified $MaxHR but also selected to estimate maximum heart rate.
            Do
            {
                Write-Host "Estimated maximum heart rate requested, but maximum of`r`n" `
                    "$MaxHR specified." -ForegroundColor Red -BackgroundColor Black
                $Ans = Read-Host "Enter 0 to use specified maximum ($MaxHR), 1 to`r`n" `
                    "estimate based on age."
                If ($Ans -eq 0)
                {
                    # Estimate changed from $True to $False. Check if $MaxHR in range.
                    $Estimate = $False
                    If (($MaxHR -lt $MaxL) -Or ($MaxHR -gt $MaxU))
                    {
                        # Specified maximum heart rate is out of range.
                        Do
                        {
                            Write-Host "Maximum heart rate ($MaxHR) must be between" `
                                "$MaxL and $MaxU" -ForegroundColor Red -BackgroundColor Black
                            $MaxHR = Read-Host "Enter your maximum heart rate" `
                                "(in beats per minute)"
                        } Until (($MaxHR -ge $MaxL) -And ($MaxHR -le $MaxU))
                    }
                }
            } Until (($Ans -eq 0) -Or ($Ans -eq 1))
        }
        If ($Estimate)
        {
            If ((-Not $Age) -Or ($Age -lt $AgeL) -Or ($Age -gt $AgeU))
            {
                # Valid age not provided.
                If (($Age -lt $AgeL) -Or ($Age -gt $AgeU))
                {
                     Write-Host "Age ($Age) must be in years, between $AgeL and $AgeU" `
                        -ForegroundColor Red -BackgroundColor Black
                }
                # Prompt for Age until a valid value is provided.
                Do
                {
                    $Age = Read-Host "Enter your age (in years)"
                    If (($Age -lt $AgeL) -Or ($Age -gt $AgeU))
                    {
                         Write-Host "Age must be in years, between $AgeL and $AgeU" `
                            -ForegroundColor Red -BackgroundColor Black
                    }
                } Until (($Age -ge $AgeL) -And ($Age -le $AgeU))
            }
            # Calculate estimated maximum heart rate based on age.
            # $Formula must have Script scope in PowerShell Version 5, so the value
            # can be assigned by the RadioButtons.
            $MaxHR = Estimate-MaxHR $Age $Script:Formula
            $Estimate = $True
        }
    }
    If (($RestingHR + $MaxRestingDiff) -gt $MaxHR)
    {
        # The maximum heart rate must be greater than the minimum by at least
        # the specified amount.
        Write-Host "Invalid values, the maximum heart rate ($MaxHR) must be at least" `
            "$MaxRestingDiff greater than the resting heart rate ($RestingHR)" `
            -ForegroundColor Red -BackgroundColor Black
        Break
    }
} # End of code when PowerShell is constrained.
Else
{
    # PowerShell is not constrained. Windows forms can be used.

    [void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

    # Specify the form.
    $Font = New-Object System.Drawing.Font("Times New Roman", 12, `
        [System.Drawing.FontStyle]::Regular)

    $HRZForm = New-Object System.Windows.Forms.Form
    $HRZForm.Text = "Calculate Training Heart Rate Zones - Karvonen Method"
    $HRZForm.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
    $HRZForm.Width = 600
    $HRZForm.Height = 620
    $HRZForm.Font = $Font
    $HRZForm.KeyPreview = $True

    $HRZForm.Add_KeyDown({
        If ($_.KeyCode -eq "Enter")
        {
            # User pressed the Enter key (same as Return). Only exit if all values on the form
            # are valid. When the user clicks the Calculate button, the focus remains on the
            # button and the Enter key event is not recognized. The code for the Calculate
            # button ends by invoking the Down arrow key, so the Enter key is recognized.
            $Status = Validate-Form
        }
        If ($_.KeyCode -eq "Escape")
        {
            # User pressed the Esc key (same as Cancel).
            $HRZForm.DialogResult = "Cancel"
            $HRZForm.Close()
        }
    })

    # Specify the controls on the form.
    $TopLabel = New-Object Windows.Forms.Label
    $TopLabel.Text = "Enter ""?"" (without the quotes) in any text box and press ""Enter"" for help on entry"
    $TopLabel.Top = 10
    $TopLabel.Left = 20
    $TopLabel.Width = 560
    $TopLabel.ForeColor = "Green"
    $HRZForm.Controls.Add($TopLabel)

    $RestingLabel = New-Object Windows.Forms.Label
    $RestingLabel.Text = "Resting heart rate, bpm (integer from $RestingL to $RestingU)"
    $RestingLabel.Top = 50
    $RestingLabel.Left = 20
    $RestingLabel.Width = 400
    $HRZForm.Controls.Add($RestingLabel)

    $Resting = New-Object System.Windows.Forms.TextBox
    $Resting.Top = 50
    $Resting.Left = 420
    $Resting.Width = 40
    $HRZForm.Controls.Add($Resting)

    $MaxLabel = New-Object Windows.Forms.Label
    $MaxLabel.Text = "Maximum heart rate, bpm (integer from $MaxL to $MaxU)"
    $MaxLabel.Top = 80
    $MaxLabel.Left = 20
    $MaxLabel.Width = 400
    $HRZForm.Controls.Add($MaxLabel)

    $Max = New-Object System.Windows.Forms.TextBox
    $Max.Top = 80
    $Max.Left = 420
    $Max.Width = 40
    $HRZForm.Controls.Add($Max)

    $Warning = New-Object Windows.Forms.Label
    $Warning.Text = "Maximum HR must be at least $MaxRestingDiff bpm greater than resting!"
    $Warning.Top = 30
    $Warning.Left= 20
    $Warning.Width = 400
    $Warning.ForeColor = "Red"
    $HRZForm.Controls.Add($Warning)

    $EstimateLabel = New-Object Windows.Forms.Label
    $EstimateLabel.Text = "Check the box to estimate maximum heart rate from age"
    $EstimateLabel.Top = 110
    $EstimateLabel.Left = 20
    $EstimateLabel.Width = 400
    $HRZForm.Controls.Add($EstimateLabel)

    $FormulaLabel = New-Object Windows.Forms.Label
    $FormulaLabel.Text = "Select a formula to estimate max HR.  Click here to compare >>"
    $FormulaLabel.Top = 140
    $FormulaLabel.Left = 20
    $FormulaLabel.Width = 430
    $HRZForm.Controls.Add($FormulaLabel)

    $Formula1 = New-Object Windows.Forms.RadioButton
    $Formula1.Text = "Oakland1"
    $Formula1.Top = 170
    $Formula1.Left = 20
    $Formula1.Width = 90
    $Formula1.Add_Click({
        # $Formula must have Script scope in PowerShell Version 5, so the value
        # can be assigned by the RadioButtons.
        $Script:Formula = "Oakland1"
    })
    $HRZForm.Controls.Add($Formula1)

    $Formula2 = New-Object Windows.Forms.RadioButton
    $Formula2.Text = "Oakland2"
    $Formula2.Top = 170
    $Formula2.Left = 140
    $Formula2.Width = 90
    $Formula2.Add_Click({
        $Script:Formula = "Oakland2"
    })
    $HRZForm.Controls.Add($Formula2)

    $Formula3 = New-Object Windows.Forms.RadioButton
    $Formula3.Text = "Haskell"
    $Formula3.Top = 170
    $Formula3.Left = 260
    $Formula3.Width = 90
    $Formula3.Add_Click({
        $Script:Formula = "Haskell"
    })
    $HRZForm.Controls.Add($Formula3)

    $Formula4 = New-Object Windows.Forms.RadioButton
    $Formula4.Text = "Cooper"
    $Formula4.Top = 170
    $Formula4.Left = 380
    $Formula4.Width = 90
    $Formula4.Add_Click({
        $Script:Formula = "Cooper"
    })
    $HRZForm.Controls.Add($Formula4)

    $Formula5 = New-Object Windows.Forms.RadioButton
    $Formula5.Text = "Tanaka"
    $Formula5.Top = 200
    $Formula5.Width = 90
    $Formula5.Left = 20
    $Formula5.Add_Click({
        $Script:Formula = "Tanaka"
    })
    $HRZForm.Controls.Add($Formula5)

    $Formula6 = New-Object Windows.Forms.RadioButton
    $Formula6.Text = "Gellish"
    $Formula6.Top = 200
    $Formula6.Left = 140
    $Formula6.Width = 90
    $Formula6.Add_Click({
        $Script:Formula = "Gellish"
    })
    $HRZForm.Controls.Add($Formula6)

    $Formula7 = New-Object Windows.Forms.RadioButton
    $Formula7.Text = "Robergs"
    $Formula7.Top = 200
    $Formula7.Left = 260
    $Formula7.Width = 90
    $Formula7.Add_Click({
        $Script:Formula = "Robergs"
    })
    $HRZForm.Controls.Add($Formula7)

    $Formula8 = New-Object Windows.Forms.RadioButton
    $Formula8.Text = "Nes, et al."
    $Formula8.Top = 200
    $Formula8.Left = 380
    $Formula8.Width = 90
    $Formula8.Add_Click({
        $Script:Formula = "Nes"
    })
    $HRZForm.Controls.Add($Formula8)

    $AgeLabel = New-Object Windows.Forms.Label
    $AgeLabel.Text = "Age, years (integer from $AgeL to $AgeU)"
    $AgeLabel.Top = 260
    $AgeLabel.Left = 20
    $AgeLabel.Width = 400
    $HRZForm.Controls.Add($AgeLabel)

    $AgeBox = New-Object System.Windows.Forms.TextBox
    $AgeBox.Top = 260
    $AgeBox.Left = 420
    $AgeBox.Width = 40
    $HRZForm.Controls.Add($AgeBox)

    $EstimateMaxLabel = New-Object System.Windows.Forms.Label
    $EstimateMaxLabel.Text = "Estimated maximum heart rate, bpm"
    $EstimateMaxLabel.Top = 290
    $EstimateMaxLabel.Left = 20
    $EstimateMaxLabel.Width = 400
    $HRZForm.Controls.Add($EstimateMaxLabel)

    $EstimateMax = New-Object System.Windows.Forms.Label
    $EstimateMax.Text = ""
    $EstimateMax.Top = 290
    $EstimateMax.Left = 420
    $EstimateMax.Width = 40
    $HRZForm.Controls.Add($EstimateMax)

    $ReportLabel = New-Object System.Windows.Forms.Label
    $ReportLabel.Text = "Calculated training heart rate zones. Click here for help >>"
    $ReportLabel.Top = 320
    $ReportLabel.Left = 20
    $ReportLabel.Width = 400
    $HRZForm.Controls.Add($ReportLabel)

    $ZonesHelp = New-Object Windows.Forms.Button
    $ZonesHelp.Text = "Zones Help"
    $ZonesHelp.Top = 320
    $ZonesHelp.Left = 420
    $ZonesHelp.Width = 110
    $ZonesHelp.Add_Click({
        # The user clicked the Zones Help button.
        Get-Help "Karvonen"
    }) # End of code for the Zones Help button, $ZonesHelp.Add_Click.
    $HRZForm.Controls.Add($ZonesHelp)

    $L0Label = New-Object System.Windows.Forms.Label
    $L0Label.Text = "Warmup       50-60% Intensity"
    $L0Label.Top = 350
    $L0Label.Left = 40
    $L0Label.Width = 340
    $HRZForm.Controls.Add($L0Label)

    $L0BPM = New-Object System.Windows.Forms.Label
    $L0BPM.Text = ""
    $L0BPM.Top = 350
    $L0BPM.Left = 380
    $L0BPM.Width = 120
    $HRZForm.Controls.Add($L0BPM)

    $L1Label = New-Object System.Windows.Forms.Label
    $L1Label.Text = "Level 1 (over distance)  61-70% Intensity"
    $L1Label.Top = 380
    $L1Label.Left = 40
    $L1Label.Width = 340
    $HRZForm.Controls.Add($L1Label)

    $L1BPM = New-Object System.Windows.Forms.Label
    $L1BPM.Text = ""
    $L1BPM.Top = 380
    $L1BPM.Left = 380
    $L1BPM.Width = 120
    $HRZForm.Controls.Add($L1BPM)

    $L2Label = New-Object System.Windows.Forms.Label
    $L2Label.Text = "Level 2 (endurance/easy speed)  71-75% Intensity"
    $L2Label.Top = 410
    $L2Label.Left = 40
    $L2Label.Width = 340
    $HRZForm.Controls.Add($L2Label)

    $L2BPM = New-Object System.Windows.Forms.Label
    $L2BPM.Text = ""
    $L2BPM.Top = 410
    $L2BPM.Left = 380
    $L2BPM.Width = 120
    $HRZForm.Controls.Add($L2BPM)

    $L3Label = New-Object System.Windows.Forms.Label
    $L3Label.Text = "Level 3 (endurance/long races) 76-80% Intensity"
    $L3Label.Top = 440
    $L3Label.Left = 40
    $L3Label.Width = 340
    $HRZForm.Controls.Add($L3Label)

    $L3BPM = New-Object System.Windows.Forms.Label
    $L3BPM.Text = ""
    $L3BPM.Top = 440
    $L3BPM.Left = 380
    $L3BPM.Width = 120
    $HRZForm.Controls.Add($L3BPM)

    $L4Label = New-Object System.Windows.Forms.Label
    $L4Label.Text = "Level 4 (anaerobic threshold)  81-90% Intensity"
    $L4Label.Top = 470
    $L4Label.Left = 40
    $L4Label.Width = 340
    $HRZForm.Controls.Add($L4Label)

    $L4BPM = New-Object System.Windows.Forms.Label
    $L4BPM.Text = ""
    $L4BPM.Top = 470
    $L4BPM.Left = 380
    $L4BPM.Width = 120
    $HRZForm.Controls.Add($L4BPM)

    $L5Label = New-Object System.Windows.Forms.Label
    $L5Label.Text = "Level 5 (peaking / racing)  91-100% Intensity"
    $L5Label.Top = 500
    $L5Label.Left = 40
    $L5Label.Width = 340
    $HRZForm.Controls.Add($L5Label)

    $L5BPM = New-Object System.Windows.Forms.Label
    $L5BPM.Text = ""
    $L5BPM.Top = 500
    $L5BPM.Left = 380
    $L5BPM.Width = 120
    $HRZForm.Controls.Add($L5BPM)

    $RestingFlag = New-Object Windows.Forms.Label
    $RestingFlag.Text = "<= Fix !!"
    $RestingFlag.ForeColor = "Red"
    $RestingFlag.Top = 50
    $RestingFlag.Left = 460
    $HRZForm.Controls.Add($RestingFlag)

    $MaxFlag = New-Object Windows.Forms.Label
    $MaxFlag.Text = "<= Fix !!"
    $MaxFlag.ForeColor = "Red"
    $MaxFlag.Top = 80
    $MaxFlag.Left = 460
    $HRZForm.Controls.Add($MaxFlag)

    $AgeFlag = New-Object Windows.Forms.Label
    $AgeFlag.Text = "<= Fix !!"
    $AgeFlag.ForeColor = "Red"
    $AgeFlag.Top = 260
    $AgeFlag.Left = 460
    $HRZForm.Controls.Add($AgeFlag)

    $FHelpLabel = New-Object Windows.Forms.Label
    $FHelpLabel.Text = "Select a formula and click here for help  >>"
    $FHelpLabel.Top = 230
    $FHelpLabel.Left = 120
    $FHelpLabel.Width = 300
    $HRZForm.Controls.Add($FHelpLabel)

    $FHelp = New-Object Windows.Forms.Button
    $FHelp.Text = "Formula Help"
    $FHelp.Top = 230
    $FHelp.Left = 420
    $FHelp.Width = 110
    $FHelp.Add_Click({
        # The user clicked the Formula Help button.
        Get-Help "$Script:Formula"
    }) # End of code for the Formula Help button, $FHelp.Add_Click.
    $HRZForm.Controls.Add($FHelp)

    $AllHelp = New-Object Windows.Forms.Button
    $AllHelp.Text = "Compare"
    $AllHelp.Top = 140
    $AllHelp.Left = 450
    $AllHelp.Width = 80
    $AllHelp.Add_Click({
        # The user clicked the Compare button.
        Get-Help "All Formulas"
    }) # End of code for the Formula Help button, $AllHelp.Add_Click.
    $HRZForm.Controls.Add($AllHelp)

    $Cancel = New-Object System.Windows.Forms.Button
    $Cancel.Text = "Cancel"
    $Cancel.Top = 540
    $Cancel.Left = 420
    $Cancel.Width = 80
    $Cancel.Add_Click({
        # The user clicked the Cancel button.
        $HRZForm.DialogResult = "Cancel"
        $HRZForm.Close()
    }) # End of code for Cancel button, $Cancel.Add_Click.
    $HRZForm.Controls.Add($Cancel)

    $Report = New-Object System.Windows.Forms.Button
    $Report.Text = "Exit/Report"
    $Report.Top = 540
    $Report.Left = 210
    $Report.Width = 110
    $Report.Add_Click({
        # The user clicked the Exit/Report button. Only exit if all values on the form
        # are valid.
        $Status = Validate-Form

        If ($Status -eq $True)
        {
            $HRZForm.DialogResult = "OK"
            $HRZForm.Close()
        }

    }) # End of code for the Exit/Report button, $Report.Add_Click.
    $HRZForm.Controls.Add($Report)

    $Calculate = New-Object System.Windows.Forms.Button
    $Calculate.Text = "Calculate"
    $Calculate.Top = 540
    $Calculate.Left = 30
    $Calculate.Width = 80
    $Calculate.Add_Click({
        # The user clicked the Calculate button. Refresh the form.

        # Retrieve values from the form.
        $RestingHR = [Int16]$Resting.Text
        $MaxHR = [Int16]$Max.Text
        $Age = [Int16]$AgeBox.Text
        If ($EstimateChk.CheckState -eq "Checked") {$Estimate = $True}
        Else {$Estimate = $False}

        $Status = Validate-Form

        If (-Not $RestingHR)
        {
            $RestingLabel.ForeColor = "Red"
            $RestingFlag.Visible = $True
            $Report.Visible = $False
        }

        If ((-Not $Estimate) -And (-Not $MaxHR))
        {
            $MaxLabel.ForeColor = "Red"
            $MaxFlag.Visible = $True
            $Report.Visible = $False
        }

        If ($Estimate)
        {
            If (-Not $Age)
            {
                $AgeLabel.ForeColor = "Red"
                $AgeFlag.Visible = $True
                $Report.Visible = $False
            }
        }
        # Invoke the Down Arrow key to move the focus off of the Calculate button.
        # This allows the script to recognize when the user presses the Enter key.
        [System.Windows.Forms.SendKeys]::SendWait("{DOWN}")

    }) # End of code for the Calculate button, $Calculate.Add_Click.
    $HRZForm.Controls.Add($Calculate)

    $EstimateChk = New-Object System.Windows.Forms.CheckBox
    $EstimateChk.Top = 110
    $EstimateChk.Left = 420
    $EstimateChk.Add_Click({
        # Refresh the form.
        $Status = Validate-Form
        If ($EstimateChk.CheckState -eq "Checked")
        {
            # The user checked the Estimate check box.
            # Show only the relevant controls on the form.
            $MaxLabel.Visible = $False
            $Max.Visible = $False
            $MaxFlag.Visible = $False
            $AgeLabel.Visible = $True
            $AgeBox.Visible = $True
            $FormulaLabel.Visible = $True
            $Formula1.Visible = $True
            $Formula2.Visible = $True
            $Formula3.Visible = $True
            $Formula4.Visible = $True
            $Formula5.Visible = $True
            $Formula6.Visible = $True
            $Formula7.Visible = $True
            $Formula8.Visible = $True
        }
        Else
        {
            # The user unchecked the Estimate check box.
            # Show only the relevant controls on the form.
            $MaxLabel.Visible = $True
            $Max.Visible = $True
            $AgeLabel.Visible = $False
            $AgeBox.Visible = $False
            $FormulaLabel.Visible = $False
            $Formula1.Visible = $False
            $Formula2.Visible = $False
            $Formula3.Visible = $False
            $Formula4.Visible = $False
            $Formula5.Visible = $False
            $Formula6.Visible = $False
            $Formula7.Visible = $False
            $Formula8.Visible = $False
        }
    }) # End of code for the EstimateChk checkbox, $EstimateChk.Add_Click.
    $HRZForm.Controls.Add($EstimateChk)

    # Retrieve parameters from the command line and populate the form.

    # $Formula must have Script scope in PowerShell Version 5, so the value
    # can be assigned by the RadioButtons.
    If (-Not $Script:Formula) {$Script:Formula = "Oakland1"}

    # Select formula for estimated maximum heart rate.
    Switch ($Script:Formula)
    {
        "Oakland1" {$Formula1.Checked = $True}
        "Oakland2" {$Formula2.Checked = $True}
        "Haskell" {$Formula3.Checked = $True}
        "Cooper" {$Formula4.Checked = $True}
        "Tanaka" {$Formula5.Checked = $True}
        "Gellish" {$Formula6.Checked = $True}
        "Robergs" {$Formula7.Checked = $True}
        "Nes" {$Formula8.Checked = $True}
        Default
        {
            Write-Host "Formula for estimating maximum heart rate ($Script:Formula) not" `
            "recognized. Default to Oakland1." -ForegroundColor Red -BackgroundColor Black
            $Script:Formula = "Oakland1"
            $Formula1.Checked = $True
        }
    }

    If ($RestingHR) {$Resting.Text = $RestingHR}
    Else {$Resting.Text = ""}

    If ($MaxHR) {$Max.Text = $MaxHR}
    Else {$Max.Text = ""}

    If ($Estimate) {$EstimateChk.CheckState = "Checked"}
    Else
    {
        If ((-Not $MaxHR) -And $Age)
        {
            $EstimateChk.CheckState = "Checked"
            $Estimate = $True
        }
    }

    If ($Age) {$AgeBox.Text = $Age}
    Else {$AgeBox.Text = ""}

    # Validate the values on the form.
    $Status = Validate-Form

    $HRZForm.Add_Load({
        $HRZForm.Activate()
    })

    # Display the form to the user.
    $Result = $HRZForm.ShowDialog()

    If ($Result -eq "Cancel")
    {
        # Cancel button clicked.
        Write-Host "Script canceled" -ForegroundColor Red -BackgroundColor Black
        Break
    }

    If ($Result -eq "OK")
    {
        # Exit/Report button clicked.
        # Retrieve values from the form.
        $RestingHR = [Int16]$Resting.Text
        $MaxHR = [Int16]$Max.Text
        $Age = [Int16]$AgeBox.Text
        If ($EstimateChk.CheckState -eq "Checked") {$Estimate = $True}
        Else {$Estimate = $False}
    }
} # End of code when PowerShell is not constrained.

# User has exited from the form or supplied all values at the command line. Output results.

# Standardize the case of the Formula.
# $Formula must have Script scope in PowerShell Version 5, so the value
# can be assigned by the RadioButtons.
If ($Script:Formula)
{
    $Script:Formula = $Script:Formula.SubString(0, 1).ToUpper() `
        + $Script:Formula.SubString(1).ToLower()
}

If ($Estimate)
{
    $MaxHR = Estimate-MaxHR $Age $Script:Formula
}

# Calculate the training heart rate zones.
$L = Calculate-Zones $RestingHR $MaxHR

# Display results.
If ($Black)
{
    # Output without using the Write-Host cmdlet so the output can be redirected to a text file.
    "HRZones.ps1"
    "$Version"
    If ($Age) {"Age: $Age"}
    "Resting heart rate: $RestingHR bpm"
    If ($Estimate -eq $True) {"Maximum heart rate: $MaxHR bpm (estimate, $Script:Formula)"}
    Else {"Maximum heart rate: $MaxHR bpm"}
    "Calculated training heart rate zones ($Today):"
    "Level  Description             Intensity    bpm"
    "-----  ----------------------  ---------  ----------"
    "       warmup                   50-60%    " + $L[0] + " - " + $L[1]
    "   1   over distance            61-70%    " + $L[2] + " - " + $L[3]
    "   2   endurance / easy speed   71-75%    " + $L[4] + " - " + $L[5]
    "   3   endurance / long races   76-80%    " + $L[6] + " - " + $L[7]
    "   4   anaerobic threshold      81-90%    " + $L[8] + " - " + $L[9]
    "   5   peaking / racing         91-100%   " + $L[10] + " - " + $L[11]
}
Else
{
    # Output using the Write-Host cmdlet and specify colors for emphasis.
    Write-Host "HRZones.ps1" -ForegroundColor green -BackgroundColor Black
    Write-Host "$Version" -ForegroundColor green -BackgroundColor Black
    If ($Age) {Write-Host "Age: $Age" -ForegroundColor green -BackgroundColor Black}
    Write-Host "Resting heart rate: $RestingHR bpm" `
        -ForegroundColor green -BackgroundColor Black
    If ($Estimate -eq $True) {Write-Host "Maximum heart rate: $MaxHR bpm" `
        "(estimate, $Script:Formula)" -ForegroundColor green -BackgroundColor Black}
    Else {Write-Host "Maximum heart rate: $MaxHR bpm" `
        -ForegroundColor green -BackgroundColor Black}
    Write-Host "Calculated training heart rate zones ($Today):" `
        -ForegroundColor Yellow -BackgroundColor Black
    Write-Host "Level  Description             Intensity    bpm" `
        -ForegroundColor Yellow -BackgroundColor Black
    Write-Host "-----  ----------------------  ---------  ---------" `
        -ForegroundColor Yellow -BackgroundColor Black
    Write-Host "       warmup                   50-60%   ", $L[0], "-", $L[1] `
        -ForegroundColor Yellow -BackgroundColor Black
    Write-Host "   1   over distance            61-70%   ", $L[2], "-", $L[3] `
        -ForegroundColor Yellow -BackgroundColor Black
    Write-Host "   2   endurance / easy speed   71-75%   ", $L[4], "-", $L[5] `
        -ForegroundColor Yellow -BackgroundColor Black
    Write-Host "   3   endurance / long races   76-80%   ", $L[6], "-", $L[7] `
        -ForegroundColor Yellow -BackgroundColor Black
    Write-Host "   4   anaerobic threshold      81-90%   ", $L[8], "-", $L[9] `
        -ForegroundColor Yellow -BackgroundColor Black
    Write-Host "   5   peaking / racing         91-100%  ", $L[10], "-", $L[11] `
        -ForegroundColor Yellow -BackgroundColor Black
}