packages feed

moonlight-delta-0.1.0.0: src-patch/Moonlight/Delta/Patch/Internal/Compose/SameSupport.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

module Moonlight.Delta.Patch.Internal.Compose.SameSupport
  ( SameSupport,
    reflexiveSupport,
    extendSameSupport,
    sameSupportLatest,
    validateBoundarySameSupport,
    spliceSameSupport,
  )
where

import Control.Monad.ST (ST, runST)
import Moonlight.Delta.Patch.Internal.Builder
import Moonlight.Delta.Patch.Internal.Cell
  ( cellAfterEndpoint,
    cellBeforeEndpoint,
    endpointToMaybe,
  )
import Moonlight.Delta.Patch.Internal.Cursor
import Moonlight.Delta.Patch.Internal.Types
import Prelude

data SameSupport key value = SameSupport !(Patch key value) !(Patch key value)

reflexiveSupport :: Patch key value -> SameSupport key value
reflexiveSupport patch =
  SameSupport patch patch
{-# INLINE reflexiveSupport #-}

sameSupportLatest :: SameSupport key value -> Patch key value
sameSupportLatest (SameSupport _ latest) =
  latest
{-# INLINE sameSupportLatest #-}

extendSameSupport :: SameSupport key value -> SameSupport key value -> SameSupport key value
extendSameSupport (SameSupport first _) (SameSupport _ latest) =
  SameSupport first latest
{-# INLINE extendSameSupport #-}

validateBoundarySameSupport ::
  forall key value error.
  (Ord key, Eq value) =>
  (key -> Maybe value -> Maybe value -> error) ->
  Patch key value ->
  Patch key value ->
  Either error (Maybe (SameSupport key value))
validateBoundarySameSupport makeBoundaryError older newer
  | entryCount older /= entryCount newer =
      Right Nothing
  | otherwise =
      go (cellsForInstance older) (cellsForInstance newer)
  where
    go [] [] =
      Right (Just (SameSupport older newer))
    go [] _ =
      Right Nothing
    go _ [] =
      Right Nothing
    go ((olderKey, olderCell) : olderRest) ((newerKey, newerCell) : newerRest) =
      case compare olderKey newerKey of
        LT -> Right Nothing
        GT -> Right Nothing
        EQ ->
          let !olderAfter = endpointToMaybe (cellAfterEndpoint olderCell)
              !newerBefore = endpointToMaybe (cellBeforeEndpoint newerCell)
           in if olderAfter /= newerBefore
                then Left (makeBoundaryError newerKey olderAfter newerBefore)
                else go olderRest newerRest
{-# INLINABLE validateBoundarySameSupport #-}

spliceSameSupport :: (PatchKey key, PatchValue value) => SameSupport key value -> Patch key value
spliceSameSupport (SameSupport first latest) =
  runST $ do
    builder <- newBuilder
    zipRows builder (cursor (toPagedMap first)) (cursor (toPagedMap latest))
    finishBuilder builder
  where
    zipRows :: (PatchKey key, PatchValue value) => Builder s key value -> Cursor key value -> Cursor key value -> ST s ()
    zipRows _ CursorEnd CursorEnd =
      pure ()
    zipRows builder firstCursor latestCursor =
      case (currentRow firstCursor, currentRow latestCursor) of
        (Just (_firstKey, before, _firstAfter), Just (latestKey, _latestBefore, after)) -> do
          appendTransition builder latestKey before after
          zipRows builder (advanceRow firstCursor) (advanceRow latestCursor)
        _ -> pure ()
{-# INLINABLE spliceSameSupport #-}