{-# LANGUAGE Safe #-}

{- |
Copyright:  (c) 2018-2021 Kowainik
SPDX-License-Identifier: MIT
Maintainer:  Kowainik <xrom.xkov@gmail.com>
Stability:   Experimental
Portability: Portable

Mini @bounded-enum@ framework inside @relude@.

@since 0.1.0
-}

module Relude.Extra.Enum
    ( next
    , prev
    , safeToEnum
    ) where

import Relude


{- | Like 'succ', but doesn't fail on 'maxBound'. Instead it returns 'minBound'.

>>> next False
True
>>> next True
False
>>> succ True
*** Exception: Prelude.Enum.Bool.succ: bad argument

@since 0.1.0
-}
next :: (Eq a, Bounded a, Enum a) => a -> a
next :: a -> a
next a
e
    | a
e a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
forall a. Bounded a => a
maxBound = a
forall a. Bounded a => a
minBound
    | Bool
otherwise     = a -> a
forall a. Enum a => a -> a
succ a
e
{-# INLINE next #-}

{- | Like 'pred', but doesn't fail on 'minBound'. Instead it returns 'maxBound'.

>>> prev True
False
>>> prev False
True
>>> pred False
*** Exception: Prelude.Enum.Bool.pred: bad argument

@since 0.6.0.0
-}
prev :: (Eq a, Bounded a, Enum a) => a -> a
prev :: a -> a
prev a
e
    | a
e a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
forall a. Bounded a => a
minBound = a
forall a. Bounded a => a
maxBound
    | Bool
otherwise     = a -> a
forall a. Enum a => a -> a
pred a
e
{-# INLINE prev #-}

{- | Returns 'Nothing' if given 'Int' outside range.

>>> safeToEnum @Bool 0
Just False
>>> safeToEnum @Bool 1
Just True
>>> safeToEnum @Bool 2
Nothing
>>> safeToEnum @Bool (-1)
Nothing

@since 0.1.0
-}
safeToEnum :: forall a . (Bounded a, Enum a) => Int -> Maybe a
safeToEnum :: Int -> Maybe a
safeToEnum Int
i = Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (a -> Int
forall a. Enum a => a -> Int
fromEnum @a a
forall a. Bounded a => a
minBound Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
i Bool -> Bool -> Bool
&& Int
i Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= a -> Int
forall a. Enum a => a -> Int
fromEnum @a a
forall a. Bounded a => a
maxBound) Maybe () -> a -> Maybe a
forall (f :: * -> *) a b. Functor f => f a -> b -> f b
$> Int -> a
forall a. Enum a => Int -> a
toEnum Int
i
{-# INLINE safeToEnum #-}