modules/SessionConfig.psm1

using module '.\Enums.psm1'
using module '.\Config.psm1'
using module '.\Helper\DateTimeHelper.psm1'
using module '.\Helper\ObjectHelper.psm1'

Import-LocalizedData -BindingVariable LocalizedData -FileName Session.Resources.psd1

class SessionConfig{
    #region Properties

    hidden [string] $SOURCE_SEPARATOR = "-"
    hidden [string] $INTERVAL_SEPARATOR = "_"

    hidden [string] $dtFileFormat = 'yyyyMMddHHmm'
    hidden [string] $tsFileFormat = 'hhmmss'
    hidden [string] $uriExternalId = 'https://sports-api.{0}/sportsbook/rest/v2/matches/external/{1}/{2}{3}'

    [OPERATOR] $operator
    [ENVIRONMENT] $environment
    [string] $timezone
    [string] $domain
    [PSCustomObject] $downloader
    [SOURCE[]] $analyzedSources
    hidden [SOURCE[]] $configuredSources

    # Calculated properties
    [string] $logFilePath

    # Analyzed scope
    [QUERY_TYPE] $queryType
    [int] $matchId
    [string] $referentTime
    [string] $timeSpan
    [datetime] $afterDateTime
    [datetime] $beforeDateTime

    #endregion

    #region Constructors

    SessionConfig([OPERATOR] $operator, [ENVIRONMENT] $environment){
        $this.Init($operator, $environment)
    }

    hidden Init([OPERATOR] $operator, [ENVIRONMENT] $environment){
        # setup current Operator's environment
        $config = [Config]::Load()

        $operatorEndpoint = $config.endpoints.$operator.$environment

        if ($null -ne $operatorEndpoint){
            $this.operator = $operator
            $this.environment = $environment
            $this.timezone = $operatorEndpoint.timezone
            $this.domain = $operatorEndpoint.domain
            $this.downloader = [ObjectHelper]::MergeObject($operatorEndpoint.downloader, $config.downloaders[$operatorEndpoint.downloader.type])

            if ($operatorEndpoint.downloader){
                $this.configuredSources = @()
                foreach ($src in $operatorEndpoint.downloader.sources.PSObject.Properties){
                    if ($src.Value){ $this.configuredSources += $src.Name }
                }

                $this.logFilePath = '{0}/{1}/{2}' -f @($config.auditLogFolder, $this.operator, $this.environment)
                $this.CreateAuditLogFolders()
            }
        }
        else {
            throw ('There is no configured endpoints for operator {0}, environment {1}' -f @($operator, $environment))
        }
    }

    #endregion

    [void] setAnalyzedScope([SOURCE[]] $sources, [long] $matchId, [string] $referentTime, [string] $timeSpan){
        if (-not $sources) { $this.analyzedSources =$this.configuredSources }
        else { $this.analyzedSources = $sources }

        $this.matchId = $matchId
        $this.referentTime = $referentTime
        $this.timeSpan = $timeSpan
        $this.afterDateTime, $this.beforeDateTime = [DateTimeHelper]::getAfterBefore($referentTime, $timeSpan)

        if ($matchId) { $this.queryType = [QUERY_TYPE]::matchId}
        if ($referentTime){ $this.queryType = [QUERY_TYPE]::combined }
        if (-not $matchId){ $this.queryType = [QUERY_TYPE]::datetimeInterval }

        Write-Host ($script:LocalizedData.AnalyzedPeriodMessage -f @($this.afterDateTime, $this.beforeDateTime, `
            [DateTimeHelper]::FormatTimespan($this.beforeDateTime - $this.afterDateTime)))
    }

    hidden [void] CreateAuditLogFolders(){
        if (-not (Test-Path $this.logFilePath)) {
            Write-Host "Creating operator's environment folder..."
            New-Item -Path $this.logFilePath -ItemType "directory" | Out-Null
        }
    }

    hidden [string] getSearchFileName([string] $source, [string]$extension){
        return ("{0}/{1}$($this.SOURCE_SEPARATOR){2}$($this.INTERVAL_SEPARATOR){3}$($this.SOURCE_SEPARATOR){4}.{5}" `
            -f @($this.logFilePath, $this.matchId, '*', `
            '*', $source, $extension))
    }

    hidden [string] getFileName([string] $source, [string]$extension){

        return ("{0}/{1}$($this.SOURCE_SEPARATOR){2}$($this.INTERVAL_SEPARATOR){3}$($this.SOURCE_SEPARATOR){4}.{5}" `
                     -f @($this.logFilePath, $this.matchId, $this.afterDateTime.ToString($this.dtFileFormat), `
                     $this.beforeDateTime.ToString($this.dtFileFormat), $source, $extension))
    }

    hidden [hashtable] getScopeFromFileName([string] $baseFileName, [datetime] $lastWriteDate){

        $from = $null; $to = $null; $src = ''
        if ($baseFileName.Contains($this.INTERVAL_SEPARATOR)){

            $fileParts = $baseFileName.Split($this.SOURCE_SEPARATOR)
            $dates = $fileParts[1].Split($this.INTERVAL_SEPARATOR)

            $from = [datetime]::ParseExact($dates[0], $this.dtFileFormat, $null)
            $to = [datetime]::ParseExact($dates[1], $this.dtFileFormat, $null)

            $src = [SOURCE]$fileParts[2]
        }
        elseif ($baseFileName.Contains($this.SOURCE_SEPARATOR)) {
            $fileParts = $baseFileName.Split($this.SOURCE_SEPARATOR)

            $to = $lastWriteDate
            $from = $null # $lastWriteDate.AddMonths(-3)
            $src = [SOURCE]$fileParts[1]
        }
        else {
            return $null
        }

        return @{from=$from; to=$to; source=$src}
    }

    [string] getFileName([SOURCE] $source, [string]$extension){
        return $this.getFileName([string]$source, $extension)
    }

    [bool] ApproveDownload() {

        #determine available scopes
        $intervalScopes = @()
        foreach ($file in (Get-ChildItem $this.getSearchFileName('*', '*'))){
            $interval = $this.getScopeFromFileName($file.BaseName, $file.LastWriteTime)
            if ($interval -and $interval.source -in $this.analyzedSources){
                $intervalScopes += $interval
            }
        }
        # if there are interval scopes, group them by source
        $optimizedScopes = @()

        if ($intervalScopes){
            foreach ($item in $intervalScopes) {
                $isFound = $false
                foreach ($os in $optimizedScopes) {
                    if ($os.from -eq $item.from -and $os.to -eq $item.to){
                        $os.source += $item.source
                        $isFound = $true
                    }
                }
                if (-not $isFound){
                    $optimizedScopes += @{from=$item.from; to=$item.to; source=@($item.source)}
                }
            }

            # Check if interval scopes have all the needed sources
            $intervalScopes = $optimizedScopes
            $optimizedScopes = @(); $canProceed = $true

            foreach ($scope in $intervalScopes) {
                foreach ($item in $this.analyzedSources) {
                    if ($item -notin $intervalScopes.source) { $canProceed = $false; break }
                }
                if ($canProceed) { $optimizedScopes += $scope}
            }
        }

        # find best scope
        $bestScope = $null; $coveredPeriod = 0
        foreach ($item in $optimizedScopes) {
            if (($item.to -gt $this.afterDateTime) -and ($item.from -lt $this.beforeDateTime)){
                $min = $this.afterDateTime
                $max = $this.beforeDateTime
                if ($item.from -gt $this.afterDateTime) { $min = $item.from }
                if ($item.to -lt $this.beforeDateTime) { $max = $item.to }

                if (($max - $min) -gt $coveredPeriod){
                    $bestScope = $item
                    $coveredPeriod = $max - $min
                }
            }
        }

        if (-not $bestScope -or $coveredPeriod -eq 0){
            Write-Host $script:LocalizedData.IncompleteLogsMessage
            return $true
        }
        else {
            Write-Host ($script:LocalizedData.AlreadyDownloadedLogsMessage -f `
                @($bestScope.from, $bestScope.to, [DateTimeHelper]::FormatTimespan($coveredPeriod)))

            $answer = Read-Host -Prompt $script:LocalizedData.UsingAlreadyDownloadedLogsQuestion

            if ($answer -in ('n', 'no')){
                return $true
            }
            else {
                $this.setAnalyzedScope($this.analyzedSources, $this.matchId, $bestScope.from.ToString(), ($bestScope.to - $bestScope.from))

                return $false
            }

        }
    }

    [string] UriFindExternalId([string] $key, [string]$prefix, [int]$matchId){
        return ($this.uriExternalId -f @($this.domain, $key, $prefix, $matchId))
    }
}