{-# LANGUAGE ScopedTypeVariables #-}
module Sound.SC3.Server.Allocator.Range (
    Range
  , range
  , sized
  , empty
  , begin
  , end
  , last
  , size
  , null
  , toList
  -- *Interval operations
  , within
  , adjoins
  , overlaps
  , contains
  , split
  , join
) where

import Prelude hiding (last, null)

-- $setup
-- >>> import Control.Applicative ((<$>), (<*>))
-- >>> import Control.Monad (liftM)
-- >>> import System.Random (Random)
-- >>> import Test.QuickCheck (Arbitrary(..))
-- >>> instance (Arbitrary i, Ord i) => Arbitrary (Range i) where arbitrary = Range <$> arbitrary <*> arbitrary

-- | Open ended interval [begin, end).
data Range a = Range {
    begin :: !a
  , end :: !a
  } deriving (Eq)

instance Show a => Show (Range a) where
  show r = unwords ["Range", show (begin r), show (end r)]

mkRange :: a -> a -> Range a
mkRange a b = Range a b

-- | Construct a range from a lower bound (included) and an upper bound (excluded).
--
-- prop> \(r :: Range Int) -> begin r == end r
range :: Ord a => a -> a -> Range a
range a b | a <= b    = mkRange a b
          | otherwise = mkRange b a

-- | Construct a range from a size and a lower bound.
--
-- >>> sized 20 10
-- Range 10 30
sized :: Num a => Int -> a -> Range a
sized n a = mkRange a (a + fromIntegral n)

-- | The empty range starting at some value.
--
-- >>> empty 10
-- Range 10 10
--
-- null (empty 10)
-- True
--
-- size (empty 10)
-- 0
empty :: Num a => a -> Range a
empty a = mkRange a a

-- | The last value in the range.
--
-- >>> last (range 10 20)
-- 19
last :: Enum a => Range a -> a
last = pred . end

-- | The size of the range.
--
-- >>> size (range 10 20)
-- 10
--
-- >>> size (sized 100 10)
-- 100
size :: Integral a => Range a -> Int
size a = fromIntegral (end a - begin a)

-- | True if range is empty.
--
-- >>> null (range 10 10)
-- True
--
-- >>> null (range 10 20)
-- False
null :: Eq a => Range a -> Bool
null a = begin a == end a

-- | Convert range to a list of its values.
toList :: Enum a => Range a -> [a]
toList a = [begin a..last a]

-- | Return true if a given value is contained within the range.
--
-- >>> within 12 (sized 3 10)
-- True
--
-- >>> within 20 (range 10 20)
-- False
--
-- >>> within 30 (range 30 30)
-- False
within :: Ord a => a -> Range a -> Bool
x `within` a = x >= begin a && x < end a

-- | Return true if two ranges adjoin each other.
--
-- >>> range 10 20 `adjoins` range 20 30
-- True
--
-- >>> range 10 20 `adjoins` range 21 30
-- False
adjoins :: Eq a => Range a -> Range a -> Bool
a `adjoins` b = (end a == begin b) || (end b == begin a)

-- | Return true if two ranges overlap each other.
overlaps :: Ord a => Range a -> Range a -> Bool
a `overlaps` b = (end a > begin b) || (end b > begin a)

-- | Return true if the second range lies completely within the first range.
contains :: Ord a => Range a -> Range a -> Bool
a `contains` b = begin b >= begin a && end b <= end a

-- | Split a range at an index.
--
-- >>> let (r1, r2) = split 6 (range 10 20)
-- >>> size r1
-- 6
-- >>> size r2
-- 4
--
-- >>> let (r1, r2) = split 6 (empty 6)
-- >>> null r1 && null r2
-- True
--
-- >>> let (r1, r2) = split 10 (sized 4 10)
-- >>> size r1
-- 4
-- >>> size r2
-- 0
split :: Integral a => Int -> Range a -> (Range a, Range a)
split n r@(Range l u)
    | n <= 0 = (empty l, r)
    | n >= size r = (r, empty u)
    | otherwise = (mkRange l (l+k), mkRange (l+k) u)
    where k = fromIntegral n

join :: Ord a => Range a -> Range a -> Range a
join a b = mkRange (min (begin a) (begin b)) (max (end a) (end b))