{- |
Module      :  Data.RangeSet.Internal
Description :  Support functions for dealing with distinct ordered range lists
Copyright   :  (c) Dylan Simon 2015
License     :  MIT

Maintainer  :  oleg.grenrus@iki.fi
Stability   :  experimental
Portability :  non-portable (tested with GHC only)

Most functions in this module deal with normalized (closed, fst <= snd,
non-overlapping, non-adjacent, ordered) ranges, but do not check this
assumption.  Most users should use a higher-level interface.
-}
{-# LANGUAGE Safe               #-}
module Data.RangeSet.Internal
  ( rangeSize
  , rangeIsSubsetList
  , isSubsetRangeList
  , insertRangeList
  , deleteRangeList
  , unionRangeList
  , differenceRangeList
  , intersectRangeList
  , complementRangeList
  , fromAscElemList
  , fromElemList
  , normalizeRangeList
  , validRangeList
  ) where

import Data.List   (sort)
import Data.Monoid (Sum (..))

-- | Determine the number of items in an 'Enum' range as a 'Sum'
rangeSize :: Enum a => a -> a -> Sum Int
rangeSize a b = Sum $ succ $ fromEnum b - fromEnum a

-- | Determine if @[x,y]@ is a subset of the list, returning the list right of
-- @y@ if so.
rangeIsSubsetList :: Ord a => a -> a -> [(a, a)] -> Maybe [(a, a)]
rangeIsSubsetList x y ((u,v):s)
  | x < u = Nothing
  | y <= v = Just ((y,v):s)
  | otherwise = rangeIsSubsetList x y s
rangeIsSubsetList _ _ [] = Nothing

-- | Determine if the first list is a subset of the second.
isSubsetRangeList :: Ord a => [(a, a)] -> [(a, a)] -> Bool
isSubsetRangeList ((x,y):as) bs = maybe False (isSubsetRangeList as) $ rangeIsSubsetList x y bs
isSubsetRangeList [] _ = True

-- | Add @[x,y]@.
--
-- There are three possibilities we consider, when inserting into non-empty set:
--
-- * discretely after: continue
-- * discretely before: prepend
-- * overlapping: union and prepend
insertRangeList :: (Ord a, Enum a) => a -> a -> [(a, a)] -> [(a, a)]
insertRangeList x y set@(uv@(u,v) : xs)
  | v < x && succ v /= x = uv : insertRangeList x y xs
  | y < u && succ y /= u = (x,y) : set
  | otherwise            = prependRangeList (min x u) (max y v) xs
insertRangeList x y [] = [(x,y)]

-- | Add @[x,y]@ to the beginning (assuming @x <= u@).
prependRangeList :: (Ord a, Enum a) => a -> a -> [(a, a)] -> [(a, a)]
prependRangeList x y set@((u,v) : xs)
  | y < u && succ y /= u = (x,y) : set
  | otherwise            = prependRangeList x (max y v) xs
prependRangeList x y [] = [(x,y)]

-- | Union two range lists.
unionRangeList :: (Ord a, Enum a) => [(a, a)] -> [(a, a)] -> [(a, a)]
unionRangeList aset@(xy@(x,y):as) bset@(uv@(u,v):bs)
  | y < u && succ y /= u = xy : unionRangeList as bset
  | v < x && succ v /= x = uv : unionRangeList aset bs
  | otherwise = prependRangeList (min x u) (max y v) $ unionRangeList as bs
unionRangeList s [] = s
unionRangeList [] s = s

-- | Remove a range from a range list.
--
-- There are 6 possibilities we consider, when deleting from non-empty set:
--
-- * more
-- * less
-- * strictly inside (splits)
-- * overlapping less-edge
-- * overlapping more-edge
-- * stricly larger
deleteRangeList :: (Ord a, Enum a) => a -> a -> [(a, a)] -> [(a, a)]
deleteRangeList x y set@(s@(u,v) : xs)
  | v < x = s : deleteRangeList x y xs
  | y < u = set
  | u < x = (u, pred x) : t
  | otherwise = t where
  t = trimRangeList' y v xs
deleteRangeList _ _ [] = []

-- | Remove @(,y]@ while (re-)adding @(y,v]@ if valid
trimRangeList' :: (Ord a, Enum a) => a -> a -> [(a, a)] -> [(a, a)]
trimRangeList' y v xs
  | y < v = (succ y, v) : xs
  | otherwise = trimRangeList y xs

-- | Remove @(,y]@
trimRangeList :: (Ord a, Enum a) => a -> [(a, a)] -> [(a, a)]
trimRangeList y set@((u,v) : xs)
  | y < u = set
  | otherwise = trimRangeList' y v xs
trimRangeList _ [] = []

-- | Compute the set difference, removing each range in the second list from
-- the first.
differenceRangeList :: (Ord a, Enum a) => [(a, a)] -> [(a, a)] -> [(a, a)]
differenceRangeList aset@(xy@(x,y):as) bset@((u,v):bs)
  | y < u = xy : differenceRangeList as bset
  | v < x = differenceRangeList aset bs
  | x < u = (x, pred u) : t
  | otherwise = t where
  t = differenceRangeList (trimRangeList' v y as) bs
differenceRangeList s [] = s
differenceRangeList [] _ = []

-- | Compute the intersection.
intersectRangeList :: Ord a => [(a, a)] -> [(a, a)] -> [(a, a)]
intersectRangeList aset@((x,y):as) bset@((u,v):bs)
  | y < u = intersectRangeList as bset
  | v < x = intersectRangeList aset bs
  | y < v = (max x u, y) : intersectRangeList as bset
  | otherwise = (max x u, v) : intersectRangeList aset bs
intersectRangeList _ [] = []
intersectRangeList [] _ = []

-- | Compute the complement intersected with @[x,)@ assuming @x<u@.
complementRangeList' :: (Ord a, Enum a, Bounded a) => a -> [(a, a)] -> [(a, a)]
complementRangeList' x ((u,v):s) = (x,pred u) : complementRangeList'' v s
complementRangeList' x [] = [(x,maxBound)]

-- | Compute the complement intersected with @(x,)@.
complementRangeList'' :: (Ord a, Enum a, Bounded a) => a -> [(a, a)] -> [(a, a)]
complementRangeList'' x s
  | x == maxBound = []
  | otherwise = complementRangeList' (succ x) s

-- | Compute the complement.
complementRangeList :: (Ord a, Enum a, Bounded a) => [(a, a)] -> [(a, a)]
complementRangeList s@((x,y):s')
  | x == minBound = complementRangeList'' y s'
  | otherwise = complementRangeList' minBound s
complementRangeList [] = [(minBound, maxBound)]

-- | Take elements off the beginning of the list while they are equal or
-- adjacent to the given item, and return the last removed item and remaining
-- list.
takeWhileAdj :: (Eq a, Enum a) => a -> [a] -> (a, [a])
takeWhileAdj x yl@(y:l)
  | x == y || succ x == y = takeWhileAdj y l
  | otherwise = (x, yl)
takeWhileAdj x [] = (x, [])

-- | Take ranges off the beginning of a unnormalized but sorted and valid range
-- list while they are overlapping or adjacent to the given value, and return
-- the last removed item and remaining list.
takeWhileRangeAdj :: (Ord a, Enum a) => a -> [(a,a)] -> (a, [(a,a)])
takeWhileRangeAdj x yzl@((y,z):l)
  | x >= y || succ x == y = takeWhileRangeAdj (max x z) l
  | otherwise = (x, yzl)
takeWhileRangeAdj x [] = (x, [])

-- | Normalize a sorted list of elements to a range list.
fromAscElemList :: (Eq a, Enum a) => [a] -> [(a, a)]
fromAscElemList (x:l) = (x, y) : fromAscElemList l' where
  (y, l') = takeWhileAdj x l
fromAscElemList [] = []

-- | Normalize an arbitrary list of elements to a range list.
fromElemList :: (Ord a, Enum a) => [a] -> [(a, a)]
fromElemList = fromAscElemList . sort

-- | Normalize a sorted list of valid ranges.
mergeRangeList :: (Ord a, Enum a) => [(a, a)] -> [(a, a)]
mergeRangeList ((x,y):l) = (x,y') : mergeRangeList l' where
  (y', l') = takeWhileRangeAdj y l
mergeRangeList [] = []

-- | Normalize an arbitrary list of ranges.
normalizeRangeList :: (Ord a, Enum a) => [(a, a)] -> [(a, a)]
normalizeRangeList = mergeRangeList . sort . filter valid where
  valid (x,y) = x <= y

-- | Check if a list is normalized and strictly above @b@.
validRangeList' :: (Ord a, Enum a, Bounded a) => a -> [(a, a)] -> Bool
validRangeList' b ((x,y):s) = b < maxBound && succ b < x && x <= y && validRangeList' y s
validRangeList' _ [] = True

-- | Check if a list is normalized.
validRangeList :: (Ord a, Enum a, Bounded a) => [(a, a)] -> Bool
validRangeList ((x,y):s) = x <= y && validRangeList' y s
validRangeList [] = True