{-|
Module         : Data.Range
Description    : An inclusive, non-empty range.
Copyright      : (c) 2016 Micxjo Funkcio
License        : BSD3
Maintainer     : micxjo@fastmail.com
Stability      : experimental
-}
module Data.Range
       ( -- * Range type
         Range
         -- * Construction
       , singleton
       , range
         -- * Query
       , member
       , notMember
       , rangeMin
       , rangeMax
       , isSingleton
       , size
         -- * Combine
       , intersection
         -- * Conversion
       , toList
       , toAscList
       , toDescList
       ) where

import Data.Semigroup (Semigroup(..))

-- | An inclusive, non-empty range.
data Range a = Range
               { _start :: !a
               , _end :: !a
               } deriving (Eq, Show)

-- | Create a singleton range.
singleton :: a -> Range a
singleton a = Range a a

-- | Create a range from (min a b) to (max a b), inclusive.
range :: Ord a => a -> a -> Range a
range start end
  | start <= end = Range start end
  | otherwise = Range end start

instance Ord a => Semigroup (Range a) where
  (<>) (Range start1 end1) (Range start2 end2) =
    Range (min start1 start2) (max end1 end2)

  stimes n r
    | n <= 0 = error "stimes: positive multiplier expected"
    | otherwise = r

-- | The intersection of two ranges. If the ranges are disjoint, Nothing.
intersection :: Ord a => Range a -> Range a -> Maybe (Range a)
intersection (Range start1 end1) (Range start2 end2) =
  if start > end
     then Nothing
     else Just (Range start end)
  where start = max start1 start2
        end = min end1 end2

overlap :: Ord a => Range a -> Range a -> Bool
overlap (Range start1 end1) (Range start2 end2) =
  (start1 >= start2 && start1 <= end2) ||
  (start2 >= start1 && start2 <= end1)

-- | The minimum element of the range.
rangeMin :: Range a -> a
rangeMin (Range a _) = a

-- | The maximum element of the range.
rangeMax :: Range a -> a
rangeMax (Range _ a) = a

-- | The number of elements in the range.
size :: Num a => Range a -> a
size (Range start end) = end - start + 1

-- | Does the range contain only  a single element?
isSingleton :: Eq a => Range a -> Bool
isSingleton (Range a b) = a == b

-- | All of the elements of the range.
toList :: Enum a => Range a -> [a]
toList (Range start end) = [start..end]

-- | All of the elemens of the range, in ascending order.
toAscList :: Enum a => Range a -> [a]
toAscList = toList

-- | All of the elements of the range, in descending order.
toDescList :: Enum a => Range a -> [a]
toDescList = reverse . toList

-- | Is the element in the range?
member :: Ord a => a -> Range a -> Bool
member a (Range start end) = a >= start && a <= end

-- | Is the element not in the range?
notMember :: Ord a => a -> Range a -> Bool
notMember a = not . member a