module Data.Semigroup.Act.Enum where

import Data.Semigroup
import Data.Semigroup.Act

-- | A wrapper for an integer acting on an 'Enum'. If the resulting index is
-- out of the enum bounds, an exception is raised.
newtype EnumIntAct a = EnumIntAct a
  deriving (Show, Read, Eq, Ord)
instance Functor EnumIntAct where
    fmap f (EnumIntAct x) = EnumIntAct (f x)
instance (Integral n, Enum a) => SemigroupAct (Sum n) (EnumIntAct a) where
    act (Sum n) = fmap (toEnum . (+ (fromIntegral n)) . fromEnum)

-- | A wrapper for an integer acting on an instance of both 'Enum' and 'Bounded'.
-- The index wrap around the bounds, so
-- @Sum 1 `act` (EnumBoundedIntAct maxBound) == (EnumBoundedIntAct minBound)@ etc.
newtype EnumBoundedIntAct a = EnumBoundedIntAct a
  deriving (Show, Read, Eq, Ord)
instance Functor EnumBoundedIntAct where
    fmap f (EnumBoundedIntAct x) = EnumBoundedIntAct (f x)
instance (Bounded a, Enum a, Integral n) => SemigroupAct (Sum n) (EnumBoundedIntAct a) where
    act (Sum n) = fmap shift
      where
        shift x = toEnum $ mn + ((fromEnum x - mn + fromIntegral n) `mod` l)
          where
            mn = fromEnum (asTypeOf minBound x)
            l  = fromEnum (asTypeOf maxBound x) - mn + 1