packages feed

interval-algebra-2.1.2: src/IntervalAlgebra/IntervalUtilities.hs

{-|
Module      : Interval Algebra Utilities
Description : Functions for operating on containers of Intervals.
Copyright   : (c) NoviSci, Inc 2020
License     : BSD3
Maintainer  : bsaul@novisci.com
Stability   : experimental

-}

{-# LANGUAGE FlexibleInstances   #-}
{-# LANGUAGE NoImplicitPrelude   #-}
{-# LANGUAGE Safe                #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections       #-}

module IntervalAlgebra.IntervalUtilities
  (

    -- * Fold over sequential intervals
    combineIntervals
  , combineIntervalsL
  , combineIntervalsFromSorted
  , combineIntervalsFromSortedL
  , rangeInterval
  , gaps
  , gapsL
  , gapsWithin

    -- * Operations on Meeting sequences of paired intervals
  , foldMeetingSafe
  , formMeetingSequence

    -- * Withering functions

    -- ** Clear containers based on predicate
  , nothingIf
  , nothingIfNone
  , nothingIfAny
  , nothingIfAll

    -- ** Filter containers based on predicate
  , filterBefore
  , filterMeets
  , filterOverlaps
  , filterFinishedBy
  , filterContains
  , filterStarts
  , filterEquals
  , filterStartedBy
  , filterDuring
  , filterFinishes
  , filterOverlappedBy
  , filterMetBy
  , filterAfter
  , filterDisjoint
  , filterNotDisjoint
  , filterConcur
  , filterWithin
  , filterEncloses
  , filterEnclosedBy

    -- * Functions for manipulating intervals
  , lookback
  , lookahead

    -- * Gaps
  , makeGapsWithinPredicate
  , pairGaps
  , anyGapsWithinAtLeastDuration
  , allGapsWithinLessThanDuration

    -- * Misc utilities
  , relations
  , relationsL
  , intersect
  , clip
  , durations
  ) where

import safe           Control.Applicative            (Applicative (pure),
                                                      liftA2, (<$>), (<*>))
import qualified Control.Foldl                  as L
import safe           Control.Monad                  (Functor (fmap))
import safe           Data.Bool                      (Bool (..), not, otherwise,
                                                      (&&), (||))
import safe           Data.Eq                        (Eq ((==)))
import safe           Data.Foldable                  (Foldable (foldl', foldr, null, toList),
                                                      all, any, or)
import safe           Data.Function                  (flip, ($), (.))
import safe           Data.List                      (map, reverse, sortOn)
import safe           Data.Maybe                     (Maybe (..), maybe,
                                                      maybeToList)
import safe           Data.Monoid                    (Monoid (mempty))
import safe           Data.Ord                       (Ord (max, min), (<), (>=))
import safe           Data.Semigroup                 (Semigroup ((<>)))
import safe           Data.Traversable               (Traversable (sequenceA))
import safe           Data.Tuple                     (fst, uncurry)
import safe           GHC.Int                        (Int)
import safe           GHC.Show                       (Show)
import safe           IntervalAlgebra.Core           (ComparativePredicateOf1,
                                                      ComparativePredicateOf2,
                                                      Interval,
                                                      IntervalCombinable ((><)),
                                                      IntervalRelation (..),
                                                      IntervalSizeable (diff, duration),
                                                      Intervallic (..), after,
                                                      before, begin, beginerval,
                                                      beginervalFromEnd, bi,
                                                      concur, contains,
                                                      disjoint, during,
                                                      enclosedBy, encloses, end,
                                                      enderval,
                                                      endervalFromBegin, equals,
                                                      extenterval, finishedBy,
                                                      finishes, meets, metBy,
                                                      notDisjoint, overlappedBy,
                                                      overlaps, relate,
                                                      startedBy, starts, within,
                                                      (<|>))
import safe           IntervalAlgebra.PairedInterval (PairedInterval,
                                                      equalPairData,
                                                      getPairData,
                                                      makePairedInterval)
import safe           Safe                           (headMay, initSafe,
                                                      lastMay, tailSafe)
import safe           Witherable                     (Filterable (filter),
                                                      Witherable (..),
                                                      catMaybes, mapMaybe)

{- $setup
>>> import GHC.List ( (++), zip )
>>> import IntervalAlgebra.IntervalDiagram
>>> import Prettyprinter ( pretty )
-}

-------------------------------------------------
-- Unexported utilties used in functions below --
-------------------------------------------------

-- An internal utility function for creating a @Fold@ that maps over a structure
-- by consecutive pairs into a new structure.
makeFolder :: (Monoid (m b), Applicative m) => (a -> a -> b) -> L.Fold a (m b)
makeFolder f = L.Fold step begin done
 where
  begin = (mempty, Nothing)
  step (fs, Nothing) y = (fs, Just y)
  step (fs, Just x ) y = (fs <> pure (f x y), Just y)
  done (fs, _) = fs

-- | Create a predicate function that checks whether within a provided spanning
--   interval, are there (e.g. any, all) gaps of (e.g. <, <=, >=, >) a specified
--   duration among  the input intervals?
makeGapsWithinPredicate
  :: ( Monoid (t (Interval a))
     , Monoid (t (Maybe (Interval a)))
     , Applicative t
     , Witherable.Witherable t
     , IntervalSizeable a b
     , Intervallic i0
     , IntervalCombinable i1 a
     )
  => ((b -> Bool) -> t b -> Bool)
  -> (b -> b -> Bool)
  -> (b -> i0 a -> t (i1 a) -> Bool)
makeGapsWithinPredicate f op gapDuration interval l =
  maybe False (f (`op` gapDuration) . durations) (gapsWithin interval l)

-- | Gets the durations of gaps (via 'IntervalAlgebra.(><)') between all pairs
--   of the input.
pairGaps
  :: (Intervallic i, IntervalSizeable a b, IntervalCombinable i a)
  => [i a]
  -> [Maybe b]
pairGaps es = fmap (fmap duration . uncurry (><)) (pairs es)
-- Generate all pair-wise combinations of a single list.
-- pairs :: [a] -> [(a, a)]
-- copied from the hgeometry library
-- (https://hackage.haskell.org/package/hgeometry-0.12.0.4/docs/src/Data.Geometry.Arrangement.Internal.html#allPairs)
 where
  pairs = go
   where
    go []       = []
    go (x : xs) = fmap (x, ) xs <> go xs

-- | Creates a new @Interval@ of a provided lookback duration ending at the
--   'begin' of the input interval.
--
-- >>> lookback 4 (beginerval 10 (1 :: Int))
-- (-3, 1)
lookback
  :: (Intervallic i, IntervalSizeable a b)
  => b   -- ^ lookback duration
  -> i a
  -> Interval a
lookback d x = enderval d (begin x)

-- | Creates a new @Interval@ of a provided lookahead duration beginning at the
--   'end' of the input interval.
--
-- >>> lookahead 4 (beginerval 1 (1 :: Int))
-- (2, 6)
lookahead
  :: (Intervallic i, IntervalSizeable a b)
  => b   -- ^ lookahead duration
  -> i a
  -> Interval a
lookahead d x = beginerval d (end x)

-- | Within a provided spanning interval, are there any gaps of at least the
--   specified duration among the input intervals?
anyGapsWithinAtLeastDuration
  :: ( IntervalSizeable a b
     , Intervallic i0
     , IntervalCombinable i1 a
     , Monoid (t (Interval a))
     , Monoid (t (Maybe (Interval a)))
     , Applicative t
     , Witherable.Witherable t
     )
  => b       -- ^ duration of gap
  -> i0 a  -- ^ within this interval
  -> t (i1 a)
  -> Bool
anyGapsWithinAtLeastDuration = makeGapsWithinPredicate any (>=)

-- | Within a provided spanning interval, are all gaps less than the specified
--   duration among the input intervals?
--
-- >>> allGapsWithinLessThanDuration 30 (beginerval 100 (0::Int)) [beginerval 5 (-1), beginerval 99 10]
-- True
allGapsWithinLessThanDuration
  :: ( IntervalSizeable a b
     , Intervallic i0
     , IntervalCombinable i1 a
     , Monoid (t (Interval a))
     , Monoid (t (Maybe (Interval a)))
     , Applicative t
     , Witherable.Witherable t
     )
  => b       -- ^ duration of gap
  -> i0 a  -- ^ within this interval
  -> t (i1 a)
  -> Bool
allGapsWithinLessThanDuration = makeGapsWithinPredicate all (<)


-- Used to combine two lists by combining the last element of @x@ and the first
-- element of @y@ by @f@. The combining function @f@ will generally return a
-- singleton list in the case that the last of x and head of y can be combined
-- or a two element list in the case they cannot.
listCombiner
  :: (Maybe a -> Maybe a -> [a]) -- ^ f
  -> [a] -- ^ x
  -> [a] -- ^ y
  -> [a]
listCombiner f x y = initSafe x <> f (lastMay x) (headMay y) <> tailSafe y
{-# INLINABLE listCombiner #-}

-- | Returns a list of the 'IntervalRelation' between each consecutive pair
--   of intervals. This is just a specialized 'relations' which returns a list.
--
-- >>> relationsL [bi 1 0, bi 1 1]
-- [Meets]
--
relationsL
  :: (Foldable f, Ord a, Intervallic i) => f (i a) -> [IntervalRelation]
relationsL = relations

-- | A generic form of 'relations' which can output any 'Applicative' and
--   'Monoid' structure.
--
-- >>> (relations [bi 1 0,bi 1 1]) :: [IntervalRelation]
-- [Meets]
--
--
relations
  :: ( Foldable f
     , Applicative m
     , Ord a
     , Intervallic i
     , Monoid (m IntervalRelation)
     )
  => f (i a)
  -> m IntervalRelation
relations = L.fold (makeFolder relate)
{-# INLINABLE relations #-}

-- | Forms a 'Just' new interval from the intersection of two intervals,
--   provided the intervals are not disjoint.
--
-- >>> intersect (bi 5 0) (bi 2 3)
-- Just (3, 5)
--
intersect
  :: (Intervallic i, IntervalSizeable a b) => i a -> i a -> Maybe (Interval a)
intersect x y | disjoint x y = Nothing
              | otherwise    = Just $ beginerval (diff e b) b
 where
  b = max (begin x) (begin y)
  e = min (end x) (end y)

-- Internal function which folds over a structure by consecutive pairs, returing
-- gaps between each pair (@Nothing@ if no such gap exists).
gapsM
  :: ( IntervalCombinable i a
     , Traversable f
     , Monoid (f (Maybe (Interval a)))
     , Applicative f
     )
  => f (i a)
  -> f (Maybe (Interval a))
gapsM = L.fold (makeFolder (\i j -> getInterval i >< getInterval j))
{-# INLINABLE gapsM #-}

{- | Returns a @Maybe@ container of intervals consisting of the gaps between
intervals in the input. __To work properly, the input should be sorted.__ See
'gapsL' for a version that always returns a list.

>>> x1 = bi 4 1
>>> x2 = bi 4 8
>>> x3 = bi 3 11
>>> ivs = [x1, x2, x3]
>>> ivs
[(1, 5),(8, 12),(11, 14)]
>>> gaps ivs
Nothing
>>> pretty $ standardExampleDiagram (zip ivs ["x1", "x2", "x3"]) []
 ----          <- [x1]
        ----   <- [x2]
           --- <- [x3]
==============

>>> x1 = bi 4 1
>>> x2 = bi 3 7
>>> x3 = bi 2 13
>>> ivs = [x1, x2, x3]
>>> ivs
[(1, 5),(7, 10),(13, 15)]
>>> gapIvs = gaps ivs
>>> gapIvs
Just [(5, 7),(10, 13)]
>>> :{
case gapIvs of
  Nothing -> pretty ""
  (Just x) -> pretty $
    standardExampleDiagram (zip ivs ["x1", "x2", "x3"]) [(x, "gapIvs")]
:}
 ----           <- [x1]
       ---      <- [x2]
             -- <- [x3]
     --   ---   <- [gapIvs]
===============
-}
gaps
  :: ( IntervalCombinable i a
     , Traversable f
     , Monoid (f (Maybe (Interval a)))
     , Applicative f
     )
  => f (i a)
  -> Maybe (f (Interval a))
gaps = sequenceA . gapsM
{-# INLINABLE gaps #-}

{- | Returns a (possibly empty) list of intervals consisting of the gaps between
intervals in the input container.
__To work properly, the input should be sorted.__ This version outputs a list.
See 'gaps' for a version that lifts the result to same input structure @f@.

>>> x1 = bi 4 1
>>> x2 = bi 4 8
>>> x3 = bi 3 11
>>> ivs = [x1, x2, x3]
>>> ivs
[(1, 5),(8, 12),(11, 14)]
>>> gapIvs = gapsL ivs
>>> gapIvs
[]
>>> :{
pretty $ standardExampleDiagram (zip ivs ["x1", "x2", "x3"]) []
:}
 ----          <- [x1]
        ----   <- [x2]
           --- <- [x3]
==============

>>> x1 = bi 4 1
>>> x2 = bi 3 7
>>> x3 = bi 2 13
>>> ivs = [x1, x2, x3]
>>> ivs
[(1, 5),(7, 10),(13, 15)]
>>> gapIvs = gapsL ivs
>>> gapIvs
[(5, 7),(10, 13)]
>>> :{
pretty $
  standardExampleDiagram (zip ivs ["x1", "x2", "x3"]) [(gapIvs, "gapIvs")]
:}
 ----           <- [x1]
       ---      <- [x2]
             -- <- [x3]
     --   ---   <- [gapIvs]
===============
-}
gapsL
  :: ( IntervalCombinable i a
     , Applicative f
     , Monoid (f (Maybe (Interval a)))
     , Traversable f
     )
  => f (i a)
  -> [Interval a]
gapsL x = maybe [] toList (gaps x)
{-# INLINABLE gapsL #-}

-- | Returns the 'duration' of each 'Intervallic i a' in the 'Functor' @f@.
--
-- >>> durations [bi 9 1, bi 10 2, bi 1 5 :: Interval Int]
-- [9,10,1]
--
durations :: (Functor f, Intervallic i, IntervalSizeable a b) => f (i a) -> f b
durations = fmap duration

-- | In the case that x y are not disjoint, clips y to the extent of x.
--
-- >>> clip (bi 5 0) ((bi 3 3) :: Interval Int)
-- Just (3, 5)
--
-- >>> clip (bi 3 0) ((bi 2 4) :: Interval Int)
-- Nothing
--
clip
  :: (Intervallic i0, Intervallic i1, IntervalSizeable a b)
  => i0 a
  -> i1 a
  -> Maybe (Interval a)
clip x y
  | overlaps x y     = Just $ enderval (diff (end x) (begin y)) (end x)
  | overlappedBy x y = Just $ beginerval (diff (end y) (begin x)) (begin x)
  | jx x y           = Just (getInterval x)
  | jy x y           = Just (getInterval y)
  | otherwise        = Nothing {- disjoint x y case -}
 where
  jy = equals <|> startedBy <|> contains <|> finishedBy
  jx = starts <|> during <|> finishes
{-# INLINABLE clip #-}

-- | Applies 'gaps' to all the non-disjoint intervals in @x@ that are /not/ disjoint
-- from @i@. Intervals that 'overlaps' or are 'overlappedBy' @i@ are 'clip'ped
-- to @i@, so that all the intervals are 'within' @i@. If all of the input intervals
-- are disjoint from the focal interval or if the input is empty, then 'Nothing'
-- is returned. When there are no gaps among the concurring intervals, then
-- @Just mempty@ (e.g. @Just []@) is returned.
--
-- >>> gapsWithin (bi 9 1) [bi 5 0, bi 2 7, bi 3 12]
-- Just [(5, 7),(9, 10)]
--
gapsWithin
  :: ( Applicative f
     , Witherable f
     , Monoid (f (Interval a))
     , Monoid (f (Maybe (Interval a)))
     , IntervalSizeable a b
     , Intervallic i0
     , IntervalCombinable i1 a
     )
  => i0 a  -- ^ i
  -> f (i1 a) -- ^ x
  -> Maybe (f (Interval a))
gapsWithin i x | null ivs  = Nothing
               | otherwise = Just res
 where
  s   = pure (endervalFromBegin 0 i)
  e   = pure (beginervalFromEnd 0 i)
  ivs = mapMaybe (clip i) (filterNotDisjoint i x)
  res = catMaybes $ gapsM (s <> ivs <> e)
{-# INLINABLE gapsWithin #-}

{- | Returns a container of intervals where any intervals that meet or share
support are combined into one interval. This functions sorts the input intervals
first. See @combineIntervalsL@ for a version that works only on lists. If you
know the input intervals are sorted, use @combineIntervalsFromSorted@ instead.

>>> x1 = bi 10 0
>>> x2 = bi 5 2
>>> x3 = bi 2 10
>>> x4 = bi 2 13
>>> ivs = [x1, x2, x3, x4]
>>> ivs
[(0, 10),(2, 7),(10, 12),(13, 15)]
>>> xComb = combineIntervals ivs
>>> xComb
[(0, 12),(13, 15)]
>>> :{
pretty $
  standardExampleDiagram
    (zip ivs ["x1", "x2", "x3", "x4"])
    [(xComb, "xComb")]
:}
----------      <- [x1]
  -----         <- [x2]
          --    <- [x3]
             -- <- [x4]
------------ -- <- [xComb]
===============
-}
combineIntervals
  :: (Applicative f, Ord a, Intervallic i, Monoid (f (Interval a)), Foldable f)
  => f (i a)
  -> f (Interval a)
combineIntervals = combineIntervalsWith combineIntervalsL

{- | Returns a container of intervals where any intervals that meet or share
support are combined into one interval. The condition is applied cumulatively,
from left to right, so
__to work properly, the input list should be sorted in increasing order__. See
@combineIntervalsLFromSorted@ for a version that works only on lists.

>>> combineIntervalsFromSorted [bi 10 0, bi 5 2, bi 2 10, bi 2 13]
[(0, 12),(13, 15)]
-}
combineIntervalsFromSorted
  :: (Applicative f, Ord a, Intervallic i, Monoid (f (Interval a)), Foldable f)
  => f (i a)
  -> f (Interval a)
combineIntervalsFromSorted = combineIntervalsWith combineIntervalsFromSortedL

-- | Unexported helper
combineIntervalsWith
  :: (Applicative f, Ord a, Intervallic i, Monoid (f (Interval a)), Foldable f)
  => ([i a] -> [Interval a])
  -> f (i a)
  -> f (Interval a)
combineIntervalsWith f x = foldl' (\x y -> x <> pure y) mempty (f $ toList x)

{- | Returns a list of intervals where any intervals that meet or share support
are combined into one interval. This function sorts the input. If you know the
input intervals are sorted, use @combineIntervalsLFromSorted@.

>>> x1 = bi 10 0
>>> x2 = bi 5 2
>>> x3 = bi 2 10
>>> x4 = bi 2 13
>>> ivs = [x1, x2, x3, x4]
>>> ivs
[(0, 10),(2, 7),(10, 12),(13, 15)]
>>> xComb = combineIntervalsL ivs
>>> xComb
[(0, 12),(13, 15)]
>>> :{
pretty $
  standardExampleDiagram
    (zip ivs ["x1", "x2", "x3", "x4"])
    [(xComb, "xComb")]
:}
----------      <- [x1]
  -----         <- [x2]
          --    <- [x3]
             -- <- [x4]
------------ -- <- [xComb]
===============
-}
combineIntervalsL :: (Intervallic i, Ord a) => [i a] -> [Interval a]
combineIntervalsL = combineIntervalsFromSortedL . sortOn getInterval

{- | Returns a list of intervals where any intervals that meet or share support
are combined into one interval. The operation is applied cumulatively, from left
to right, so
__to work properly, the input list should be sorted in increasing order__.

>>> combineIntervalsFromSortedL [bi 10 0, bi 5 2, bi 2 10, bi 2 13]
[(0, 12),(13, 15)]

>>> combineIntervalsFromSortedL [bi 10 0, bi 5 2, bi 0 8]
[(0, 10)]
-}
combineIntervalsFromSortedL
  :: forall a i . (Ord a, Intervallic i) => [i a] -> [Interval a]
combineIntervalsFromSortedL = reverse . foldl' op []
 where
  op []       y = [getInterval y]
  op (x : xs) y = if x `before` y
    -- Since x <= y, not (x `before` y) iff they meet or share support
    then yiv : x : xs
    else extenterval x yiv : xs
    where yiv = getInterval y

{- | @Maybe@ form an @Interval a@ from @Control.Foldl t => t (Interval a)@
spanning the range of all intervals in the list, i.e. whose @begin@ is the
minimum of @begin@ across intervals in the list and whose @end@ is the maximum
of @end@.

>>> rangeInterval ([] :: [Interval Int])
Nothing

>>> x1 = bi 2 2
>>> x2 = bi 3 6
>>> x3 = bi 4 7
>>> ivs = [x1, x2, x3] :: [Interval Int]
>>> ivs
[(2, 4),(6, 9),(7, 11)]
>>> spanIv = rangeInterval ivs
>>> spanIv
Just (2, 11)
>>> :{
case spanIv of
  Nothing -> pretty ""
  (Just x) -> pretty $ standardExampleDiagram
    (zip (ivs ++ [x]) ["x1", "x2", "x3", "spanIv"])
    []
:}
  --        <- [x1]
      ---   <- [x2]
       ---- <- [x3]
  --------- <- [spanIv]
===========

>>> rangeInterval Nothing
Nothing
>>> rangeInterval (Just (bi 1 0))
Just (0, 1)
-}
rangeInterval :: (Ord a, L.Foldable t) => t (Interval a) -> Maybe (Interval a)
rangeInterval = L.fold (liftA2 extenterval <$> L.minimum <*> L.maximum)

-- | Given a predicate combinator, a predicate, and list of intervals, returns
--   the input unchanged if the predicate combinator is @True@. Otherwise, returns
--   an empty list. See 'nothingIfAny' and 'nothingIfNone' for examples.
nothingIf
  :: (Monoid (f (i a)), Filterable f)
  => ((i a -> Bool) -> f (i a) -> Bool) -- ^ e.g. 'any' or 'all'
  -> (i a -> Bool) -- ^ predicate to apply to each element of input list
  -> f (i a)
  -> Maybe (f (i a))
nothingIf quantifier predicate x =
  if quantifier predicate x then Nothing else Just x

-- | Returns the 'Nothing' if *none* of the element of input satisfy
--   the predicate condition.
--
-- For example, the following returns 'Nothing' because none of the intervals
-- in the input list 'starts' (3, 5).
--
-- >>> nothingIfNone (starts (bi 2 3)) [bi 1 3, bi 1 5]
-- Nothing
--
-- In the following, (3, 5) 'starts' (3, 6), so 'Just' the input is returned.
--
-- >>> nothingIfNone (starts (bi 2 3)) [bi 3 3, bi 1 5]
-- Just [(3, 6),(5, 6)]
--
nothingIfNone
  :: (Monoid (f (i a)), Foldable f, Filterable f)
  => (i a -> Bool) -- ^ predicate to apply to each element of input list
  -> f (i a)
  -> Maybe (f (i a))
nothingIfNone = nothingIf (\f x -> (not . any f) x)

-- | Returns 'Nothing' if *any* of the element of input satisfy the predicate condition.
--
-- >>> nothingIfAny (startedBy (bi 2 3)) [bi 3 3, bi 1 5]
-- Just [(3, 6),(5, 6)]
--
-- >>> nothingIfAny (starts (bi 2 3)) [bi 3 3, bi 1 5]
-- Nothing
--
nothingIfAny
  :: (Monoid (f (i a)), Foldable f, Filterable f)
  => (i a -> Bool) -- ^ predicate to apply to each element of input list
  -> f (i a)
  -> Maybe (f (i a))
nothingIfAny = nothingIf any

-- | Returns 'Nothing' if *all* of the element of input satisfy the predicate condition.
--
-- >>> nothingIfAll (starts (bi 2 3)) [bi 3 3, bi 4 3]
-- Nothing
--
nothingIfAll
  :: (Monoid (f (i a)), Foldable f, Filterable f)
  => (i a -> Bool) -- ^ predicate to apply to each element of input list
  -> f (i a)
  -> Maybe (f (i a))
nothingIfAll = nothingIf all

-- | Creates a function for filtering a 'Witherable.Filterable' of @i1 a@s
--   by comparing the @Interval a@s that of an @i0 a@.
makeFilter
  :: (Filterable f, Intervallic i0, Intervallic i1)
  => ComparativePredicateOf2 (i0 a) (i1 a)
  -> i0 a
  -> (f (i1 a) -> f (i1 a))
makeFilter f p = Witherable.filter (f p)

{- |
Filter 'Witherable.Filterable' containers of one @'Intervallic'@ type based by comparing to
a (potentially different) 'Intervallic' type using the corresponding interval
predicate function.
-}
filterOverlaps, filterOverlappedBy, filterBefore, filterAfter, filterStarts, filterStartedBy, filterFinishes, filterFinishedBy, filterMeets, filterMetBy, filterDuring, filterContains, filterEquals, filterDisjoint, filterNotDisjoint, filterConcur, filterWithin, filterEncloses, filterEnclosedBy
  :: (Filterable f, Ord a, Intervallic i0, Intervallic i1)
  => i0 a
  -> f (i1 a)
  -> f (i1 a)
filterOverlaps = makeFilter overlaps
filterOverlappedBy = makeFilter overlappedBy
filterBefore = makeFilter before
filterAfter = makeFilter after
filterStarts = makeFilter starts
filterStartedBy = makeFilter startedBy
filterFinishes = makeFilter finishes
filterFinishedBy = makeFilter finishedBy
filterMeets = makeFilter meets
filterMetBy = makeFilter metBy
filterDuring = makeFilter during
filterContains = makeFilter contains
filterEquals = makeFilter equals
filterDisjoint = makeFilter disjoint
filterNotDisjoint = makeFilter notDisjoint
filterConcur = makeFilter concur
filterWithin = makeFilter within
filterEncloses = makeFilter encloses
filterEnclosedBy = makeFilter enclosedBy

-- | Folds over a list of Paired Intervals and in the case that the 'getPairData'
--   is equal between two sequential meeting intervals, these two intervals are
--   combined into one. This function is "safe" in the sense that if the input is
--   invalid and contains any sequential pairs of intervals with an @IntervalRelation@,
--   other than 'Meets', then the function returns an empty list.
foldMeetingSafe
  :: (Eq b, Ord a, Show a)
  => [PairedInterval b a] -- ^ Be sure this only contains intervals
                                  --   that sequentially 'meets'.
  -> [PairedInterval b a]
foldMeetingSafe l = maybe [] (getMeeting . foldMeeting) (parseMeeting l)

-- | Folds over a list of Meeting Paired Intervals and in the case that the 'getPairData'
--   is equal between two sequential meeting intervals, these two intervals are
--   combined into one.
foldMeeting
  :: (Eq b, Ord a, Show a)
  => Meeting [PairedInterval b a]
  -> Meeting [PairedInterval b a]
foldMeeting (Meeting l) =
  foldl' joinMeetingPairedInterval (Meeting []) (packMeeting l)

-- This type identifies that @a@ contains intervals that sequentially meet one
-- another.
newtype Meeting a = Meeting { getMeeting :: a } deriving (Eq, Show)

-- Box up Meeting.
packMeeting :: [a] -> [Meeting [a]]
packMeeting = fmap (\z -> Meeting [z])

-- Test a list of intervals to be sure they all meet; if not return Nothing.
parseMeeting :: (Ord a, Intervallic i) => [i a] -> Maybe (Meeting [i a])
parseMeeting x | all (== Meets) (relationsL x) = Just $ Meeting x
               | otherwise                     = Nothing

-- A specific case of 'joinMeeting' for @PairedIntervals@.
joinMeetingPairedInterval
  :: (Eq b, Ord a, Show a)
  => Meeting [PairedInterval b a]
  -> Meeting [PairedInterval b a]
  -> Meeting [PairedInterval b a]
joinMeetingPairedInterval = joinMeeting equalPairData

-- A general function for combining any two @Meeting [i a]@ by 'listCombiner'.
joinMeeting
  :: (Ord a, Intervallic i)
  => ComparativePredicateOf1 (i a)
  -> Meeting [i a]
  -> Meeting [i a]
  -> Meeting [i a]
joinMeeting f (Meeting x) (Meeting y) =
  Meeting $ listCombiner (join2MeetingWhen f) x y

-- The intervals @x@ and @y@ should meet! The predicate function @p@ determines
-- when the two intervals that meet should be combined.
join2MeetingWhen
  :: (Ord a, Intervallic i)
  => ComparativePredicateOf1 (i a)
  -> Maybe (i a)
  -> Maybe (i a)
  -> [i a]
join2MeetingWhen p Nothing  Nothing  = []
join2MeetingWhen p Nothing  (Just y) = [y]
join2MeetingWhen p (Just x) Nothing  = [x]
join2MeetingWhen p (Just x) (Just y) | p x y = [setInterval y (extenterval x y)]
                                     | otherwise = pure x <> pure y

{- |
Takes two *ordered* events, x <= y, and "disjoins" them in the case that the
two events have different states, creating a sequence (list) of new events that
sequentially meet one another. Since x <= y, there are 7 possible interval
relations between x and y. If the states of x and y are equal and x is not
before y, then x and y are combined into a single event.
-}
disjoinPaired
  :: (Eq b, Monoid b, Show a, IntervalSizeable a c)
  => (PairedInterval b) a
  -> (PairedInterval b) a
  -> Meeting [(PairedInterval b) a]
disjoinPaired o e = case relate x y of
  Before     -> Meeting [x, evp e1 b2 mempty, y]
  Meets      -> foldMeeting $ Meeting [x, y]
  Overlaps   -> foldMeeting $ Meeting [evp b1 b2 s1, evp b2 e1 sc, evp e1 e2 s2]
  FinishedBy -> foldMeeting $ Meeting [evp b1 b2 s1, ev i2 sc]
  Contains   -> foldMeeting $ Meeting [evp b1 b2 s1, evp b2 e2 sc, evp e2 e1 s1]
  Starts     -> foldMeeting $ Meeting [ev i1 sc, evp e1 e2 s2]
  _          -> Meeting [ev i1 sc] {- Equals case -}
 where
  x  = min o e
  y  = max o e
  i1 = getInterval x
  i2 = getInterval y
  s1 = getPairData x
  s2 = getPairData y
  sc = s1 <> s2
  b1 = begin x
  b2 = begin y
  e1 = end x
  e2 = end y
  ev = flip makePairedInterval
  evp b e = ev (beginerval (diff e b) b)
{-# INLINABLE disjoinPaired #-}

{- |
The internal function for converting a non-disjoint, ordered sequence of
events into a disjoint, ordered sequence of events. The function operates
by recursion on a pair of events and the input events. The first of the
is the accumulator set -- the disjoint events that need no longer be
compared to input events. The second of the pair are disjoint events that
still need to be compared to be input events.
-}
recurseDisjoin
  :: (Monoid b, Eq b, IntervalSizeable a c, Show a)
  => ([(PairedInterval b) a], [(PairedInterval b) a])
  -> [(PairedInterval b) a]
  -> [(PairedInterval b) a]
recurseDisjoin (acc, o : os) []       = acc <> (o : os)           -- the "final" pattern
recurseDisjoin (acc, []    ) []       = acc                 -- another "final" pattern
recurseDisjoin (acc, []    ) (e : es) = recurseDisjoin (acc, [e]) es -- the "initialize" pattern
recurseDisjoin (acc, o : os) (e : es)
  |                       -- the "operating" patterns
     -- If input event is equal to the first comparator, skip the comparison.
    e == o = recurseDisjoin (acc, o : os) es
  |

     {- If o is either before or meets e, then
     the first of the combined events can be put into the accumulator.
     That is, since the inputs events are ordered, once the beginning of o
     is before or meets e, then we are assured that all periods up to the
     beginning of o are fully disjoint and subsequent input events will
     not overlap these in any way. -}
    (before <|> meets) o e = recurseDisjoin
    (acc <> nh, recurseDisjoin ([], nt) os)
    es
  |

    --The standard recursive operation.
    otherwise = recurseDisjoin (acc, recurseDisjoin ([], n) os) es
 where
  n  = getMeeting $ disjoinPaired o e
  nh = maybeToList (headMay n)
  nt = tailSafe n
{-# INLINABLE recurseDisjoin #-}

{- |
Convert an ordered sequence of @PairedInterval b a@. that may have any interval relation
('before', 'starts', etc) into a sequence of sequentially meeting @PairedInterval b a@.
That is, a sequence where one the end of one interval meets the beginning of
the subsequent event. The 'getPairData' of the input @PairedIntervals@ are
combined using the Monoid '<>' function, hence the pair data must be a
'Monoid' instance.
-}
formMeetingSequence
  :: (Eq b, Show a, Monoid b, IntervalSizeable a c)
  => [PairedInterval b a]
  -> [PairedInterval b a]
formMeetingSequence x
  | null x = []
  | allMeet x && not (hasEqData x) = x
  | otherwise = formMeetingSequence (recurseDisjoin ([], []) x)
  -- recurseDisjoin ([], []) (recurseDisjoin ([], []) (recurseDisjoin ([], []) x))

   -- the multiple passes of recurseDisjoin is to handle the situation where the
   -- initial passes almost disjoins all the events correctly into a meeting sequence
   -- but due to nesting of intervals in the input -- some of the sequential pairs have
   -- the same data after the first pass. The recursive passes merges any sequential
   -- intervals that have the same data.
   --
   -- There is probably a more efficient way to do this
{-# INLINABLE formMeetingSequence #-}

allMeet :: (Ord a) => [PairedInterval b a] -> Bool
allMeet x = all (== Meets) (relationsL x)

hasEqData :: (Eq b) => [PairedInterval b a] -> Bool
hasEqData x = or (L.fold (makeFolder (==)) (fmap getPairData x) :: [Bool])