{- |
Similar to Data.Edison.Coll.EnumSet
but it allows to choose the underlying type for bit storage.
This is really a low-level module for type-safe foreign function interfaces.
-}
module Data.EnumSet (
   T(Cons, decons),
   fromEnum,
   fromEnums,
   toEnums,
   intToEnums,
   null,
   empty,
   (.&.),
   (.|.),
   xor,
   unions,
   get,
   put,
   set,
   clear,
   flip,
   fromBool,
   ) where

import qualified Data.Bits as B
import Data.Bits (Bits, )

import qualified Foreign.Storable.Newtype as Store
import Foreign.Storable (Storable(..), )

import qualified Prelude as P
import Prelude hiding (fromEnum, toEnum, null, flip, )


newtype T word enum = Cons {decons :: word}
   deriving (Eq)

instance (Storable word, Enum enum) => Storable (T word enum) where
   sizeOf = Store.sizeOf decons
   alignment = Store.alignment decons
   peek = Store.peek Cons
   poke = Store.poke decons


fromEnum :: (Enum a, Bits w) => a -> T w a
fromEnum = Cons . B.bit . P.fromEnum

fromEnums :: (Enum a, Bits w) => [a] -> T w a
fromEnums = Cons . foldl B.setBit 0 . map P.fromEnum

toEnums :: (Enum a, Bits w) => T w a -> [a]
toEnums =
   map fst . filter (P.flip B.testBit 0 . snd) .
   zip [P.toEnum 0 ..] . iterate (P.flip B.shiftR 1) .
   decons

intToEnums :: (Enum a, Integral w) => T w a -> [a]
intToEnums =
   map fst . filter (odd . snd) .
   zip [P.toEnum 0 ..] . iterate (P.flip div 2) .
   decons


null :: (Enum a, Bits w) => T w a -> Bool
null (Cons x)  =  x==0

empty :: (Enum a, Bits w) => T w a
empty = Cons 0


{-# INLINE lift2 #-}
lift2 :: (w -> w -> w) -> (T w a -> T w a -> T w a)
lift2 f (Cons x) (Cons y) = Cons (f x y)

-- fixities like in Data.Bits
infixl 7 .&.
infixl 5 .|.

(.&.), (.|.), xor :: (Enum a, Bits w) => T w a -> T w a -> T w a
(.&.) = lift2 (B..&.)
(.|.) = lift2 (B..|.)
xor   = lift2 B.xor

unions :: (Enum a, Bits w) => [T w a] -> T w a
unions = foldl (.|.) empty


get :: (Enum a, Bits w) => a -> T w a -> Bool
get n = P.flip B.testBit (P.fromEnum n) . decons

put :: (Enum a, Bits w) => a -> Bool -> T w a -> T w a
put n b s =
   fromBool n b .|. clear n s


{-# INLINE lift1 #-}
lift1 ::
   (Enum a, Bits w) =>
   (w -> Int -> w) -> (a -> T w a -> T w a)
lift1 f n (Cons vec) = Cons (f vec (P.fromEnum n))

set :: (Enum a, Bits w) => a -> T w a -> T w a
set = lift1 B.setBit

clear :: (Enum a, Bits w) => a -> T w a -> T w a
clear = lift1 B.clearBit

flip :: (Enum a, Bits w) => a -> T w a -> T w a
flip = lift1 B.complementBit

fromBool :: (Enum a, Bits w) => a -> Bool -> T w a
fromBool n b =
   Cons (B.shiftL (fromIntegral $ P.fromEnum b) (P.fromEnum n))