modules/FeedProcessor/BetradarProcessor.psm1

using module '..\Enums.psm1'
using module '..\Helper\DateTimeHelper.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 BetradarProcessor: Processor{

    BetradarProcessor([SOURCE_TYPE] $sourceType): base($sourceType){
        $this.source = [SOURCE]::betradar

        $this.producers.Add('BR-UOF', 11)
        $this.producers.Add('BR-STD', 12)
        $this.producers.Add('BR-LO', 13)

        $this.mapExternalKeyPrefix.Add('betradar', 'sr:match:')
        $this.mapExternalKeyPrefix.Add('betradarCtrl', '')
        $this.mapExternalKeyPrefix.Add('betradarUnified', 'sr:match:')

        $this.shouldReprocess = $false
        $this.isTypeBased = $false
        $this.haveMarketSeparator = $false
        $this.needFeedProducer = $false
    }

    #region Internal Resolving Methods

    hidden [string] ResolveChangeType([string] $status){

        $_retValue = $status
        switch ($status) {
            '1' { $_retValue = 'New' }
            '2' { $_retValue = 'DateTime' }
            '3' { $_retValue = 'Cancelled' }
            '4' { $_retValue = 'Format' }
            '5' { $_retValue = 'Coverage' }
        }

        return $_retValue
    }

    hidden [string] ResolveProduct([string] $product){

        $_retValue = $product
        switch ($product) {
            '1' { $_retValue = 'LiveOdds' }
            '2' { $_retValue = 'MTS' }
            '3' { $_retValue = 'BetradarCtrl' }
            '4' { $_retValue = 'BetPal' }
            '5' { $_retValue = 'Premium Cricket' }
        }

        return $_retValue
    }

    hidden [string] ResolveMatchStatus([string] $status){
        $_retValue = $status

        switch ($status) {
            '0' { $_retValue = [CONST]::MATCH_PREMATCH }
            '1' { $_retValue = [CONST]::MATCH_INPLAY }
            '3' { $_retValue = [CONST]::MATCH_ENDED }
            '4' { $_retValue = [CONST]::MATCH_CLOSED }
        }

        return $_retValue
    }

    hidden [string] ResolveMarketStatus([string] $status){

        $_retValue = $status
        switch ($status) {
            '-4' { $_retValue = 'Canceled' }
            '-3' { $_retValue = 'Settled' }
            '-2' { $_retValue = 'Handed over' }
            '-1' { $_retValue = 'Suspended' }
            '0' { $_retValue = 'Deactivated' }
            '1' { $_retValue = 'Active' }
        }

        return $_retValue
    }

    hidden [string] ResolveOutcomeStatus([string] $status){
        $_retValue = $status

        switch ($status) {
            0 { $_retValue = [CONST]::STATUS_SUSPENDED }
            1 { $_retValue = [CONST]::STATUS_ACTIVE }
        }

        return $_retValue
    }

    hidden [string] ResolveSettlement([string] $result, [string] $void_factor){

        # result="0" and no void_factor: Lose entire bet
        # result="1" and no void_factor: Win entire bet
        # result="0" and void_factor="1": Refund entire bet
        # result="1" and void_factor="0.5": Refund half bet and win other half
        # result="0" and void_factor="0.5": Refund half bet and lose other half.

        $_retValue = ''
        switch ($result) {
            '0' {
                if (-not $void_factor){
                    $_retValue = [CONST]::SETTLEMENT_LOST
                }
                if ($void_factor -eq 1){
                    $_retValue = [CONST]::SETTLEMENT_REFUND
                }
                if ($void_factor -eq 0.5){
                    $_retValue = [CONST]::SETTLEMENT_HALF_LOST
                }
            }
            '1' {
                if (-not $void_factor){
                    $_retValue = [CONST]::SETTLEMENT_WON
                }
                if ($void_factor -eq 0.5){
                    $_retValue = [CONST]::SETTLEMENT_HALF_WON
                }
            }
        }

        return $_retValue
    }

    hidden [string] ResolveReporting([string] $reporting){
        $result = ''

        switch ($reporting) {
            '-1' { $result = 'Suspended' }
            '0' { $result = 'Not Available' }
            '1' { $result = 'Active' }
        }

        return $result
    }

    hidden [hashtable] GetSpecifiers([string]$string) {
        $_retValue = @{}

        if ($string){
            $_tempSpec = $string.split('|')

            foreach ($_item in $_tempSpec) {
                $_val = $_item.split('=')
                $_retValue.Add($_val[0], $_val[1])
            }
        }

        return $_retValue
    }

    #endregion

    #region Processing Methods

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

        $root = ($line.content.PSCustomObject.Members | Where-Object MemberType -like NoteProperty | Select-Object -First 1 -ExpandProperty Value)
        $this.message.uniqueIdentifier = $root.timestamp
        $this.message.timestamp = $root.timestamp
        $this.message.producer = $root.product
    }

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

        $this.ProcessMessage($line)

        $eventContent = $null; $action = '';
        if ($line.content.fixture_change) {
            $eventContent = $line.content.fixture_change
            $action = [CONST]::MSG_FIXTURE
        }
        elseif ($line.content.bet_stop) {
            $eventContent = $line.content.bet_stop
            $action = [CONST]::MSG_BET_STOP
        }
        elseif ($line.content.bet_cancel) {
            $eventContent = $line.content.bet_cancel
            $action = [CONST]::MSG_BET_CANCEL
        }
        elseif ($line.content.rollback_bet_cancel) {
            $eventContent = $line.content.rollback_bet_cancel
            $action = [CONST]::MSG_ROLLBACK_BET_CANCEL
        }

        $this.message.event = $this.getEvent($eventContent, $includeSportData)
        $this.message.event.action = $action

        $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.odds_change.sport_event_status)
    }

    [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)
        }

        $marketContent = $null; $action=''
        if ($line.content.odds_change) {
            $marketContent = $line.content.odds_change.odds.market
            $action = [CONST]::MSG_ODDS_CHANGE
        }
        elseif ($line.content.bet_settlement) {
            $marketContent = $line.content.bet_settlement.outcomes.market
            $action = [CONST]::MSG_SETTLEMENT
        }
        elseif ($line.content.rollback_bet_settlement) {
            $marketContent = $line.content.rollback_bet_settlement.outcomes.market
            $action = [CONST]::MSG_ROLLBACK_SETTLEMENT
        }
        elseif ($line.content.snapshot_complete) {
            $marketContent = $line.content.snapshot_complete.odds.market
            $action = [CONST]::MSG_SNAPSHOT
        }

        $this.message.event.markets = @()
        foreach ($market in $this.FilterMarkets($marketContent, $searchScope, $marketId)) {
            $m = $this.getMarket($market, $outcomeId)
            $m.action = $action
            if ($line.content.odds_change.sport_event_status) { $m.other.Add('match_status', $this.ResolveMatchStatus($line.content.odds_change.sport_event_status.status))}

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

    [bool] isEventRelated([PSCustomObject] $line){
        return ($line.content.fixture_change -or $line.content.bet_stop)
    }

    [bool] isEnrichmentRelated([PSCustomObject] $line){
        return ($line.content.odds_change.sport_event_status)
    }

    [bool] isMarketRelated([PSCustomObject] $line, [Nullable[SEARCH_SCOPE]] $searchScope, [string[]] $marketId){
        if ($line.content.odds_change) { return ($null -ne $this.FilterMarkets($line.content.odds_change.odds.market, $searchScope, $marketId)) }
        elseif ($line.content.bet_settlement) { return ($null -ne $this.FilterMarkets($line.content.bet_settlement.outcomes.market, $searchScope, $marketId)) }
        elseif ($line.content.rollback_bet_settlement) { return ($null -ne $this.FilterMarkets($line.content.rollback_bet_settlement.outcomes.market, $searchScope, $marketId)) }
        elseif ($line.content.snapshot_complete) { return ($null -ne $this.FilterMarkets($line.content.snapshot_complete.odds.market, $searchScope, $marketId)) }

        return $false
    }

    #endregion

    #region Private Methods

    hidden [PSCustomObject] FilterMarkets([PSCustomObject] $marketsContent, [SEARCH_SCOPE] $searchScope, [string[]] $marketId){
        if (-not $searchScope) { $searchScope = [SEARCH_SCOPE]::internalId }
        return ($marketsContent | Where-Object { ($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) }

        $event.startDate = [DateTimeHelper]::ConvertFromTimestamp($messageContent.start_time, $true)
        #$event.status = $this.ResolveMatchStatus($messageContent.start_time, $true)

        $event.other = @{}
        if ($messageContent.groups) { $event.info.Add('groups', $messageContent.groups) }
        if ($messageContent.change_type) { $event.other.Add('changeType', $this.ResolveChangeType($messageContent.change_type)) }
        if ($messageContent.product) { $event.other.Add('product', $this.ResolveProduct($messageContent.product)) }
        # bet cancel
        if ($messageContent.end_time) { $event.info.Add('end', [DateTimeHelper]::ConvertFromTimestamp($messageContent.end_time)) }

        return $event
    }

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

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

        $market.id = $marketContent.id
        $market.status = $this.ResolveMarketStatus($marketContent.status)
        $market.specifiers = $this.GetSpecifiers($marketContent.specifiers)

        if ($marketContent.odds_change) { $market.action = [CONST]::MSG_ODDS_CHANGE }
        elseif ($marketContent.bet_settlement) { $market.action = [CONST]::MSG_SETTLEMENT; $market.status=[CONST]::STATUS_RESULTED }
        elseif ($marketContent.rollback_bet_settlement) { $market.action = [CONST]::MSG_ROLLBACK_SETTLEMENT }
        elseif ($marketContent.snapshot_complete) { $market.action = [CONST]::MSG_SNAPSHOT }

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

        return $market
    }

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

        $outcome.id = $outcomeContent.id
        $outcome.name = $outcomeContent.name
        $outcome.status = $this.ResolveOutcomeStatus($outcomeContent.active)
        $outcome.price = $outcomeContent.odds
        $outcome.settlement = $this.ResolveSettlement($outcomeContent.result, $outcomeContent.void_factor)

        return $outcome
    }

    hidden [SportData] getSportData([PSCustomObject] $messageContent){
        $_sportId = ''
        $_sport = ''
        $_categoryId = ''
        $_category = ''
        $_leagueId = ''
        $_league = ''
        $_matchId = $messageContent.fixture_change.event_id

        $_match = ''
        $_homeId = ''
        $_home = ''
        $_awayId = ''
        $_away = ''

        $_references = ''

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

    hidden [Score] getScoreboard([PSCustomObject] $eventStatusContent){
        $_periodId = $eventStatusContent.match_status

        $_home = ''
        $_away = ''

        $_scores = @()
        $_scores += $eventStatusContent.home_score + ':' + $eventStatusContent.away_score

        $_currentServer = $eventStatusContent.current_server
        $_playingMinute = $eventStatusContent.match_time

        $_other = @{}
        if ($eventStatusContent.reporting) { $_other.Add('reporting', $this.ResolveReporting($eventStatusContent.reporting)) }
        $_data = $eventStatusContent

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

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

        if ($messageContent.odds_change) {
            $cardinality.markets = $messageContent.odds_change.odds.market.count
            $cardinality.outcomes = $messageContent.odds_change.odds.market.outcome.count
        }
        if ($messageContent.bet_settlement) {
            $cardinality.markets = $messageContent.bet_settlement.outcomes.market.count
            $cardinality.outcomes = $messageContent.bet_settlement.outcomes.market.outcome.count
        }

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

        return $cardinality
    }

    #endregion
}