<#
.SYNOPSIS
  Modern Standby Performance Assessment

.DESCRIPTION
  Automates Modern Standby power transitions and measures entry/exit latency metrics

.PARAMETER iterations
    Number of Modern Standby transitions to automate
.PARAMETER sleeptimeout_sec
    Duration in seconds of the Modern Standby session
.PARAMETER postdelay_sec
    Delay in seconds between iterations
.PARAMETER NetworkingState
    Disconnected standby
.PARAMETER disablequiethours
    Disable the OS Quiet Hours feature before entering Modern Standby
.PARAMETER modeaggressive
    Enable aggressive mode during disconnected standby
.PARAMETER simulatedbatteries
    Simulate batteries without unplugging the AC power
.PARAMETER enablepowerlogs
    Select to gather additional power related log files for diagnostics
.PARAMETER traceprofile
    Alternate WPRP tracing profile
.PARAMETER ExecuteOnly
    Only execute the workload without analysis
.PARAMETER AnalyzeOnly
    Only analyze pre-existing ETL traces
.PARAMETER DebugFlag
    Pause at the end of the script to allow diagnostics of console output
.PARAMETER IncludeAnalysisIteration
    Add one analysis iteration after the timing iterations are completed
.PARAMETER ConfigJson
    Alternate JSON configuration file
#>
Param( 
    [parameter(Mandatory=$false)][int32]$iterations,
    [parameter(Mandatory=$false)][int32]$sleeptimeout_sec,
    [parameter(Mandatory=$false)][int32]$postdelay_sec,
    [parameter(Mandatory=$false)][int32]$NetworkingState,
    [parameter(Mandatory=$false)][switch]$disablequiethours,
    [parameter(Mandatory=$false)][switch]$modeaggressive,
    [parameter(Mandatory=$false)][switch]$simulatedbatteries,
    [parameter(Mandatory=$false)][switch]$enablepowerlogs,
    [parameter(Mandatory=$false)][string]$traceprofile,
    [parameter(Mandatory=$false)][switch]$ExecuteOnly,
    [parameter(Mandatory=$false)][switch]$AnalyzeOnly,
    [parameter(Mandatory=$false)][switch]$DebugFlag,
    [parameter(Mandatory=$false)][switch]$IncludeAnalysisIteration,
    [parameter(Mandatory=$true)][string]$ConfigJson
)
try
{
    $SimBattInstalled = $false
    $SimBattDisableRealBatteries = $false
    $SimBattToDC = $false

    # Need to know where our script is so we can use full path to all our dependent files.
    $InvocationPath = Split-Path -Path $MyInvocation.MyCommand.Path -Parent

    # Configuration JSON file
    try {
        $IniJSON =  Get-Content -Raw -Path $ConfigJson | ConvertFrom-Json
    }
    catch {
        Throw "Unable to load JSON config file ($ConfigJson)"
    }

    #subfolders
    $ToolsPath = $InvocationPath
    $ModulesPath = Join-Path -path $InvocationPath modules

     #Import AXE support helper module
    Import-Module "$ModulesPath\CommonPS.psm1" -ErrorAction Stop
    Import-Module "$ModulesPath\AxePSHelper.psm1" -ErrorAction Stop
    Import-Module "$ModulesPath\EtwPSHelper.psm1" -ErrorAction Stop

    #Import simulate batteries module
    Import-Module "$ModulesPath\SimBattPSHelper.psm1" -ErrorAction Stop

    # Import powershell helper module
    Import-Module "$ModulesPath\PowerLogsPSHelper.psm1" -ErrorAction Stop

    # Proc architecture
    $arch = Get-ProcessorArchitecture
    $realarch = Get-Real-ProcessorArchitecture

    # Architecture dependent resources
    $archtools = Join-Path -path $ToolsPath $arch
    $WPTPath = $archtools
    $env:path = "$WPTPath;" + $env:path

    $StabDelay = $IniJSON.Execution.StabilizationDelaySec

    ######################################
    #
    #   Execution
    #
    ######################################

    if ($ExecuteOnly)
    {
        # Path for process execution logs (err and std outputs)
        $LogsPath = join-path -path $AXEResultsPath logs
        New-Item $LogsPath -ItemType directory > $null

        try
        {
            if ($simulatedbatteries)
            {
                # change directory to make SimBattCtl.exe happy
                Set-Location -Path "$InvocationPath\$realarch"
                # Install simulated batteries
                "Install simulated batteries" | Out-AxeInfo
                Invoke-SimBattCtl -StageMessage "Install simulated batteries" `
                    -Option "-i" -LogsPath "$LogsPath"
                $SimBattInstalled = $true

                # Disable all real batteries
                "Disable all real batteries" | Out-AxeInfo
                Invoke-SimBattCtl -StageMessage "Disable all real batteries" `
                    -Option "-x" -LogsPath "$LogsPath"
                $SimBattDisableRealBatteries = $true

                # Set simulated batteries to DC power
                "Set simulated batteries to DC power" | Out-AxeInfo
                Invoke-SimBattCtl -StageMessage "Set simulated batteries to DC power" `
                    -Option "-d" -LogsPath "$LogsPath"
                $SimBattToDC = $true
            }

            # Start tracing
            "Start tracing for timing iterations..." | Out-AxeInfo
            Start-ETWTrace -WprpFile "$InvocationPath\$($IniJSON.Analysis.WPRP)" `
                -ProfileName $IniJSON.Analysis.TracingProfile `
                -Filemode `
                -LogPath $LogsPath

            "Sleeping $StabDelay seconds for stabilization" | Out-AxeInfo 
            start-sleep -Seconds $StabDelay

            # Launch the ConnectedStandby automation binary
            "Launching ModernStandby automation for $iterations iterations" | Out-AxeInfo
            Start-Process "$InvocationPath\$realarch\ConnectedStandby.exe" `
                -ArgumentList "/sleeptimeout_sec $sleeptimeout_sec /iterations $iterations /postonoffsec $postdelay_sec /NetworkingState $NetworkingState /axebinpath `"$env:AssessmentAxeBinPath`" $(if($disablequiethours) {"/disablequiethours"}) $(if($modeaggressive) {"/modeaggressive"}) " `
                -Wait `
                -RedirectStandardError "$LogsPath\ms_timing_err.txt" `
                -RedirectStandardOutput "$LogsPath\ms_timing_std.txt" `
                -NoNewWindow

            # Stop tracing
            "Saving ETL trace" | Out-AxeInfo 
            Stop-ETWTrace -ETLName $IniJSON.TimingTraceName -LogPath $AXEResultsPath

            # Generate analysis trace, if selected
            if ($IncludeAnalysisIteration)
            {
                # Start tracing
                "Start tracing for analysis iterations..." | Out-AxeInfo
                Start-ETWTrace -WprpFile "$InvocationPath\$($IniJSON.Analysis.WPRP)" `
                    -ProfileName $IniJSON.Analysis.TracingProfile `
                    -Filemode `
                    -VerboseMode `
                    -LogPath $LogsPath

                "Sleeping $StabDelay seconds for stabilization" | Out-AxeInfo
                start-sleep -Seconds $StabDelay

                # Launch the ConnectedStandby automation binary
                "Launching ModernStandby automation for $($IniJSON.Execution.AnalysisIterationCount) iterations" | Out-AxeInfo
                Start-Process "$InvocationPath\$realarch\ConnectedStandby.exe" `
                    -ArgumentList "/sleeptimeout_sec $sleeptimeout_sec /iterations $($IniJSON.Execution.AnalysisIterationCount) /postonoffsec $postdelay_sec /NetworkingState $NetworkingState /axebinpath `"$env:AssessmentAxeBinPath`" $(if($disablequiethours) {"/disablequiethours"}) $(if($modeaggressive) {"/modeaggressive"}) " `
                    -Wait `
                    -RedirectStandardError "$LogsPath\ms_analysis_err.txt" `
                    -RedirectStandardOutput "$LogsPath\ms_analysis_std.txt" `
                    -NoNewWindow

                # Stop tracing
                "Saving ETL trace" | Out-AxeInfo
                Stop-ETWTrace -ETLName $IniJSON.AnalysisTraceName -LogPath $AXEResultsPath
            }
        }
        catch
        {
            throw
        }
        finally
        {
            # Uninstall simulated batteries before starting power logs to avoid
            # uninstallation (of simulated batteries) failure
            if ($SimBattToDC)
            {
                # Set simulated batteries to AC power
                "Set simulated batteries to AC power" | Out-AxeInfo
                $err = Invoke-SimBattCtl -StageMessage "Set simulated batteries to AC power" `
                    -Option "-a" -LogsPath "$LogsPath" `
                    -ThrowOnError $false
                if ($err.length -ne 0)
                {
                    "$err" | Out-AxeInfo
                }
            }

            if ($SimBattDisableRealBatteries)
            {
                # Enable all real batteries
                "Enable all real batteries" | Out-AxeInfo
                $err = Invoke-SimBattCtl -StageMessage "Enable all real batteries" `
                    -Option "-e" -LogsPath "$LogsPath" `
                    -ThrowOnError $false
                if ($err.length -ne 0)
                {
                    "$err" | Out-AxeInfo
                }
            }

            if ($SimBattInstalled)
            {
                # Uninstall simulated batteries
                "Uninstall simulated batteries" | Out-AxeInfo
                $err = Invoke-SimBattCtl -StageMessage "Uninstall simulated batteries" `
                    -Option "-u" -LogsPath "$LogsPath" `
                    -ThrowOnError $false
                if ($err.length -ne 0)
                {
                    "$err" | Out-AxeInfo
                }
            }
        }

        # Enable power logs
        if ($enablepowerlogs)
        {
            "Start Power Logs" | Out-AxeInfo
            # Path for power logs
            $PowerLogsPath = join-path -path $AXEResultsPath powerlogs
            if (-not (Test-Path "$PowerLogsPath" -pathType container))
            {
                New-Item $PowerLogsPath -ItemType directory > $null
            }
            Start-PowerLogs -PowerLogsPath $PowerLogsPath
        }
    }


    ######################################
    #
    # Analysis
    #
    ######################################

    if ($AnalyzeOnly)
    {
        # Path for process execution logs (err and std outputs)
        $LogsPath = join-path -path $AXEResultsPath logs
        New-Item $LogsPath -ItemType directory > $null

        $TimingETL = "$AXEExecutionPath\$($IniJSON.TimingTraceName).etl"

        # Verify we can find the ETL
        if (-not (Test-Path $TimingETL -PathType Leaf))
        {
            throw [System.IO.FileNotFoundException] "$TimingETL not found."
        }


        # Export regions of interest to CSV using WPAExporter tool
        "Running WPAExporter to export regions of interests..." | Out-AxeInfo 
        Start-Process "$WPTPath\wpaexporter.exe" `
            -ArgumentList "-i `"$TimingETL`" -profile `"$InvocationPath\$($IniJSON.Analysis.WPAProfile)`" -outputfolder `"$AXEResultsPath`"" `
            -Wait `
            -RedirectStandardError "$LogsPath\wpaexport_err.txt" `
            -RedirectStandardOutput "$LogsPath\wpaexport_std.txt" `
            -NoNewWindow
        
        # Verify we can find the CSV
        $RegionsCSV = "$AXEResultsPath\$($IniJSON.Analysis.RegionsCSV)"
        if (-not (Test-Path $RegionsCSV -PathType Leaf))
        {
            throw [System.IO.FileNotFoundException] "$RegionsCSV not found."
        }

        # Export SleepStudySessionSummary to CSV using WPAExporter tool
        "Running WPAExporter to export SleepStudySessionSummary..." | Out-AxeInfo
        Start-Process "$WPTPath\wpaexporter.exe" `
            -ArgumentList "-i `"$AXEExecutionPath\$($IniJSON.AnalysisTraceName).etl`" -profile `"$InvocationPath\$($IniJSON.Analysis.WPAProfileSummary)`" -outputfolder `"$AXEResultsPath`"" `
            -Wait `
            -RedirectStandardError "$LogsPath\wpaexport_err.txt" `
            -RedirectStandardOutput "$LogsPath\wpaexport_std.txt" `
            -NoNewWindow

         # Verify we can find the CSV
        $SummaryCSV = "$AXEResultsPath\$($IniJSON.Analysis.SummaryCSV)"
        if (-not (Test-Path $SummaryCSV -PathType Leaf))
        {
            throw [System.IO.FileNotFoundException] "$SummaryCSV not found."
        }

        # Import CSV and rename header columns (to avoid spaces and special characters)
        "Importing SleepStudySessionSummary csv file..." | Out-AxeInfo
        $resultsSummary = @(Import-Csv -Path $SummaryCSV -Header taskName, dripsPercentage, hwDripsPercentage, csExitLatencyInUs, time | Select -Skip 1)

        # Count number of Modern Standby sessions in the ETL
        $SummaryCount = $resultsSummary.count
        "SummaryCount = $SummaryCount" | Out-AxeInfo

        # Import CSV and rename header columns (to avoid spaces and special characters)
        "Importing regions of interest file..." | Out-AxeInfo 
        $results = @(Import-Csv -Path $RegionsCSV -Header Region, Name, Instance, Duration | Select -Skip 1)

        # Count number of Modern Standby sessions in the ETL
        $SessionCount = (@($results | where {$_.Name -eq "$($IniJSON.Analysis.SessionRootRegion)"})).count
        "Found $SessionCount Modern Standby sessions in ETL ..." | Out-AxeInfo 

        # Start generating the results XML
        "Converting regions of interests to XML results file..." | Out-AxeInfo 
        $XMLHelper = Get-AxeResultSnippet

        # For test iteration count and analysis iteration count are different condition
        $MaxCount = $SessionCount
        if ($MaxCount -lt $SummaryCount)
        {
            $MaxCount = $SummaryCount
        }

        for($i=1 ; $i -le $MaxCount ; $i++)
        {
            "Processing iteration #$i..." | Out-AxeInfo 

            # Add new iteration to XML
            $XMLIteration = $XMLHelper.AddIteration()
            $XMLIteration.Ordinal = $i - 1

            # Add [SleepStudySessionSummary] result to XML
            if ($i -le $SummaryCount)
            {
                $Metric_csExitLatencyInUs = [uint64] ($resultsSummary[$i - 1].csExitLatencyInUs)
                $Metric_dripsPercentage = [uint64] ($resultsSummary[$i - 1].dripsPercentage)
                $Metric_hwDripsPercentage = [uint64] ($resultsSummary[$i - 1].hwDripsPercentage)
                $Metric_deltaDripsPercentage = [uint64]([math]::abs([int64]$Metric_dripsPercentage - [int64]$Metric_hwDripsPercentage))

                "Metric_csExitLatencyInUs = $Metric_csExitLatencyInUs" | Out-AxeInfo
                "Metric_dripsPercentage = $Metric_dripsPercentage" | Out-AxeInfo
                "Metric_hwDripsPercentage = $Metric_hwDripsPercentage" | Out-AxeInfo
                "Metric_deltaDripsPercentage = $Metric_deltaDripsPercentage" | Out-AxeInfo

                # Add new iteration for SleepStudySessionSummary to XML
                $XMLIteration.AddMetricValue("CSExitLatency", $Metric_csExitLatencyInUs)
                $XMLIteration.AddMetricValue("SoftwareDrips", $Metric_dripsPercentage)
                $XMLIteration.AddMetricValue("HardwareDrips", $Metric_hwDripsPercentage)
                $XMLIteration.AddMetricValue("SwHwDripsDelta", $Metric_deltaDripsPercentage)

                # Write checking fail results to results.xml file
                #$ErrorCode = [int32]0x80004005  #E_FAIL
                $ErrorCode = [uint32][Convert]::ToUInt32("0x80004005", 16)

                if ($Metric_csExitLatencyInUs -ge  $IniJSON.Analysis.MaxExitLatencyInUs)
                {
                    $CheckingValue = $IniJSON.Analysis.MaxExitLatencyInUs
                    $ErrorMessage = "Time to exit Modern Standby state value $Metric_csExitLatencyInUs (us) is more than $CheckingValue (us)."
                    $XMLHelper.AddError($ErrorCode, $ErrorMessage)
                }

                if ($Metric_dripsPercentage -lt  $IniJSON.Analysis.MinSwDripsPercentage)
                {
                    $CheckingValue = $IniJSON.Analysis.MinSwDripsPercentage
                    $ErrorMessage = "Software DRIPS value $Metric_dripsPercentage% is less than $CheckingValue%."
                    $XMLHelper.AddError($ErrorCode, $ErrorMessage)
                }

                if ($Metric_deltaDripsPercentage -gt  $IniJSON.Analysis.MaxDeltaDripsPercentage)
                {
                    $CheckingValue = $IniJSON.Analysis.MaxDeltaDripsPercentage
                    $ErrorMessage = "Delta between software and hardware DRIPS residency value $Metric_hwDripsPercentage% is more than $CheckingValue%."
                    $XMLHelper.AddError($ErrorCode, $ErrorMessage)
                }
            }


            # Add [Session Duration] and [Modern Standby Phases] results to xml
            if ($i -le $SessionCount)
            {
                # Add [Session Duration] result
                $sessionDurationValue = [int]0
                $regionName = "$($IniJSON.Analysis.SessionRootRegion): [$i]"
                $regionNameList = @($results | where { ($_.Region -eq $regionName) })
                if ($regionNameList.Count -gt 0)   # $regionName is found
                {
                    $sessionDurationValue = [int]($regionNameList[0].Duration)
                }
                else  # Try to get a row without iteration count in [Region] column.  Because [Instance Name] column (iteration count) is null if only executing 1 iteration test.
                {
                  $regionName = "$($IniJSON.Analysis.SessionRootRegion)"
                  $regionNameList = @($results | where { ($_.Region -eq $regionName) })
                  if ($regionNameList.Count -gt 0)
                  {
                      $sessionDurationValue = [int]($regionNameList[0].Duration)
                  }
                }
                "Region Name and duration value = $regionName, $sessionDurationValue" | Out-AxeInfo
                $XMLIteration.AddMetricValue("SessionDuration", $sessionDurationValue)

                #Add [Modern Standby Phases] result
                $RootTokenCount = (($IniJSON.Analysis.SessionRootRegion).Split("/")).Count
                "RootTokenCount = $RootTokenCount" | Out-AxeInfo

                # Find all test cases for the current iteration
                $SessionTestCases = @(($results | where { $_.Region.StartsWith($regionName) }) | Sort-Object Region)

                $TestCasesCount = $SessionTestCases.Count
                "TestCases count = $TestCasesCount" | Out-AxeInfo

                if ($TestCasesCount -gt 0)
                {
                    foreach ($case in $SessionTestCases)
                    {
                        $tokenList = $case.Name.Split("/")
                        $tokenCount = $tokenList.Count
                        "tokenList count = $tokenCount" | Out-AxeInfo

                        if ($tokenCount -gt $RootTokenCount)
                        {
                            $nodeName = $tokenList[$tokenCount - 1]

                            # Add TestCase to XML
                            $XMLTestCase = $XMLIteration.AddTestCase()
                            $XMLTestCase.Key = $nodeName
                            $XMLTestCase.Name = $nodeName
                            $XMLTestCase.AddParent("Phases")
                            $XMLTestCase.AddMetricValue("Duration_ms", [int]($case.Duration))

                            if (($tokenCount - 1) -gt $RootTokenCount)
                            {
                                $XMLTestCase.AddParent($tokenList[$tokenCount - 2])
                            }
                        }
                    }
                }
                else
                {
                    throw "Unable to find test cases for iteration $i"
                }
            }

        }

        # Save XML
        "Saving XML results file to $AXEResultsPath\results.xml ..." | Out-AxeInfo 
        $XMLHelper.Save("$AXEResultsPath\results.xml")
    }
 
}
catch
{
    $_ | Out-AxeError $_.Exception.Hresult
    $message = $_.Exception.Message + [Environment]::NewLine + $_.InvocationInfo.PositionMessage
    $message | Out-AxeInfo
    throw
}
finally 
{
    "Cancelling any active tracing session..." | Out-AxeInfo 
    Clear-ETWTrace

    "AXE cleanup..." | Out-AxeInfo 
    Start-AxeCleanup

    if ($DebugFlag) {
        Read-Host "Press ENTER key to exit"
    }
}
