function convertUTF8ToASCII
{
    param ([Parameter(Mandatory = $true, Position = 0)] [String]$utf8Text)
    $utf8TextBytes = [system.text.encoding]::UTF8.GetBytes($utf8Text)
    $asciiText = [system.text.encoding]::convert([text.encoding]::UTF8, [text.encoding]::ASCII, $utf8TextBytes)
    -join [system.text.encoding]::ASCII.GetChars($asciiText)
}

function checkInstitute
{
    param([parameter(position = 0)][string]$instituteSign)
    return $Config.SPK.ContainsKey($instituteSign)
}

function getTimeStamp
{
    param ([Parameter(Mandatory = $false, Position = 0)][string]$formatString = "yyyyMMddHHmmss")   
    $now = Get-Date
    return $now.ToString($formatString)
}

function openLog
{
    param([string] $Path = $LogFileName)
    closeLog
    $parentDir = Split-Path -Parent $Path
    if ($parentDir -and -not (Test-Path $parentDir))
    {
        New-Item -ItemType Directory -Path $parentDir -Force | Out-Null
    }
    $Global:LogWriter = [System.IO.StreamWriter]::new(
        $Path, $true, [System.Text.UTF8Encoding]::new($false))
    $Global:LogWriter.AutoFlush = $true
    $Global:LogWriterPath = $Path
}

function closeLog
{
    if ($Global:LogWriter)
    {
        $Global:LogWriter.Flush()
        $Global:LogWriter.Dispose()
        $Global:LogWriter = $null
        $Global:LogWriterPath = $null
    }
}

function outLogVerbose
{
    if (-not $Config.Verbose)
    {
        return
    }
    outLog @args -ToScreen $false
}

function outLog
{
    [CmdletBinding(PositionalBinding = $false)]
    param
    (
        [Parameter(ValueFromRemainingArguments = $true)]
        [object[]] $Messages,
        [bool] $ToScreen = $true
    )
    $timestamp = Get-Date
    $outString = $timestamp.ToString("HH:mm:ss.fff") + ": "
    if ($Messages)
    {
        $outString += ($Messages -join [Environment]::NewLine)
    }
    if ($previuosTime)
    {
        $outString += [Environment]::NewLine + ($end - $start)
    }
    if (-not $Global:LogWriter -or $Global:LogWriterPath -ne $LogFileName)
    {
        openLog $LogFileName
    }
    $Global:LogWriter.WriteLine($outString)
    if ($ToScreen)
    {
        Write-Host $outString
    }
}
function removeDuplicatesFromSortedList
{
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyCollection()]
        [System.Collections.Generic.List[string]] $List,
        [Parameter(Mandatory = $false)]
        [string] $ListName = "list"
    )
    if ($List.Count -lt 2)
    {
        return
    }
    $unique = [System.Collections.Generic.List[string]]::new($List.Count)
    $previous = $null
    foreach ($entry in $List)
    {
        if ($entry -cne $previous)
        {
            $unique.Add($entry)
            $previous = $entry
        }
    }
    $removedCount = $List.Count - $unique.Count
    if ($removedCount -gt 0)
    {
        $List.Clear()
        $List.AddRange($unique)
    }
    outLog ($ListName + ": removed " + $removedCount + " duplicate lines, " + $List.Count + " remaining")
}

function outScreen
{
    $timestamp = Get-Date
    $outString = $timestamp.ToString("HH:mm:ss.fff") + ": "
    for ($i = 0; $i -lt $args.count; $i++)
    {
        $j = $i + 1
        $outString += $args[$i] 
    }
    if ($previuosTime)
    {
        $outString += ($end - $start)  
    }
    Write-Host $outString
}

function checkInput
{
    param([parameter(position = 0)][string]$spk)
    $spk = $spk.ToUpperInvariant()
    if ([string]::IsNullOrEmpty($spk))
    {
        outLogVerbose 'if ([string]::IsNullOrEmpty($spk)) -> true'
        if (isProbablyDevelopmentUser)
        {
            outLogVerbose 'if (isProbablyDevelopmentUser) -> true'
            $spk = $Config.DefaultInstitute.ToUpperInvariant()   
        }
        else
        {
            outLogVerbose 'else -> true (if/elseif (isProbablyDevelopmentUser) -> false)'
            while ($true)
            {
                outLogVerbose 'while ($true) -> true'
                $spk = $(Read-Host "please enter Institute Parameter (parameter '-sparkasse'), 'Ctrl-C for exit'" ).ToUpperInvariant()
                if ([string]::IsNullOrEmpty($spk))
                {
                    outLogVerbose 'if ([string]::IsNullOrEmpty($spk)) -> true'
                    continue
                }
                elseif (checkInstitute($spk))
                {
                    outLogVerbose 'elseif (checkInstitute($spk)) -> true'
                    break
                }
                else
                {
                    outLogVerbose 'else -> true (if/elseif (checkInstitute($spk)) -> false)'
                    continue
                }
            }       
        }
    }
    elseif (-not (checkInstitute($spk)))
    {
        outLogVerbose 'elseif (-not (checkInstitute($spk))) -> true'
        exit  
    }
    return $spk
}

function checkInputSourceFiles
{
    if (-not $InstitutsSourcePath)
    {
        outLogVerbose 'if (-not $InstitutsSourcePath) -> true'
        return $false
    }
    if (-not $InstitutsSourcePathKURS)
    {
        outLogVerbose 'if (-not $InstitutsSourcePathKURS) -> true'
        return $false
    }
    return $true
}

function checkInputDataFiles
{
    $ok = $true
    if (-not $ADObjectsFilePath)
    {
        outLogVerbose 'if (-not $ADObjectsFilePath) -> true'
        outLog "no ADObjectsFile found !"
        $ok = $false
    }
    if (-not $ADObjectMembershipsFilePath)
    {
        outLogVerbose 'if (-not $ADObjectMembershipsFilePath) -> true'
        outLog "no ADObjectsFile found !"
        $ok = $false
    }
    if (-not $ADApplicationsInFilePath)
    {
        outLogVerbose 'if (-not $ADApplicationsInFilePath) -> true'
        outLog "no ADApplicationsInFile found !"
        $ok = $false
    }
    if (-not $ADDirectoriesInFilePath)
    {
        outLogVerbose 'if (-not $ADDirectoriesInFilePath) -> true'
        outLog "no ADDirectoriesInFile found !"
        $ok = $false
    }
    if (-not $SecurityInFilePath)
    {
        outLogVerbose 'if (-not $SecurityInFilePath) -> true'
        outLog "no SecurityInFile found !"
        $ok = $false
    }
    if (-not $KURSPositionsInFilePath)
    {
        outLogVerbose 'if (-not $KURSPositionsInFilePath) -> true'
        outLog "no KURSPositionsInFile found !"
        $ok = $false
    }
    if (-not $KURSEmployeesInFilePath)
    {
        outLogVerbose 'if (-not $KURSEmployeesInFilePath) -> true'
        outLog "no KURSEmployeesInFile found !"
        $ok = $false
    }
    $ADObjectsFileTimeStamp = (Get-ChildItem $ADObjectsFilePath).BaseName.Split("_")[2]
    $ADObjectMembershipsFileTimeStamp = (Get-ChildItem $ADObjectMembershipsFilePath).BaseName.Split("_")[2]
    if ($ADObjectsFileTimeStamp -ne $ADObjectMembershipsFileTimeStamp)
    {
        outLogVerbose 'if ($ADObjectsFileTimeStamp -ne $ADObjectMembershipsFileTimeStamp) -> true'
        outLog "TimeStamps of latest input files found are not equal: ADObjectsFileTimeStamp:" $ADObjectsFileTimeStamp "ADObjectMembershipsFileTimeStamp: " $ADObjectMembershipsFileTimeStamp ", stopping process !"
        $ok = $false
    }  
    return $ok  
}

function checkTestMode
{
    if ($Config.TestMode -gt 0)
    {
        outLogVerbose 'if ($Config.TestMode -gt 0) -> true'
        outLog "entering TestMode, reading the first" $Config.TestMode "ADObject-entries for every roletype"
        return $true
    }
    outLog "entering ProductionMode, reading all entries"
    return $false
}

function isProbablyDevelopmentUser
{
    $userName = [Environment]::UserName
    if ($userName -ieq "edmond")
    {
        outLogVerbose 'if ($userName -ieq "edmond") -> true'
        return $true
    }
    elseif ($userName -ieq "burgwink")
    {
        outLogVerbose 'elseif ($userName -ieq "burgwink") -> true'
        return $true        
    }
    return $false
}

function dumpVar
{
    param([object]$member)
    Write-Host ($member | Format-Table -Force | Out-String)
}

function dumpConfig
{
    dumpVar($Config)
}

function stringReverse
{
    param ([Parameter(Mandatory = $true, Position = 0)][string]$text)
    $textArray = $Text.ToCharArray()
    [Array]::Reverse($textArray)
    -join $textArray               
}

function stringArrayReverseToCommaSeparatedString
{
    param ([Parameter(Mandatory = $true, Position = 0)] [String[]]$stringArray)
    [Array]::Reverse($stringArray)
    $outString = $stringArray -join ',' | Out-String  
    return $outString         
}
function stringArrayReverseToCommaSeparatedStringReturn
{
    param ([Parameter(Mandatory = $true, Position = 0)] [String[]]$stringArray)
    [Array]::Reverse($stringArray)
    $outString = $stringArray -join ',' | Out-String  
    return $outString         
}
function stringArrayReverseToCommaSeparatedStringWriteOutput
{
    param ([Parameter(Mandatory = $true, Position = 0)] [String[]]$stringArray)
    [Array]::Reverse($stringArray)
    $outString = $stringArray -join ',' | Out-String  
    Write-Output $outString         
}

function stringArrayReverseToCommaSeparatedStringEcho
{
    param ([Parameter(Mandatory = $true, Position = 0)] [String[]]$stringArray)
    [Array]::Reverse($stringArray)
    $stringArray -join ','   
}

function moveToInstituteTmpPath
{
    param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$filePath,
        [Parameter(Mandatory = $false, Position = 1)]
        [bool]$force = $false
    )
    $fileName = [System.IO.Path]::GetFileName($filePath)
    $tmpFilePath = $InstituteRootPathTmp + "/" + $fileName
    if ($force)
    {
        outLogVerbose 'if ($force) -> true'
        Remove-Item -Path $tmpFilePath -ErrorAction Ignore
    }
    Move-Item -Path $filePath -Destination $InstituteRootPathTmp
    return $tmpFilePath
}



function writeExceptionInfoReduced
{
    param([Parameter(Mandatory)][System.Management.Automation.ErrorRecord]$ErrorRecord)
    Write-Host "=== EXCEPTION ===" -ForegroundColor Red
    Write-Host "Message : $($ErrorRecord.Exception.Message)" -ForegroundColor Red
    Write-Host "Type    : $($ErrorRecord.Exception.GetType().FullName)" -ForegroundColor Yellow
    Write-Host $ErrorRecord.InvocationInfo.PositionMessage
    Write-Host "ScriptStackTrace:" -ForegroundColor Yellow
    Write-Host $ErrorRecord.ScriptStackTrace
}

function writeExceptionInfo
{
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [System.Management.Automation.ErrorRecord]$ErrorRecord,
        [switch]$Rethrow
    )

    Write-Host "=== EXCEPTION ===" -ForegroundColor Red
    Write-Host "Message : $($ErrorRecord.Exception.Message)" -ForegroundColor Red
    Write-Host "Type    : $($ErrorRecord.Exception.GetType().FullName)" -ForegroundColor Yellow
    Write-Host $ErrorRecord.InvocationInfo.PositionMessage
    Write-Host "ScriptStackTrace:" -ForegroundColor Yellow
    Write-Host $ErrorRecord.ScriptStackTrace

    if ($Rethrow)
    {
        outLogVerbose 'if ($Rethrow) -> true'
        throw $ErrorRecord
    }
}