# Language must be fully installed, not just its parts
# Settings -> Time & Language -> Language & Region -> jazyk
#
# Exception in RTSS for Parsec is recommended for correct screen capture
#
# Parsec Service must not start so that AHK can type values in
# services -> Parsec -> Start - Manual
#
# Project wiki contains detailed explanation on how this works and the design choices behind this
$parsecd = "parsecd"
$ahkScript = "C:\AOFG\ParsecPasswordFillIn.ahk"
$parsecInstalledPath = "C:\Program Files\Parsec\parsecd.exe"
$ahkExecutable = "C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe"
$tokenFilePath = $env:ProgramData + "\Parsec\user.bin"
$logFile = "C:\AOFG\parsecLoginLog.txt"

$extraDelay = 2

$ok = 200
$invalidEmail = 400
$invalidCredentials = 401
$confirmLogin = 403

$invalid = 500

Start-Transcript -Path $logFile | Out-Null

function Write-Log(
    [Parameter(Mandatory = $True)]
    [ValidateSet("INFO", "WARN", "ERROR")]
    [String]$Level = "INFO",

    [Parameter(Mandatory = $True)]
    [string]$Message
)
{
    $Stamp = (Get-Date).toString("dd-MM-yyyy HH:mm:ss")
    Write-Host "$Stamp $Level $Message"
}

function Start-Parsec
{
    $running = Get-Process -Name $parsecd -ErrorAction SilentlyContinue
    if($null -eq $running) 
    {
        Start-Process $parsecInstalledPath
    }
}

function Start-ParsecService
{
    Start-Service -Name "Parsec"
}

function Normalize-ErrorCode([string]$errorCode)
{
    if($errorCode.Length -ne 3)
    {
        return $invalid
    }

    if($errorCode[0] -eq 'A')
    {
        $errorCode = '4' + $errorCode[1] + $errorCode[2]
    }

    if($errorCode[1] -eq 'O' -or $errorCode[1] -eq 'o')
    {
        $errorCode = $errorCode[0] + '0' + $errorCode[2]
    }

    if($errorCode[2] -eq 'O' -or $errorCode[2] -eq 'o')
    {
        $errorCode = $errorCode[0] + $errorCode[1] + '0'
    }

    if($errorCode[2] -eq 'l' -or $errorCode[2] -eq 'I' -or $errorCode[2] -eq '|' -or $errorCode[2] -eq '!')
    {
        $errorCode = $errorCode[0] + $errorCode[1] + '1'
    }

    return $errorCode
}

function Get-ParsecErrorCode([string]$rawWindowText)
{
    $pattern = "Error\s*.(\d[\d|O][\d|O|I|l|\||!]).*"
    $matchResult = $rawWindowText | Select-String -Pattern $pattern

    if($matchResult.Matches.Count -gt 0) {
        $errorCode = $matchResult.Matches[0].Groups[1].Value
        $normalizedErrorCode = Normalize-ErrorCode -errorCode $errorCode
        return $normalizedErrorCode
    }
    else
    {
        return $invalid
    }
}

function IsAnyUserLoggedIn 
{
    if((Test-Path $tokenFilePath -PathType Leaf)) {
        Write-Log -Level INFO -Message "Login success"
        return $true
    }

    Write-Log -Level INFO -Message "No user logged in"
    return $false
}

$parsecErrorCode = $invalid
Start-Parsec

if(IsAnyUserLoggedIn) {
    Start-ParsecService
    Stop-Transcript
    exit
}

function Get-Credentials() {
    $credentialsPipe = "aofg_parsec_credentials"
    # Create a named pipe server
    $credentialsPipeServer = New-Object System.IO.Pipes.NamedPipeServerStream $credentialsPipe, 'In'
    # Wait for a connection from the client
    $credentialsPipeServer.WaitForConnection()
    # Read the data sent by the client
    $credentialsReader = New-Object System.IO.StreamReader $credentialsPipeServer
    $email = $credentialsReader.ReadLine()
    $password = $credentialsReader.ReadLine()

    $credentialsReader.Close()
    $credentialsPipeServer.Close()
    
    Write-Log -Level INFO -Message "Data read successfully"
    return @{
        "email" = $email
        "password" = $password
    }
}

function Write-OutputToPipe([string]$output)
{
    $outputPipe = "aofg_parsec_login_output"
    $outputPipeServer = New-Object System.IO.Pipes.NamedPipeServerStream $outputPipe, 'Out'
    $outputPipeServer.WaitForConnection()
    $outputWriter = New-Object System.IO.StreamWriter $outputPipeServer
    $outputWriter.WriteLine($output)

    $outputWriter.Close()
    $outputPipeServer.Close()
}

function Escape-Argument {
    param([string]$arg)

    # Escape internal quotes: replace " with `"
    $escaped = $arg -replace '"', '\"'

    # Surround the whole thing with quotes
    return '"' + $escaped + '"'
}

function Close-ParsecWindow {
    Start-Process -FilePath $ahkExecutable -ArgumentList "C:\AOFG\CloseParsecWindow.ahk" -Wait -NoNewWindow
}

while($true)
{
    $credentials = Get-Credentials
    $escapedEmail = Escape-Argument $credentials.email
    $escapedPassword = Escape-Argument $credentials.password
    $arguments = "$ahkScript $escapedEmail $escapedPassword"


    $processInfo = New-Object System.Diagnostics.ProcessStartInfo
    $processInfo.FileName = $ahkExecutable
    $processInfo.Arguments = $arguments
    $processInfo.RedirectStandardOutput = $true
    $processInfo.UseShellExecute = $false
    $processInfo.CreateNoWindow = $true

    $process = New-Object System.Diagnostics.Process
    $process.StartInfo = $processInfo
    $process.Start() | Out-Null

    $stdout = $process.StandardOutput.ReadLine()

    $process.WaitForExit()

    Start-Sleep -Seconds $extraDelay

    if (IsAnyUserLoggedIn) {
        Write-OutputToPipe $ok
        Start-ParsecService
        Close-ParsecWindow
        break
    }

    $parsecErrorCode = Get-ParsecErrorCode $stdout
    Write-Log -Level WARN -Message "Login failed:`tCode: $parsecErrorCode`tOCR:`t$stdout"

    # https://support.parsec.app/hc/en-us/articles/360022720351-All-Error-Codes

    if($parsecErrorCode -match "400")
    {
        Write-OutputToPipe $invalidEmail
    }
    elseif($parsecErrorCode -match "401")
    {
        Write-OutputToPipe $invalidCredentials        
    }
    elseif($parsecErrorCode -match "403")
    {
        Write-OutputToPipe $confirmLogin
    }
    else
    {
        Write-OutputToPipe "$parsecErrorCode`tOCR:`t$stdout"
    }
}

Stop-Transcript
