{-# LANGUAGE ScopedTypeVariables #-}
-- | This internal module provides functions used to define the various
-- @enumFrom*@ functions of 'Enum'.
--
-- We expect 'fromEnum' to be an ordering homomorphism, that is:
--
-- @
-- forall a b. Enum a b
-- succ a == b => fromEnum a < fromEnum b
-- @
--
-- Note that this homomorphism is most likely not surjective.  Note further that
-- one cannot assume:
--
-- @
-- CANNOT BE ASSUMED !
-- succ a == b => fromEnum a + 1 == fromEnum b
-- @
--
-- The 'succ' essor of a given message enum value @A@ that's not 'maxBound' is
-- the enum value @B@ whose 'fromEnum' value is the one immediately after @A@'s
-- 'fromEnum' value.  That is, 'fromEnum' determines order, but not distance.
--
-- As an example, consider the enum in the test suite:
--
-- @
-- enum Baz {
--     BAZ1 = 1; BAZ2 = 2; BAZ3 = 4; BAZ4 = 6;
--     BAZ5 = 7; BAZ6 = 9; BAZ7 = 10; BAZ8 = 12;
-- }
-- @
--
-- In this case, @succ BAZ2@ is @BAZ3@ despite their fromEnum values differing
-- by 2.  Further, @[BAZ2, BAZ4 ..]@ or equivalently
-- @messageEnumFromThen BAZ2 BAZ4@ is every other enum (i.e. a distance of 2)
-- when taken as a list, i.e.  @[BAZ2, BAZ4, BAZ6, BAZ8]@ despite the
-- 'fromEnum' distances being @[4, 3, 3]@.
--
-- That said, it is highly unwise to use any of the @[a,b ..*]@ patterns or
-- @enumFromThen*@ functions since adding or removing enums values can cause
-- previously functioning code to fail.  I.e. removing @BAZ3@ in the above
-- example makes the result equivalent @fromEnum BAZ2@ and the sequence now
-- includes every enum value save @BAZ1@.  This is all despite the fact that
-- @BAZ3@ was never referenced.
module Data.ProtoLens.Message.Enum
    ( messageEnumFrom
    , messageEnumFromTo
    , messageEnumFromThen
    , messageEnumFromThenTo
    ) where

import Data.List (unfoldr)
import Data.Ord (comparing)

messageEnumFromTo :: Enum a => a -> a -> [a]
messageEnumFromTo :: forall a. Enum a => a -> a -> [a]
messageEnumFromTo a
start a
stop = case forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing forall a. Enum a => a -> Int
fromEnum a
start a
stop of
    Ordering
LT -> forall a. Enum a => a -> a -> a -> [a]
messageEnumFromThenTo a
start (forall a. Enum a => a -> a
succ a
start) a
stop
    -- The only time we can't call 'succ' on @start@ is if it's 'maxBound' which
    -- is >= all values by definition, so the below cases cover all possible
    -- cases where succ cannot be called.
    Ordering
EQ -> [a
start]
    Ordering
GT -> []

messageEnumFrom :: (Enum a, Bounded a) => a -> [a]
messageEnumFrom :: forall a. (Enum a, Bounded a) => a -> [a]
messageEnumFrom = forall a b c. (a -> b -> c) -> b -> a -> c
flip forall a. Enum a => a -> a -> [a]
messageEnumFromTo forall a. Bounded a => a
maxBound

messageEnumFromThen :: (Enum a, Bounded a) => a -> a -> [a]
messageEnumFromThen :: forall a. (Enum a, Bounded a) => a -> a -> [a]
messageEnumFromThen a
start a
step = case forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing forall a. Enum a => a -> Int
fromEnum a
start a
step of
    Ordering
LT -> forall a. Enum a => a -> a -> a -> [a]
messageEnumFromThenTo a
start a
step forall a. Bounded a => a
maxBound
    Ordering
EQ -> forall a. a -> [a]
repeat a
start
    Ordering
GT -> forall a. Enum a => a -> a -> a -> [a]
messageEnumFromThenTo a
start a
step forall a. Bounded a => a
minBound

messageEnumFromThenTo :: forall a . Enum a => a -> a -> a -> [a]
messageEnumFromThenTo :: forall a. Enum a => a -> a -> a -> [a]
messageEnumFromThenTo a
start a
step a
stop = case forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing forall a. Enum a => a -> Int
fromEnum a
start a
step of
    Ordering
LT -> (a -> a) -> Ordering -> [a]
helper forall a. Enum a => a -> a
succ Ordering
GT
    Ordering
EQ -> if Int
stopInt forall a. Ord a => a -> a -> Bool
>= Int
stepInt then forall a. a -> [a]
repeat a
start else []
    Ordering
GT -> (a -> a) -> Ordering -> [a]
helper forall a. Enum a => a -> a
pred Ordering
LT
  where
    stopInt :: Int
stopInt = forall a. Enum a => a -> Int
fromEnum a
stop
    stepInt :: Int
stepInt = forall a. Enum a => a -> Int
fromEnum a
step
    helper :: (a -> a) -> Ordering -> [a]
helper a -> a
iter Ordering
isAfter
        | forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing forall a. Enum a => a -> Int
fromEnum a
start a
stop forall a. Eq a => a -> a -> Bool
== Ordering
isAfter = []
        | forall a. Ord a => a -> a -> Ordering
compare Int
stepInt Int
stopInt forall a. Eq a => a -> a -> Bool
== Ordering
isAfter = [a
start]
        | Bool
otherwise = a
start forall a. a -> [a] -> [a]
: forall b a. (b -> Maybe (a, b)) -> b -> [a]
unfoldr (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> (a, Maybe a)
unfoldIter) (forall a. a -> Maybe a
Just a
step)
      where
        -- This applies @iter@ (which is either succ or pred depending on our
        -- direction) @n@ times.  This returns @Just@ the result unless we've
        -- passed @stop@, in which case we return @Nothing@.
        jump :: t -> a -> Maybe a
jump t
0 a
a = forall a. a -> Maybe a
Just a
a
        jump t
n a
a
            | Int
stopInt forall a. Eq a => a -> a -> Bool
== forall a. Enum a => a -> Int
fromEnum a
a = forall a. Maybe a
Nothing
            | Bool
otherwise = t -> a -> Maybe a
jump (t
nforall a. Num a => a -> a -> a
-t
1) forall a b. (a -> b) -> a -> b
$ a -> a
iter a
a
        unfoldIter :: a -> (a, Maybe a)
unfoldIter a
a = (a
a, forall {t}. (Eq t, Num t) => t -> a -> Maybe a
jump Integer
skipCount a
a)
        countSkips :: Integer -> a -> Integer
        countSkips :: Integer -> a -> Integer
countSkips Integer
n a
start'
            | Int
stepInt forall a. Eq a => a -> a -> Bool
== forall a. Enum a => a -> Int
fromEnum a
start' = Integer
n
            | Bool
otherwise = Integer -> a -> Integer
countSkips (Integer
nforall a. Num a => a -> a -> a
+Integer
1) forall a b. (a -> b) -> a -> b
$ a -> a
iter a
start'
        -- This is the number of applications of @iter@ (again, either @succ@
        -- or @pred@) needed to get from @start@ to @step@.
        skipCount :: Integer
skipCount = Integer -> a -> Integer
countSkips Integer
0 a
start