content: {-# LANGUAGE DeriveFunctor #-}
  
  module Cardano.Wallet.Primitive.Types.Range
      ( Range (..)
      , RangeBound (..)
      , mapLowerBound
      , mapUpperBound
      , everything
      , isAfter
      , isBefore
      , isWithin
      , hasLowerBound
      , hasUpperBound
      , isFinite
      , isSingleton
      , isValid
      , lowerBound
      , upperBound
      , isSubrangeOf
      )
  where
  
  import Prelude
  
  import Data.Maybe
      ( isJust
      )
  
  -- | Represents a range of values.
  --
  -- A range is defined by two /optional/ bounds:
  --
  -- 1. an /inclusive/ lower bound
  -- 2. an /inclusive/ upper bound
  --
  -- There are four cases:
  --
  -- +---------------------------------+-------------+---------------------------+
  -- | Value                           | Range       | Membership                |
  -- |                                 | Represented | Function                  |
  -- +=================================+=============+===========================+
  -- | @'Range' ('Just' x) ('Just' y)@ | @[ x, y ]@  | @\\p -> p >= x && p <= y@ |
  -- +---------------------------------+-------------+---------------------------+
  -- | @'Range' ('Just' x) 'Nothing' @ | @[ x, ∞ )@  | @\\p -> p >= x          @ |
  -- +---------------------------------+-------------+---------------------------+
  -- | @'Range' 'Nothing'  ('Just' y)@ | @(−∞, y ]@  | @\\p -> p <= y          @ |
  -- +---------------------------------+-------------+---------------------------+
  -- | @'Range' 'Nothing'  'Nothing' @ | @(−∞, ∞ )@  | @\\p -> True            @ |
  -- +---------------------------------+-------------+---------------------------+
  --
  data Range a = Range
      { inclusiveLowerBound :: Maybe a
      , inclusiveUpperBound :: Maybe a
      } deriving (Eq, Functor, Show)
  
  -- | Apply a function to the lower bound of a range.
  mapLowerBound :: (a -> a) -> Range a -> Range a
  mapLowerBound f (Range x y) = Range (f <$> x) y
  
  -- | Apply a function to the upper bound of a range.
  mapUpperBound :: (a -> a) -> Range a -> Range a
  mapUpperBound f (Range x y) = Range x (f <$> y)
  
  -- | Represents a range boundary.
  data RangeBound a
      = NegativeInfinity
      | InclusiveBound a
      | PositiveInfinity
      deriving (Eq, Ord)
  
  -- | The range that includes everything.
  everything :: Range a
  everything = Range Nothing Nothing
  
  -- | Returns 'True' if (and only if) the given range has an upper bound and the
  --   specified value is greater than the upper bound.
  isAfter :: Ord a => a -> Range a -> Bool
  isAfter x (Range _ high) =
      maybe False (x >) high
  
  -- | Returns 'True' if (and only if) the given range has a lower bound and the
  --   specified value is smaller than the lower bound.
  isBefore :: Ord a => a -> Range a -> Bool
  isBefore x (Range low _) =
      maybe False (x <) low
  
  -- | Returns 'True' if (and only if) the given value is not smaller than the
  --   lower bound (if present) of the given range and is not greater than the
  --   upper bound (if present) of the given range.
  isWithin :: Ord a => a -> Range a -> Bool
  isWithin x (Range low high) =
      (maybe True (x >=) low) &&
      (maybe True (x <=) high)
  
  -- | Returns 'True' if (and only if) the given range has a lower bound.
  hasLowerBound :: Range a -> Bool
  hasLowerBound = isJust . inclusiveLowerBound
  
  -- | Returns 'True' if (and only if) the given range has an upper bound.
  hasUpperBound :: Range a -> Bool
  hasUpperBound = isJust . inclusiveUpperBound
  
  -- | Returns 'True' if (and only if) the given range has both a lower and upper
  --   bound.
  isFinite :: Range a -> Bool
  isFinite r = hasLowerBound r && hasUpperBound r
  
  -- | Returns 'True' if (and only if) the range covers exactly one value.
  isSingleton :: Eq a => Range a -> Bool
  isSingleton (Range a b) = ((==) <$> a <*> b) == Just True
  
  -- | Returns 'True' if (and only if) the lower bound of a range is not greater
  --   than its upper bound.
  isValid :: Ord a => Range a -> Bool
  isValid (Range a b) = ((<=) <$> a <*> b) /= Just False
  
  -- | Get the lower bound of a 'Range'.
  lowerBound :: Range a -> RangeBound a
  lowerBound = maybe NegativeInfinity InclusiveBound . inclusiveLowerBound
  
  -- | Get the upper bound of a 'Range'.
  upperBound :: Range a -> RangeBound a
  upperBound = maybe PositiveInfinity InclusiveBound . inclusiveUpperBound
  
  -- | Returns 'True' if (and only if) the first given range is a subrange of the
  --   second given range.
  isSubrangeOf :: Ord a => Range a -> Range a -> Bool
  isSubrangeOf r1 r2 =
      lowerBound r1 >= lowerBound r2 &&
      upperBound r1 <= upperBound r2
  