packages feed

moonlight-delta-0.1.0.0: test/epoch/EpochSupport/Reference.hs

module EpochSupport.Reference
  ( EpochReference (..),
    referenceFromInput,
    referenceFromDelta,
    referenceTransportKeys,
    referenceChangedKeys,
    referenceRetiredKeys,
    referenceTransportView,
    referenceCompose,
  )
where

import Data.IntMap.Strict (IntMap)
import Data.IntMap.Strict qualified as IntMap
import Data.IntSet (IntSet)
import Data.IntSet qualified as IntSet
import Data.Maybe (catMaybes)
import Moonlight.Delta.Epoch
import EpochSupport.Types (EpochInput (..))

-- | A deliberately denormalized sequential oracle. Every source key owns an
-- explicit 'Maybe' target, so composition is ordinary Kleisli composition of
-- partial functions rather than a copy of the production sparse formula.
data EpochReference = EpochReference
  { erSourceVersion :: !Version,
    erTargetVersion :: !Version,
    erSourceKeys :: !IntSet,
    erTargetKeys :: !IntSet,
    erMovement :: !(IntMap (Maybe Int)),
    erDirtyTargetKeys :: !IntSet
  }
  deriving stock (Eq, Show)

-- | Lower an accepted constructor input into the deliberately denormalized
-- oracle without consulting any production transport observer.
referenceFromInput :: EpochInput (IntMap Int) IntSet -> EpochReference
referenceFromInput input =
  EpochReference
    { erSourceVersion = endpointVersion (eiSource input),
      erTargetVersion = endpointVersion (eiTarget input),
      erSourceKeys = sourceKeySet,
      erTargetKeys = targetKeySet,
      erMovement = movement,
      erDirtyTargetKeys = IntSet.union transportedChanged freshTargetKeys
    }
  where
    sourceKeySet = endpointKeys (eiSource input)
    targetKeySet = endpointKeys (eiTarget input)
    movement =
      IntMap.fromAscList
        [ ( sourceKey,
            if IntSet.member sourceKey (eiRetired input)
              then Nothing
              else Just (IntMap.findWithDefault sourceKey sourceKey (eiTransport input))
          )
          | sourceKey <- IntSet.toAscList sourceKeySet
        ]
    freshTargetKeys =
      IntSet.difference targetKeySet (IntSet.fromList (catMaybes (IntMap.elems movement)))
    transportedChanged =
      IntSet.fromList
        [ targetKey
          | sourceKey <- IntSet.toAscList (eiChanged input),
            Just (Just targetKey) <- [IntMap.lookup sourceKey movement]
        ]

referenceFromDelta :: EpochDelta (IntMap Int) IntSet -> EpochReference
referenceFromDelta deltaValue =
  EpochReference
    { erSourceVersion = sourceVersion deltaValue,
      erTargetVersion = targetVersion deltaValue,
      erSourceKeys = sourceKeys deltaValue,
      erTargetKeys = targetKeys deltaValue,
      erMovement =
        IntMap.fromList
          ( fmap (fmap Just) (IntMap.toAscList (transportedKeys transportResult))
              <> fmap (,Nothing) (IntSet.toAscList (transportRetiredKeys transportResult))
          ),
      erDirtyTargetKeys = changedKeysAcrossEpoch deltaValue
    }
  where
    transportResult =
      transportKeys deltaValue (sourceKeys deltaValue)

referenceTransportKeys :: EpochReference -> IntSet -> Transport (IntMap Int) IntSet
referenceTransportKeys reference queryKeys =
  Transport
    { transportedKeys =
        IntMap.fromList
          [ (sourceKey, targetKey)
            | sourceKey <- IntSet.toAscList knownKeys,
              Just (Just targetKey) <- [IntMap.lookup sourceKey (erMovement reference)]
          ],
      transportRetiredKeys =
        IntSet.fromList
          [ sourceKey
            | sourceKey <- IntSet.toAscList knownKeys,
              Just Nothing <- [IntMap.lookup sourceKey (erMovement reference)]
          ],
      transportUnknownKeys =
        IntSet.difference queryKeys (erSourceKeys reference)
    }
  where
    knownKeys =
      IntSet.intersection queryKeys (erSourceKeys reference)

referenceChangedKeys :: EpochReference -> IntSet
referenceChangedKeys =
  erDirtyTargetKeys

referenceRetiredKeys :: EpochReference -> IntSet
referenceRetiredKeys reference =
  IntSet.fromList
    [ sourceKey
      | (sourceKey, Nothing) <- IntMap.toAscList (erMovement reference)
    ]

referenceTransportView ::
  EpochReference ->
  ContextView IntSet section ->
  Either (ViewTransportError Int) (ContextView IntSet section)
referenceTransportView reference contextView
  | cvVersion contextView /= erSourceVersion reference =
      Left (ViewSourceVersionMismatch (erSourceVersion reference) (cvVersion contextView))
  | otherwise =
      case IntSet.toAscList (transportUnknownKeys transportResult) of
        unknownKey : _ ->
          Left (ViewObservedKeyUnknown unknownKey)
        [] ->
          Right
            ( viewWithVersion
                (erTargetVersion reference)
                (viewWithSupport (IntSet.fromList (IntMap.elems (transportedKeys transportResult))) contextView)
            )
  where
    transportResult =
      referenceTransportKeys reference (cvObservedKeys contextView)

referenceCompose ::
  EpochReference ->
  EpochReference ->
  Either (ComposeError Int) EpochReference
referenceCompose newer older
  | erTargetVersion older /= erSourceVersion newer =
      Left (ComposeVersionMismatch (erTargetVersion older) (erSourceVersion newer))
  | erTargetKeys older /= erSourceKeys newer =
      Left ComposeUniverseMismatch
  | otherwise =
      Right
        EpochReference
          { erSourceVersion = erSourceVersion older,
            erTargetVersion = erTargetVersion newer,
            erSourceKeys = erSourceKeys older,
            erTargetKeys = erTargetKeys newer,
            erMovement = IntMap.map composeMovement (erMovement older),
            erDirtyTargetKeys =
              IntSet.union
                (transportDirtyThroughReference newer (erDirtyTargetKeys older))
                (erDirtyTargetKeys newer)
          }
  where
    composeMovement Nothing =
      Nothing
    composeMovement (Just intermediateKey) =
      IntMap.findWithDefault Nothing intermediateKey (erMovement newer)

transportDirtyThroughReference :: EpochReference -> IntSet -> IntSet
transportDirtyThroughReference reference dirtyKeys =
  IntSet.fromList
    [ targetKey
      | dirtyKey <- IntSet.toAscList dirtyKeys,
        Just (Just targetKey) <- [IntMap.lookup dirtyKey (erMovement reference)]
    ]