modules/FeedProcessor/SingularProcessor.psm1

using module '..\Enums.psm1'
using module '..\Helper\DateTimeHelper.psm1'
using module '..\Helper\ObjectHelper.psm1'
using module '.\AuditLog\Message.psm1'
using module '.\AuditLog\Event.psm1'
using module '.\AuditLog\Market.psm1'
using module '.\AuditLog\Outcome.psm1'
using module '.\AuditLog\Event\SportData.psm1'
using module '.\AuditLog\Event\Score.psm1'
using module '.\AuditLog\Message\Cardinality.psm1'
using module '.\ProcessorBase.psm1'

class SingularProcessor: Processor{

    SingularProcessor([SOURCE_TYPE] $sourceType): base($sourceType){
        switch ($sourceType) {
            ([SOURCE_TYPE]::backend) {
                $this.source = [SOURCE]::sb
                $this.producers.Add('DFS', 0)
                $this.mapExternalKeyPrefix.Add('shortCode', '')
            }
            ([SOURCE_TYPE]::feedService) {
                $this.source = [SOURCE]::core
                $this.producers.Add('DFS', 0)
            }
            ([SOURCE_TYPE]::adapter) {
                $this.source = [SOURCE]::custom

                $this.producers.Add('CUS-UOF', 31)
                $this.producers.Add('CUS-LO', 32)
                $this.producers.Add('CUS-PRE', 33)
                $this.producers.Add('CUS-M-LO', 34)
                $this.producers.Add('CUS-MASTER-PRE', 35)
                $this.producers.Add('CUS-MASTER-UOF', 36)

                $this.mapExternalKeyPrefix.Add('custom', '3111') # TODO
            }
        }
        $this.shouldReprocess = $false
        $this.isTypeBased = $false
        $this.haveMarketSeparator = $false
        $this.needFeedProducer = $true
    }

    #region Internal Resolving Methods
    hidden [string] ResolveEventStatus([string] $state, [string] $liveCoverage){
        $status = ''

        switch ($state) {
            'NotStarted' { $status = [CONST]::STATUS_ACTIVE }
            'Live' {
                if ($liveCoverage -eq 'True') {
                    $status = [CONST]::STATUS_ACTIVE
                }
                else {
                    $status = [CONST]::STATUS_STOPPED
                }
            }
            'Ended' { $status = [CONST]::STATUS_STOPPED }
            'Finished' { $status = [CONST]::STATUS_STOPPED }
        }

        return $status
    }

    hidden [string] ResolveMessageType([string] $type){
        $result = ''

        switch ($type) {
            'FIXTURE_CHANGE' { $result = [CONST]::MSG_FIXTURE }
            'BET_STOP' { $result = [CONST]::MSG_BET_STOP }
            'ODDS_CHANGE' { $result = [CONST]::MSG_ODDS_CHANGE }
            'BET_SETTLEMENT' { $result = [CONST]::MSG_SETTLEMENT }
            default { $result = $type}
        }

        return $result
    }

    #endregion

    #region Processing Methods

    [void] ProcessMessage([PSCustomObject] $line) {
        ([Processor]$this).ProcessMessage($line)

        $this.message.uniqueIdentifier = $line.content.dfsMetrics.providerTimestamp
        $this.message.timestamp = $line.content.timestamp

        if ($this.sourceType -in ([SOURCE_TYPE]::backend, [SOURCE_TYPE]::feedService)) {
            if (-not $line.content.feedId){
                $this.message.producer = $line.content.payload.producerName
            }
            else {
                $this.message.producer = $this.mapFeedProducer[$line.content.feedId]
            }
        }
        else {
            $this.message.producer = $line.content.feedId
        }

        $this.message.changeType = $this.ResolveMessageType($line.content.type)
    }

    [void] ProcessEvent([PSCustomObject] $line, [bool] $includeSportData) {

        $this.ProcessMessage($line)
        $this.message.event = $this.getEvent($line.content, $includeSportData)

        $this.isEventProcessed = $true
    }

    [void] ProcessCardinality([PSCustomObject] $line) {

        $this.message.cardinality = $this.getCardinality($line.content)
    }

    [void] ProcessEnrichment([PSCustomObject] $line) {
        if (-not $this.message.event) {
            $this.ProcessEvent($line, $false)
        }
        $this.message.event.scoreDetails = $this.getScoreboard($line.content)
    }

    [void] ProcessMarket([PSCustomObject] $line, [Nullable[SEARCH_SCOPE]] $searchScope, [string[]] $marketId, [string[]] $outcomeId) {

        if (-not $searchScope) { $searchScope = [SEARCH_SCOPE]::internalId }
        if (-not $this.message.event) {
            $this.ProcessEvent($line, $false)
        }

        $this.message.event.markets = @()
        foreach ($market in $this.FilterMarkets($line.content.payload.markets, $searchScope, $marketId)) {
            $m = $this.getMarket($market, $outcomeId)
            $m.action = $line.content.type

            $this.message.event.markets += $m
        }
        $this.isMarketProcessed = $true
    }

    [bool] isEventRelated([PSCustomObject] $line){
        return ($line.content.type -eq 'FIXTURE_CHANGE') -or ($line.content.type -in ('BET_STOP', 'FEED_STOP'))
    }

    [bool] isEnrichmentRelated([PSCustomObject] $line){
        return ($line.content.dfsMetrics.type -eq 'ENRICHMENT')
    }

    [bool] isMarketRelated([PSCustomObject] $line, [Nullable[SEARCH_SCOPE]] $searchScope, [string[]] $marketId){
        if (-not $searchScope) { $searchScope = [SEARCH_SCOPE]::internalId }

        return ($line.content.type -in ('ODDS_CHANGE', 'BET_SETTLEMENT') -and `
            ($null -ne $this.FilterMarkets($line.content.payload.markets, $searchScope, $marketId)))
    }

    #endregion

    #region Private Methods

    hidden [PSCustomObject] FilterMarkets([PSCustomObject] $marketsContent, [SEARCH_SCOPE] $searchScope, [string[]] $marketId){

        return ($marketsContent | Where-Object { ($searchScope -eq [SEARCH_SCOPE]::externalId -and $_.externalId -in $marketid) -or `
                ($searchScope -eq [SEARCH_SCOPE]::internalId -and $_.id -in $marketid) -or `
                ('*' -in $marketid) })
    }

    hidden [PSCustomObject] FilterOutcomes([PSCustomObject] $outcomesContent, [string[]] $outcomeId){
        return $outcomesContent | Where-Object { ($_.id -in $outcomeId) -or ('*' -in $outcomeId) }
    }

    hidden [Event] getEvent([PSCustomObject] $messageContent, [bool] $includeSportData){
        [Event] $event = [Event]::new()

        if ($includeSportData) { $event.sportData = $this.getSportData($messageContent) }

        if ($this.sourceType -eq [SOURCE_TYPE]::backend){
            $event.startDate = (ConvertLogDate $messageContent.payload.event.startDate  '')
        }
        elseif ($this.sourceType -eq [SOURCE_TYPE]::feedService){
            $event.startDate = (ConvertLogDate ([DateTimeHelper]::ConvertFromTimestamp($messageContent.payload.event.startDate, $true))  '')
        }
        $event.liveCoverage = $messageContent.payload.event.liveCoverage

        $event.action = $messageContent.type
        if($messageContent.payload.fixtureChangeType) { $event.other.Add('changeType', $messageContent.payload.fixtureChangeType) }
        if($messageContent.payload.event.status) { $event.other.Add('state', $messageContent.payload.event.status) }

        $event.status = $this.ResolveEventStatus($messageContent.payload.event.status, $event.liveCoverage)

        return $event
    }

    hidden [Market] getMarket([PSCustomObject] $marketContent, [string[]] $outcomeId) {

        [Market] $market = [Market]::new()

        $market.id = $marketContent.id
        $market.name = $marketContent.name
        $market.externalId = $marketContent.externalId

        $market.status = $marketContent.marketStatus
        $market.specifiers = [ObjectHelper]::ConvertToHashTable($marketContent.specifiers)
        $market.resulted = $marketContent.resulted

        $market.other = @{}

        foreach ($outcome in $this.FilterOutcomes($marketContent.outcomes, $outcomeId)) {
            $market.outcomes += $this.getOutcome($outcome)
        }

        if ($marketContent.payload.oddsChangeReason){ $market.other.Add('reason', $marketContent.payload.oddsChangeReason) }

        return $market
    }

    hidden [Outcome] getOutcome([PSCustomObject] $outcomeContent) {
        [Outcome] $outcome = [Outcome]::new()

        $outcome.id = $outcomeContent.id
        $outcome.name = $outcomeContent.name
        $outcome.action = $outcomeContent.action
        $outcome.status = $outcomeContent.outcomeStatus
        $outcome.price = $outcomeContent.odds
        $outcome.resulted = $outcomeContent.outcomeResult
        $outcome.settlement = $outcomeContent.settlementStatus
        $outcome.other = @{ }

        return $outcome
    }

    hidden [SportData] getSportData([PSCustomObject] $messageContent){
        $_sportId = $messageContent.payload.event.sportId
        $_sport = ''
        $_categoryId = $messageContent.payload.event.categoryId
        $_category = ''
        $_leagueId = $messageContent.payload.event.subCategoryId
        $_league = ''
        $_matchId = $messageContent.payload.event.id

        $_match = $messageContent.payload.event.name
        $_homeId = $messageContent.payload.event.homeCompetitorIds
        $_home = $messageContent.payload.event.competitors | Where-Object { $_.id -in $_homeId }
        $_awayId = $messageContent.payload.event.awayCompetitorIds
        $_away = $messageContent.payload.event.competitors | Where-Object { $_.id -in $_awayId }

        $_references = $messageContent.payload.event.references

        return [SportData]::new($_sportId, $_sport, $_categoryId, $_category, $_leagueId, $_league, $_matchId, $_match, $_homeId, $_home, $_awayId, $_away, $_references)
    }

    hidden [Score] getScoreboard([PSCustomObject] $messageContent){
        $_periodId = $messageContent.payload.periodId.data
        if ($messageContent.payload.scoreboard.data.currentGamePoints){
            $_home = $messageContent.payload.scoreboard.data.currentGamePoints.homePoints
            $_away = $messageContent.payload.scoreboard.data.currentGamePoints.awayPoints
        }
        else {
            $_home = $messageContent.payload.scoreboard.data.currentHomeScore
            $_away = $messageContent.payload.scoreboard.data.currentAwayScore
        }
        $_scores = @()
        foreach ($i in $messageContent.payload.scoreboard.data.periodScores) {
            $_h = $i.homeScore
            $_a = $i.awayScore
            $_scores += $_h + ':' + $_a
        }
        $_currentServer = $messageContent.payload.currentServer.data
        $_playingMinute = $messageContent.payload.eventClock.data.playingMinute
        $_data = $messageContent.payload

        return [Score]::new($_periodId, $_home, $_away, $_scores, $_currentServer, $_playingMinute, $_data)
    }

    hidden [Cardinality] getCardinality([PSCustomObject] $messageContent){
        $cardinality = [Cardinality]::new()

        $cardinality.markets = $messageContent.payload.markets.count
        $cardinality.outcomes = $messageContent.payload.markets.outcomes.count

        if ($cardinality.markets -eq 0){
            if ($this.isEventRelated($messageContent)) {
                $cardinality.fixtures = 1
            }
            else {
                $cardinality.other = 1
            }
        }

        return $cardinality
    }

    #endregion
}