packages feed

typed-range-0.1.0.0: Data/Range/Typed/RangeInternal.hs

{-# LANGUAGE LambdaCase #-}

module Data.Range.Typed.RangeInternal where

import Control.Monad (guard)
import Data.Functor (($>))
import Data.Maybe (catMaybes, mapMaybe)
import Data.Range.Typed.Data
import Data.Range.Typed.Spans
import Data.Range.Typed.Util

{-
 - The following assumptions must be maintained at the beginning of these internal
 - functions so that we can reason about what we are given.
 -
 - RangeMerge assumptions:
 - * The span ranges will never overlap the bounds.
 - * The span ranges are always sorted in ascending order by the first element.
 - * The lower and upper bounds never overlap in such a way to make it an infinite range.
 -}
data RangeMerge a
  = RM
      { largestLowerBound :: Maybe (Bound a),
        largestUpperBound :: Maybe (Bound a),
        spanRanges :: [(Bound a, Bound a)]
      }
  | IRM
  | ERM
  deriving (Show, Eq)

emptyRangeMerge :: RangeMerge a
emptyRangeMerge = RM Nothing Nothing []

storeRange :: (Ord a) => AnyRangeFor c a -> RangeMerge a
storeRange (AnyRangeFor range) =
  case range of
    InfiniteRange -> IRM
    EmptyRange -> ERM
    LowerBoundRange lower -> emptyRangeMerge {largestLowerBound = Just lower}
    UpperBoundRange upper -> emptyRangeMerge {largestUpperBound = Just upper}
    SpanRange x y
      | boundValue x == boundValue y && pointJoinType x y == Separate -> emptyRangeMerge
      | otherwise -> emptyRangeMerge {spanRanges = [(minBounds x y, maxBounds x y)]}
    SingletonRange x -> emptyRangeMerge {spanRanges = [(InclusiveBound x, InclusiveBound x)]}

storeRanges :: (Ord a) => RangeMerge a -> [AnyRangeFor c a] -> RangeMerge a
storeRanges = foldr (unionRangeMerges . storeRange)

loadRanges :: (Ord a) => [AnyRangeFor c a] -> RangeMerge a
loadRanges = storeRanges emptyRangeMerge
{-# INLINE [0] loadRanges #-}

exportRangeMerge :: (Eq a) => RangeMerge a -> [AnyRange a]
exportRangeMerge =
  \case
    IRM -> [AnyRangeFor InfiniteRange]
    ERM -> [AnyRangeFor EmptyRange]
    RM lb up spans ->
      let putLowerBound :: Maybe (Bound a) -> [AnyRange a]
          putLowerBound = maybe [] (return . AnyRangeFor . LowerBoundRange)
          putUpperBound :: Maybe (Bound a) -> [AnyRange a]
          putUpperBound = maybe [] (return . AnyRangeFor . UpperBoundRange)
          putSpans = map simplifySpan
          simplifySpan (x, y) =
            if x == y && pointJoinType x y /= Separate
              then AnyRangeFor $ SingletonRange $ boundValue x
              else AnyRangeFor $ SpanRange x y
       in putUpperBound up <> putSpans spans <> putLowerBound lb

{-# RULES "load/export" [1] forall x. loadRanges (exportRangeMerge x) = x #-}

intersectSpansRM :: (Ord a) => RangeMerge a -> RangeMerge a -> RangeMerge a
intersectSpansRM one two = RM Nothing Nothing newSpans
  where
    newSpans = intersectSpans (spanRanges one) (spanRanges two)

intersectWith :: (Ord a) => (Bound a -> (Bound a, Bound a) -> Maybe (Bound a, Bound a)) -> Maybe (Bound a) -> [(Bound a, Bound a)] -> [(Bound a, Bound a)]
intersectWith _ Nothing _ = []
intersectWith fix (Just lower) xs = mapMaybe (fix lower) xs

fixLower :: (Ord a) => Bound a -> (Bound a, Bound a) -> Maybe (Bound a, Bound a)
fixLower lower (x, y) = do
  guard (boundValue lower <= boundValue y)
  return (maxBoundsIntersection lower x, y)

fixUpper :: (Ord a) => Bound a -> (Bound a, Bound a) -> Maybe (Bound a, Bound a)
fixUpper upper (x, y) = do
  guard (boundValue x <= boundValue upper)
  return (x, minBoundsIntersection y upper)

intersectionRangeMerges :: (Ord a) => RangeMerge a -> RangeMerge a -> RangeMerge a
intersectionRangeMerges ERM _ = ERM
intersectionRangeMerges _ ERM = ERM
intersectionRangeMerges IRM two = two
intersectionRangeMerges one IRM = one
intersectionRangeMerges one two =
  RM
    { largestLowerBound = newLowerBound,
      largestUpperBound = newUpperBound,
      spanRanges = unionSpans sortedResults
    }
  where
    lowerOneSpans = intersectWith fixLower (largestLowerBound one) (spanRanges two)
    lowerTwoSpans = intersectWith fixLower (largestLowerBound two) (spanRanges one)
    upperOneSpans = intersectWith fixUpper (largestUpperBound one) (spanRanges two)
    upperTwoSpans = intersectWith fixUpper (largestUpperBound two) (spanRanges one)
    intersectedSpans = intersectSpans (spanRanges one) (spanRanges two)

    sortedResults =
      removeEmptySpans $
        foldr1
          insertionSortSpans
          [ lowerOneSpans,
            lowerTwoSpans,
            upperOneSpans,
            upperTwoSpans,
            intersectedSpans,
            calculateBoundOverlap one two
          ]

    newLowerBound = calculateNewBound largestLowerBound maxBoundsIntersection one two
    newUpperBound = calculateNewBound largestUpperBound minBoundsIntersection one two

    calculateNewBound ::
      (Ord a) =>
      (RangeMerge a -> Maybe (Bound a)) ->
      (Bound a -> Bound a -> Bound a) ->
      RangeMerge a ->
      RangeMerge a ->
      Maybe (Bound a)
    calculateNewBound ext comp one' two' = case (ext one', ext two') of
      (Just x, Just y) -> Just $ comp x y
      (_, Nothing) -> Nothing
      (Nothing, _) -> Nothing

calculateBoundOverlap :: (Ord a) => RangeMerge a -> RangeMerge a -> [(Bound a, Bound a)]
calculateBoundOverlap one two = catMaybes [oneWay, secondWay]
  where
    oneWay = do
      x <- largestLowerBound one
      y <- largestUpperBound two
      guard (compareLower y x /= LT)
      return (x, y)

    secondWay = do
      x <- largestLowerBound two
      y <- largestUpperBound one
      guard (compareLower y x /= LT)
      return (x, y)

unionRangeMerges :: (Ord a) => RangeMerge a -> RangeMerge a -> RangeMerge a
unionRangeMerges ERM one = one
unionRangeMerges one ERM = one
unionRangeMerges IRM _ = IRM
unionRangeMerges _ IRM = IRM
unionRangeMerges one two = infiniteCheck filterTwo
  where
    filterOne = foldr filterLowerBound boundedRM (unionSpans sortedSpans)
    filterTwo = foldr filterUpperBound (filterOne {spanRanges = []}) (spanRanges filterOne)

    infiniteCheck :: (Ord a) => RangeMerge a -> RangeMerge a
    infiniteCheck IRM = IRM
    infiniteCheck rm@(RM (Just lower) (Just upper) _) =
      if compareUpperToLower upper lower /= LT
        then IRM
        else rm
    infiniteCheck rm = rm

    newLowerBound = calculateNewBound largestLowerBound minBounds one two
    newUpperBound = calculateNewBound largestUpperBound maxBounds one two

    sortedSpans = insertionSortSpans (spanRanges one) (spanRanges two)

    boundedRM =
      RM
        { largestLowerBound = newLowerBound,
          largestUpperBound = newUpperBound,
          spanRanges = []
        }

    calculateNewBound ::
      (Ord a) =>
      (RangeMerge a -> Maybe (Bound a)) ->
      (Bound a -> Bound a -> Bound a) ->
      RangeMerge a ->
      RangeMerge a ->
      Maybe (Bound a)
    calculateNewBound ext comp one' two' = case (ext one', ext two') of
      (Just x, Just y) -> Just $ comp x y
      (z, Nothing) -> z
      (Nothing, z) -> z

filterLowerBound :: (Ord a) => (Bound a, Bound a) -> RangeMerge a -> RangeMerge a
filterLowerBound _ ERM = ERM
filterLowerBound _ IRM = IRM
filterLowerBound a rm@(RM Nothing _ _) = rm {spanRanges = a : spanRanges rm}
filterLowerBound s@(lower, _) rm@(RM (Just lowestBound) _ _) =
  case boundCmp lowestBound s of
    GT -> rm {spanRanges = s : spanRanges rm}
    LT -> rm
    EQ -> rm {largestLowerBound = Just $ minBounds lowestBound lower}

filterUpperBound :: (Ord a) => (Bound a, Bound a) -> RangeMerge a -> RangeMerge a
filterUpperBound _ ERM = ERM
filterUpperBound _ IRM = IRM
filterUpperBound a rm@(RM _ Nothing _) = rm {spanRanges = a : spanRanges rm}
filterUpperBound s@(_, upper) rm@(RM _ (Just upperBound') _) =
  case boundCmp upperBound' s of
    LT -> rm {spanRanges = s : spanRanges rm}
    GT -> rm
    EQ -> rm {largestUpperBound = Just $ maxBounds upperBound' upper}

invertRM :: (Ord a) => RangeMerge a -> RangeMerge a
invertRM ERM = IRM
invertRM IRM = emptyRangeMerge
invertRM (RM Nothing Nothing []) = IRM
invertRM (RM (Just lower) Nothing []) = RM Nothing (Just . invertBound $ lower) []
invertRM (RM Nothing (Just upper) []) = RM (Just . invertBound $ upper) Nothing []
invertRM (RM (Just lower) (Just upper) []) = RM Nothing Nothing [(invertBound upper, invertBound lower)]
invertRM rm =
  RM
    { largestUpperBound = newUpperBound,
      largestLowerBound = newLowerBound,
      spanRanges = upperSpan <> betweenSpans <> lowerSpan
    }
  where
    newLowerValue = invertBound $ snd $ last $ spanRanges rm
    newUpperValue = invertBound $ fst $ head $ spanRanges rm

    newUpperBound = case largestUpperBound rm of
      Just _ -> Nothing
      Nothing -> Just newUpperValue

    newLowerBound = case largestLowerBound rm of
      Just _ -> Nothing
      Nothing -> Just newLowerValue

    upperSpan = case largestUpperBound rm of
      Nothing -> []
      Just upper -> [(invertBound upper, newUpperValue)]
    lowerSpan = case largestLowerBound rm of
      Nothing -> []
      Just lower -> [(newLowerValue, invertBound lower)]

    betweenSpans = invertSpans $ spanRanges rm

joinRM :: (Eq a, Enum a) => RangeMerge a -> RangeMerge a
joinRM o@(RM _ _ []) = o
joinRM rm = RM lower higher spansAfterHigher
  where
    joinedSpans = joinSpans $ spanRanges rm

    (lower, spansAfterLower) =
      case (largestLowerBound rm, reverse joinedSpans) of
        o@(Just l, (xl, xh) : xs) ->
          if succ (highestValueInUpperBound xh) == lowestValueInLowerBound l
            then (Just xl, reverse xs)
            else o
        x -> x

    (higher, spansAfterHigher) =
      case (largestUpperBound rm, spansAfterLower) of
        o@(Just h, (xl, xh) : xs) ->
          if highestValueInUpperBound h == pred (lowestValueInLowerBound xl)
            then (Just xh, xs)
            else o
        x -> x

updateBound :: Bound a -> a -> Bound a
updateBound = ($>)

unmergeRM :: RangeMerge a -> [RangeMerge a]
unmergeRM ERM = [ERM]
unmergeRM IRM = [IRM]
unmergeRM (RM lower upper spans) =
  maybe [] (\x -> [RM Nothing (Just x) []]) upper
    <> fmap (\x -> RM Nothing Nothing [x]) spans
    <> maybe [] (\x -> [RM (Just x) Nothing []]) lower