<#
  .SYNOPSIS
  Executes RADE EOO test of FreeDV.

  .DESCRIPTION
  This script starts FreeDV in TX mode for approximately 5 seconds using an autogenerated configuration file
  that will access the audio devices passed in. The audio output from TX is saved to a temporary file using the SoX
  tool. After 5 seconds, FreeDV will terminate and restart in RX mode using the recorded temporary file. This script 
  will examine the output to determine whether it is able to properly decode the test callsign. If the callsign
  does not appear in the logs, the test is marked as having failed.

  .INPUTS
  None. You can't pipe objects to this script.

  .OUTPUTS
  The script outputs test status to the console.

  .EXAMPLE
  PS> .\TestFreeDVReporting.ps1 -RadioToComputerDevice "Microphone (USB Audio CODEC)" -ComputerToRadioDevice "Speakers (USB Audio CODEC)" -ComputerToSpeakerDevice "Speakers (Realtek High Definition Audio(SST))" -MicrophoneToComputerDevice "Microphone Array (Realtek High Definition Audio(SST))"

#>

param (
    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [string] 
    # The sound device to receive RX audio from.
    $RadioToComputerDevice, 

    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [string] 
    # The sound device to emit decoded audio to.
    $ComputerToSpeakerDevice, 

    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [string] 
    # The sound device to receive analog audio from.
    $MicrophoneToComputerDevice, 

    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [string] 
    # The sound device to emit TX audio to.
    $ComputerToRadioDevice)

<#
    .Description
    Performs the actual test with FreeDV by generating the needed configuration file, starting FreeDV and then examining the output.
#>
function Test-FreeDV {
    param (
        $RadioToComputerDevice,
        $ComputerToSpeakerDevice,
        $MicrophoneToComputerDevice,
        $ComputerToRadioDevice
    )

    $current_loc = Get-Location

    # Generate new conf 
    $conf_tmpl = Get-Content "$current_loc\freedv-ctest-reporting.conf.tmpl"
    $conf_tmpl = $conf_tmpl.Replace("@FREEDV_RADIO_TO_COMPUTER_DEVICE@", $RadioToComputerDevice)
    $conf_tmpl = $conf_tmpl.Replace("@FREEDV_COMPUTER_TO_RADIO_DEVICE@", $ComputerToRadioDevice)
    $conf_tmpl = $conf_tmpl.Replace("@FREEDV_MICROPHONE_TO_COMPUTER_DEVICE@", $MicrophoneToComputerDevice)
    $conf_tmpl = $conf_tmpl.Replace("@FREEDV_COMPUTER_TO_SPEAKER_DEVICE@", $ComputerToSpeakerDevice)
    $tmp_file = New-TemporaryFile
    $conf_tmpl | Set-Content -Path $tmp_file.FullName
    
    # Start SoX
    $soxPsi = New-Object System.Diagnostics.ProcessStartInfo
    $soxPsi.CreateNoWindow = $true
    $soxPsi.UseShellExecute = $false
    $soxPsi.RedirectStandardError = $false
    $soxPsi.RedirectStandardOutput = $false
    $soxPsi.FileName = "sox.exe"
    $soxPsi.WorkingDirectory = $current_loc
    $quoted_device = "`"" + $RadioToComputerDevice + "`""
    $soxPsi.Arguments = @("-t waveaudio $quoted_device -c 1 -r 8000 -t wav `"$current_loc\test.wav`"")
    
    $soxProcess = New-Object System.Diagnostics.Process
    $soxProcess.StartInfo = $soxPsi
    [void]$soxProcess.Start()
    
    # Start mock rigctld
    $rigctlPsi = New-Object System.Diagnostics.ProcessStartInfo
    $rigctlPsi.CreateNoWindow = $true
    $rigctlPsi.UseShellExecute = $false
    $rigctlPsi.RedirectStandardError = $true
    $rigctlPsi.RedirectStandardOutput = $true
    $rigctlPsi.FileName = "$current_loc\python.exe"
    $rigctlPsi.WorkingDirectory = $current_loc
    $quoted_tmp_filename = "`"" + "hamlibserver.py" + "`""
    $rigctlPsi.Arguments = @("$quoted_tmp_filename " + $soxProcess.Id)
    
    $rigctlProcess = New-Object System.Diagnostics.Process
    $rigctlProcess.StartInfo = $rigctlPsi
    [void]$rigctlProcess.Start()
    
    # Start freedv.exe
    $psi = New-Object System.Diagnostics.ProcessStartInfo
    $psi.CreateNoWindow = $true
    $psi.UseShellExecute = $false
    $psi.RedirectStandardError = $true
    $psi.RedirectStandardOutput = $true
    $psi.FileName = "$current_loc\freedv.exe"
    $psi.WorkingDirectory = $current_loc
    $quoted_tmp_filename = "`"" + $tmp_file.FullName + "`""
    $psi.Arguments = @("/f $quoted_tmp_filename /ut tx /utmode RADEV1 /txtime 5")

    $process = New-Object System.Diagnostics.Process
    $process.StartInfo = $psi
    [void]$process.Start()

    # Read output from first FreeDV run
    $err_output = $process.StandardError.ReadToEnd();
    $output = $process.StandardOutput.ReadToEnd();
    $process.WaitForExit()

    Write-Host "$err_output"
    
    # Stop recording audio
    try {
        $soxProcess.Kill()
    } catch {
        # Ignore failure as Python could have killed sox
    }
    $soxProcess.WaitForExit()
    
    # Restart FreeDV in RX mode
    $psi.Arguments = @("/f $quoted_tmp_filename /ut rx /utmode RADEV1 /rxfile `"$current_loc\test.wav`"")

    $process = New-Object System.Diagnostics.Process
    $process.StartInfo = $psi
    [void]$process.Start()
    
    # Read output from second FreeDV run
    $err_output_fdv = $process.StandardError.ReadToEnd()
    $output = $process.StandardOutput.ReadToEnd()
    $process.WaitForExit()

    Write-Host "$err_output_fdv"
    
    # Kill mock rigctld
    $rigctlProcess.Kill()
    $err_output = $rigctlProcess.StandardError.ReadToEnd()
    $output = $rigctlProcess.StandardOutput.ReadToEnd()
    $rigctlProcess.WaitForExit()

    Write-Host "$err_output"

    # Check for RX callsign
    $syncs = ($err_output_fdv -split "`r?`n") | Where { $_.Contains("Reporting callsign ZZ0ZZZ @ SNR") }
    if ($syncs.Count -eq 1) {
        return $true
    }
    return $false
}

$passes = 0
$fails = 0

$result = Test-FreeDV `
    -RadioToComputerDevice $RadioToComputerDevice `
    -ComputerToSpeakerDevice $ComputerToSpeakerDevice `
    -MicrophoneToComputerDevice $MicrophoneToComputerDevice `
    -ComputerToRadioDevice $ComputerToRadioDevice
if ($result -eq $true)
{
    $passes++
}
else
{
    $fails++
}

Write-Host "Mode: RADEV1, Passed: $passes, Failures: $fails"

if ($fails -gt 0) {
    throw "Test failed"
    exit 1
}

