{-|
Module      : Data.Enum.Circular
Description : Circular successor & predecessor for bounded enum types
Copyright   : (c) 2023-2026 Mirko Westermeier
License     : MIT

Sometimes, bounded enum types should be circular. Consider this enum
type of directions:

@
data Direction  = North
                | East
                | South
                | West
                deriving (Eq, Enum, Bounded)
@

The 'Enum' instance allows for @succ North@ to be @East@ and @succ East@
to be @South@. But in this case, one would like to have some kind of
@succ@ with @succ West = North@ again. With 'Eq' and 'Bounded' instances,
the functions defined in this module act like circular versions of 'succ'
and 'pred'.

Note: this module is designed for small, finite enum types created with
@deriving Enum@. It is not suitable for types where
@fromEnum maxBound + 1@ overflows 'Int', such as 'Int' or 'Word' itself.
-}

module Data.Enum.Circular (csucc, cpred, Circular(..)) where

-- | Circular version of 'succ'
csucc :: (Eq a, Enum a, Bounded a) => a -> a
csucc :: forall a. (Eq a, Enum a, Bounded a) => a -> a
csucc = Circular a -> a
forall a. Circular a -> a
unCircular (Circular a -> a) -> (a -> Circular a) -> a -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Circular a -> Circular a
forall a. Enum a => a -> a
succ (Circular a -> Circular a) -> (a -> Circular a) -> a -> Circular a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Circular a
forall a. a -> Circular a
Circular

-- | Circular version of 'pred'
cpred :: (Eq a, Enum a, Bounded a) => a -> a
cpred :: forall a. (Eq a, Enum a, Bounded a) => a -> a
cpred = Circular a -> a
forall a. Circular a -> a
unCircular (Circular a -> a) -> (a -> Circular a) -> a -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Circular a -> Circular a
forall a. Enum a => a -> a
pred (Circular a -> Circular a) -> (a -> Circular a) -> a -> Circular a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Circular a
forall a. a -> Circular a
Circular


--  | Type Alias you can use to express your intent and avoid 'Enum'
--    functions from biting you.
--
--    Beware: unlike regular 'Enum' types, backwards enumeration via
--    @[West, South ..]@ leads to singleton results. Since the enum is
--    circular, a "backwards" step is equivalent to a large forwards step,
--    but the enumeration ends at the "to" element earlier than expected.
--    Use 'pred' or 'cpred' explicitly with 'iterate' to go backwards.
--
--    Also assumes that the underlying 'Enum' instance is well-behaved,
--    i.e. @fromEnum minBound == 0@ and @fromEnum maxBound + 1@ does not
--    overflow 'Int'. Both hold for all types using @deriving Enum@ with a
--    small number of constructors, but /not/ for 'Int', 'Word', or similar.

newtype Circular a = Circular {forall a. Circular a -> a
unCircular :: a}
  deriving (Int -> Circular a -> ShowS
[Circular a] -> ShowS
Circular a -> String
(Int -> Circular a -> ShowS)
-> (Circular a -> String)
-> ([Circular a] -> ShowS)
-> Show (Circular a)
forall a. Show a => Int -> Circular a -> ShowS
forall a. Show a => [Circular a] -> ShowS
forall a. Show a => Circular a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: forall a. Show a => Int -> Circular a -> ShowS
showsPrec :: Int -> Circular a -> ShowS
$cshow :: forall a. Show a => Circular a -> String
show :: Circular a -> String
$cshowList :: forall a. Show a => [Circular a] -> ShowS
showList :: [Circular a] -> ShowS
Show, Circular a -> Circular a -> Bool
(Circular a -> Circular a -> Bool)
-> (Circular a -> Circular a -> Bool) -> Eq (Circular a)
forall a. Eq a => Circular a -> Circular a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: forall a. Eq a => Circular a -> Circular a -> Bool
== :: Circular a -> Circular a -> Bool
$c/= :: forall a. Eq a => Circular a -> Circular a -> Bool
/= :: Circular a -> Circular a -> Bool
Eq)

instance (Enum a, Bounded a) => Enum (Circular a) where

  toEnum :: Int -> Circular a
  toEnum :: Int -> Circular a
toEnum = a -> Circular a
forall a. a -> Circular a
Circular (a -> Circular a) -> (Int -> a) -> Int -> Circular a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> a
forall a. Enum a => Int -> a
toEnum (Int -> a) -> (Int -> Int) -> Int -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
len)
    where len :: Int
len = a -> Int
forall a. Enum a => a -> Int
fromEnum (a
forall a. Bounded a => a
maxBound :: a) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1

  fromEnum :: Circular a -> Int
  fromEnum :: Circular a -> Int
fromEnum = a -> Int
forall a. Enum a => a -> Int
fromEnum (a -> Int) -> (Circular a -> a) -> Circular a -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Circular a -> a
forall a. Circular a -> a
unCircular

  enumFromTo :: Circular a -> Circular a -> [Circular a]
  enumFromTo :: Circular a -> Circular a -> [Circular a]
enumFromTo Circular a
start Circular a
end = (Int -> Circular a) -> [Int] -> [Circular a]
forall a b. (a -> b) -> [a] -> [b]
map Int -> Circular a
forall a. Enum a => Int -> a
toEnum ([Int] -> [Circular a]) -> [Int] -> [Circular a]
forall a b. (a -> b) -> a -> b
$ Int -> Int -> [Int]
forall a. Enum a => a -> a -> [a]
enumFromTo Int
i (Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
dist)
    where i :: Int
i     = Circular a -> Int
forall a. Enum a => a -> Int
fromEnum Circular a
start
          j :: Int
j     = Circular a -> Int
forall a. Enum a => a -> Int
fromEnum Circular a
end
          dist :: Int
dist  = (Int
j Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
i) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
len
          len :: Int
len   = a -> Int
forall a. Enum a => a -> Int
fromEnum (a
forall a. Bounded a => a
maxBound :: a) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1

  enumFromThenTo :: Circular a -> Circular a -> Circular a -> [Circular a]
  enumFromThenTo :: Circular a -> Circular a -> Circular a -> [Circular a]
enumFromThenTo Circular a
from Circular a
next Circular a
to
    | Int
step Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 = Circular a -> [Circular a]
forall a. a -> [a]
repeat Circular a
from
    | Bool
otherwise = (Int -> Circular a) -> [Int] -> [Circular a]
forall a b. (a -> b) -> [a] -> [b]
map Int -> Circular a
forall a. Enum a => Int -> a
toEnum ([Int] -> [Circular a]) -> [Int] -> [Circular a]
forall a b. (a -> b) -> a -> b
$ Int -> Int -> Int -> [Int]
forall a. Enum a => a -> a -> a -> [a]
enumFromThenTo Int
i (Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
step) (Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
dist)
    where i :: Int
i     = Circular a -> Int
forall a. Enum a => a -> Int
fromEnum Circular a
from
          j :: Int
j     = Circular a -> Int
forall a. Enum a => a -> Int
fromEnum Circular a
next
          k :: Int
k     = Circular a -> Int
forall a. Enum a => a -> Int
fromEnum Circular a
to
          step :: Int
step  = (Int
j Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
i) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
len
          dist :: Int
dist  = (Int
k Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
i) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
len
          len :: Int
len   = a -> Int
forall a. Enum a => a -> Int
fromEnum (a
forall a. Bounded a => a
maxBound :: a) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1