{-# LANGUAGE BangPatterns               #-}
{-# LANGUAGE BlockArguments             #-}
{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE DeriveDataTypeable         #-}
{-# LANGUAGE ExplicitForAll             #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE PolyKinds                  #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE UnboxedTuples              #-}
{-# LANGUAGE UnicodeSyntax              #-}

-- | Efficient sets over bounded enumerations, using bitwise operations based on
-- [containers](https://hackage.haskell.org/package/containers-0.6.0.1/docs/src/Data.IntSet.Internal.html)
-- and
-- [EdisonCore](https://hackage.haskell.org/package/EdisonCore-1.3.2.1/docs/src/Data-Edison-Coll-EnumSet.html).
-- In many cases, @EnumSet@s may be optimised away entirely by constant folding
-- at compile-time. For example, in the following code:
--
-- @
--
-- import Data.Enum.Set.Base as E
--
-- data Foo = A | B | C | D | E | F | G | H deriving (Bounded, Enum, Eq, Ord, Show)
--
-- addFoos :: E.EnumSet Word Foo -> E.EnumSet Word Foo
-- addFoos = E.delete A . E.insert B
--
-- bar :: E.EnumSet Word Foo
-- bar = addFoos $ E.fromFoldable [A, C, E]
--
-- barHasB :: Bool
-- barHasB = E.member A bar
--
-- @
--
-- With @-O@ or @-O2@, @bar@ will compile to @GHC.Types.W\# 22\#\#@ and
-- @barHasA@ will compile to @GHC.Types.False@.
--
-- For type @EnumSet W E@, @W@ should be a 'Word'-like type that implements
-- 'Bits' and 'Num', and @E@ should be a type that implements 'Eq' and 'Enum'
-- equivalently and is a bijection to 'Int'.
-- @EnumSet W E@ can only store a value of @E@ if the result of applying
-- 'fromEnum' to the value is positive and less than the number of bits in @W@.
-- For this reason, it is preferable for @E@ to be a type that derives 'Eq' and
-- 'Enum', and for @W@ to have more bits than the number of constructors of @E@.
--
-- For type @EnumSet W E@, if the highest @fromEnum@ value of @E@ is 29,
-- @W@ should be 'Data.Word.Word', because it always has at least 30 bits.
-- Otherwise, options include 'Data.Word.Word32', 'Data.Word.Word64', and the
-- [wide-word](https://hackage.haskell.org/package/wide-word-0.1.0.8/) package's
-- [Data.WideWord.Word128](https://hackage.haskell.org/package/wide-word-0.1.0.8/docs/Data-WideWord-Word128.html).
-- Foreign types may also be used.
--
-- "Data.Enum.Set" provides an alternate type alias that moves the underlying
-- representation to an associated type token, so that e.g.
-- @EnumSet Word64 MyEnum@ is replaced by @EnumSet MyEnum@, and reexports this
-- module with adjusted type signatures.
--
-- Note: complexity calculations assume that @W@ implements 'Bits' with
-- constant-time functions, as is the case with 'Data.Word.Word' etc. If this
-- is not the case, the complexity of those operations should be added to the
-- complexity of 'EnumSet' functions.
module Data.Enum.Set.Base
  ( -- * Set type
    EnumSet

    -- * Construction
  , empty
  , singleton
  , fromFoldable

  -- * Insertion
  , insert

  -- * Deletion
  , delete

  -- * Query
  , member
  , notMember
  , null
  , size
  , isSubsetOf

  -- * Combine
  , union
  , difference
  , (\\)
  , symmetricDifference
  , intersection

  -- * Filter
  , filter
  , partition

  -- * Map
  , map
  , map'

  -- * Folds
  , foldl, foldl', foldr, foldr'
  , foldl1, foldl1', foldr1, foldr1'

  -- ** Special folds
  , foldMap
  , traverse
  , any
  , all

   -- * Min/Max
  , minimum
  , maximum
  , deleteMin
  , deleteMax
  , minView
  , maxView

  -- * Conversion
  , toList
  , fromRaw
  ) where

import qualified GHC.Exts
import qualified Data.Foldable as F

import Prelude hiding (all, any, filter, foldl, foldl1, foldMap, foldr, foldr1, map, maximum, minimum, null, traverse)

import Control.Applicative (liftA2)
import Control.DeepSeq (NFData)
import Control.Monad
import Data.Aeson (ToJSON(..))
import Data.Bits
import Data.Data (Data)
import Data.Monoid (Monoid(..))
import Data.Vector.Unboxed (Vector, MVector, Unbox)
import Foreign.Storable (Storable)
import GHC.Exts (IsList(Item), build)
import Text.Read

import qualified Data.Vector.Generic as G
import qualified Data.Vector.Generic.Mutable as M
import qualified Data.Vector.Primitive as P

import qualified Data.Containers
import           Data.Containers (SetContainer, IsSet)
import qualified Data.MonoTraversable
import           Data.MonoTraversable (Element, GrowingAppend, MonoFoldable, MonoFunctor, MonoPointed, MonoTraversable)

{--------------------------------------------------------------------
  Set type
--------------------------------------------------------------------}

-- | A set of values @a@ with representation @word@,
-- implemented as bitwise operations.
newtype EnumSet word a = EnumSet word
    deriving (Eq, Ord, Data, Storable, NFData, P.Prim, Unbox)

newtype instance MVector s (EnumSet word a) = MV_EnumSet (P.MVector s (EnumSet word a))
newtype instance Vector    (EnumSet word a) = V_EnumSet (P.Vector (EnumSet word a))

instance P.Prim word => M.MVector MVector (EnumSet word a) where
    basicLength (MV_EnumSet v) = M.basicLength v
    {-# INLINE basicLength #-}
    basicUnsafeSlice i n (MV_EnumSet v) = MV_EnumSet $ M.basicUnsafeSlice i n v
    {-# INLINE basicUnsafeSlice #-}
    basicOverlaps (MV_EnumSet v1) (MV_EnumSet v2) = M.basicOverlaps v1 v2
    {-# INLINE basicOverlaps #-}
    basicUnsafeNew n = MV_EnumSet `liftM` M.basicUnsafeNew n
    {-# INLINE basicUnsafeNew #-}
    basicInitialize (MV_EnumSet v) = M.basicInitialize v
    {-# INLINE basicInitialize #-}
    basicUnsafeReplicate n x = MV_EnumSet `liftM` M.basicUnsafeReplicate n x
    {-# INLINE basicUnsafeReplicate #-}
    basicUnsafeRead (MV_EnumSet v) i = M.basicUnsafeRead v i
    {-# INLINE basicUnsafeRead #-}
    basicUnsafeWrite (MV_EnumSet v) i x = M.basicUnsafeWrite v i x
    {-# INLINE basicUnsafeWrite #-}
    basicClear (MV_EnumSet v) = M.basicClear v
    {-# INLINE basicClear #-}
    basicSet (MV_EnumSet v) x = M.basicSet v x
    {-# INLINE basicSet #-}
    basicUnsafeCopy (MV_EnumSet v1) (MV_EnumSet v2) = M.basicUnsafeCopy v1 v2
    {-# INLINE basicUnsafeCopy #-}
    basicUnsafeMove (MV_EnumSet v1) (MV_EnumSet v2) = M.basicUnsafeMove v1 v2
    {-# INLINE basicUnsafeMove #-}
    basicUnsafeGrow (MV_EnumSet v) n = MV_EnumSet `liftM` M.basicUnsafeGrow v n
    {-# INLINE basicUnsafeGrow #-}


instance P.Prim word => G.Vector Vector (EnumSet word a) where
    basicUnsafeFreeze (MV_EnumSet v) = V_EnumSet `liftM` G.basicUnsafeFreeze v
    {-# INLINE basicUnsafeFreeze #-}
    basicUnsafeThaw (V_EnumSet v) = MV_EnumSet `liftM` G.basicUnsafeThaw v
    {-# INLINE basicUnsafeThaw #-}
    basicLength (V_EnumSet v) = G.basicLength v
    {-# INLINE basicLength #-}
    basicUnsafeSlice i n (V_EnumSet v) = V_EnumSet $ G.basicUnsafeSlice i n v
    {-# INLINE basicUnsafeSlice #-}
    basicUnsafeIndexM (V_EnumSet v) i = G.basicUnsafeIndexM v i
    {-# INLINE basicUnsafeIndexM #-}
    basicUnsafeCopy (MV_EnumSet mv) (V_EnumSet v) = G.basicUnsafeCopy mv v
    {-# INLINE basicUnsafeCopy #-}
    elemseq _ = seq
    {-# INLINE elemseq #-}

instance Bits w => Semigroup (EnumSet w a) where
    (<>) = union
    {-# INLINE (<>) #-}

instance Bits w => Monoid (EnumSet w a) where
    mempty = empty
    {-# INLINE mempty #-}

instance (Bits w, Enum a) => MonoPointed (EnumSet w a) where
    opoint = singleton
    {-# INLINE opoint #-}

instance (FiniteBits w, Num w, Enum a) => IsList (EnumSet w a) where
    type Item (EnumSet w a) = a
    fromList = fromFoldable
    {-# INLINE fromList #-}
    toList = toList
    {-# INLINE toList #-}

instance (FiniteBits w, Num w, Enum a, ToJSON a) => ToJSON (EnumSet w a) where
    toJSON = toJSON . toList
    {-# INLINE toJSON #-}
    toEncoding = toEncoding . toList
    {-# INLINE toEncoding #-}

type instance Element (EnumSet w a) = a

instance (FiniteBits w, Num w, Enum a) => MonoFunctor (EnumSet w a) where
    omap = map
    {-# INLINE omap #-}

instance (FiniteBits w, Num w, Enum a) => MonoFoldable (EnumSet w a) where
    ofoldMap = foldMap
    {-# INLINE ofoldMap #-}
    ofoldr = foldr
    {-# INLINE ofoldr #-}
    ofoldl' = foldl'
    {-# INLINE ofoldl' #-}
    ofoldr1Ex = foldr1
    {-# INLINE ofoldr1Ex #-}
    ofoldl1Ex' = foldl1'
    {-# INLINE ofoldl1Ex' #-}
    otoList = toList
    {-# INLINE otoList #-}
    oall = all
    {-# INLINE oall #-}
    oany = any
    {-# INLINE oany #-}
    onull = null
    {-# INLINE onull #-}
    olength = size
    {-# INLINE olength #-}
    olength64 w = fromIntegral $ size w
    {-# INLINE olength64 #-}
    headEx = minimum
    {-# INLINE headEx #-}
    lastEx = maximum
    {-# INLINE lastEx #-}
    oelem = member
    {-# INLINE oelem #-}
    onotElem x = not . member x
    {-# INLINE onotElem #-}

instance (FiniteBits w, Num w, Enum a) => GrowingAppend (EnumSet w a)

instance (FiniteBits w, Num w, Enum a) => MonoTraversable (EnumSet w a) where
    otraverse = traverse
    {-# INLINE otraverse #-}

instance (FiniteBits w, Num w, Eq a, Enum a) => SetContainer (EnumSet w a) where
    type ContainerKey (EnumSet w a) = a
    member = member
    {-# INLINE member #-}
    notMember = notMember
    {-# INLINE notMember #-}
    union = union
    {-# INLINE union #-}
    difference = difference
    {-# INLINE difference #-}
    intersection = intersection
    {-# INLINE intersection #-}
    keys = toList
    {-# INLINE keys #-}

instance (FiniteBits w, Num w, Eq a, Enum a) => IsSet (EnumSet w a) where
    insertSet = insert
    {-# INLINE insertSet #-}
    deleteSet = delete
    {-# INLINE deleteSet #-}
    singletonSet = singleton
    {-# INLINE singletonSet #-}
    setFromList = fromFoldable
    {-# INLINE setFromList #-}
    setToList = toList
    {-# INLINE setToList #-}
    filterSet = filter
    {-# INLINE filterSet #-}

instance (FiniteBits w, Num w, Enum x, Show x) => Show (EnumSet w x) where
    showsPrec p xs = showParen (p > 10) $
        showString "fromList " . shows (toList xs)
    {-# INLINABLE showsPrec #-}

instance (Bits w, Num w, Enum x, Read x) => Read (EnumSet w x) where
    readPrec = parens $ prec 10 do
        Ident "fromList" <- lexP
        fromFoldable <$> (readPrec :: ReadPrec [x])
    {-# INLINABLE readPrec #-}
    readListPrec = readListPrecDefault
    {-# INLINABLE readListPrec #-}

{--------------------------------------------------------------------
  Construction
--------------------------------------------------------------------}

-- | /O(1)/. The empty set.
empty ::  w a. Bits w
      => EnumSet w a
empty = EnumSet zeroBits
{-# INLINE empty #-}

-- | /O(1)/. A set of one element.
singleton ::  w a. (Bits w, Enum a)
          => a -> EnumSet w a
singleton = EnumSet . bit . fromEnum
{-# INLINE singleton #-}

-- | /O(n)/. Create a set from a finite foldable data structure.
fromFoldable ::  f w a. (Foldable f, Bits w, Enum a)
             => f a -> EnumSet w a
fromFoldable = EnumSet . F.foldl' (flip $ (.|.) . bit . fromEnum) zeroBits

{--------------------------------------------------------------------
  Insertion
--------------------------------------------------------------------}

-- | /O(1)/. Add a value to the set.
insert ::  w a. (Bits w, Enum a)
       => a -> EnumSet w a -> EnumSet w a
insert !x (EnumSet w) = EnumSet . setBit w $ fromEnum x

{--------------------------------------------------------------------
  Deletion
--------------------------------------------------------------------}

-- | /O(1)/. Delete a value in the set.
delete ::  w a. (Bits w, Enum a)
       => a -> EnumSet w a -> EnumSet w a
delete !x (EnumSet w) = EnumSet . clearBit w $ fromEnum x

{--------------------------------------------------------------------
  Query
--------------------------------------------------------------------}

-- | /O(1)/. Is the value a member of the set?
member ::  w a. (Bits w, Enum a)
       => a -> EnumSet w a -> Bool
member !x (EnumSet w) = testBit w $ fromEnum x

-- | /O(1)/. Is the value not in the set?
notMember ::  w a. (Bits w, Enum a)
          => a -> EnumSet w a -> Bool
notMember !x = not . member x

-- | /O(1)/. Is this the empty set?
null ::  w a. Bits w
     => EnumSet w a -> Bool
null (EnumSet w) = zeroBits == w
{-# INLINE null #-}

-- | /O(1)/. The number of elements in the set.
size ::  w a. (Bits w, Num w)
     => EnumSet w a -> Int
size (EnumSet !w) = popCount w

-- | /O(1)/. Is this a subset?
-- @(s1 `isSubsetOf` s2)@ tells whether @s1@ is a subset of @s2@.
isSubsetOf ::  w a. (Bits w)
           => EnumSet w a -> EnumSet w a -> Bool
isSubsetOf (EnumSet x) (EnumSet y) = x .|. y == y
{-# INLINE isSubsetOf #-}

{--------------------------------------------------------------------
  Combine
--------------------------------------------------------------------}

-- | /O(1)/. The union of two sets.
union ::  w a. Bits w
      => EnumSet w a -> EnumSet w a -> EnumSet w a
union (EnumSet x) (EnumSet y) = EnumSet $ x .|. y
{-# INLINE union #-}

-- | /O(1)/. Difference between two sets.
difference ::  w a. Bits w
           => EnumSet w a -> EnumSet w a -> EnumSet w a
difference (EnumSet x) (EnumSet y) = EnumSet $ (x .|. y) `xor` y
{-# INLINE difference #-}

-- | /O(1)/. See 'difference'.
(\\) ::  w a. Bits w
     => EnumSet w a -> EnumSet w a -> EnumSet w a
(\\) = difference
infixl 9 \\
{-# INLINE (\\) #-}

-- | /O(1)/. Elements which are in either set, but not both.
symmetricDifference ::  w a. Bits w
                    => EnumSet w a -> EnumSet w a -> EnumSet w a
symmetricDifference (EnumSet x) (EnumSet y) = EnumSet $ x `xor` y
{-# INLINE symmetricDifference #-}

-- | /O(1)/. The intersection of two sets.
intersection ::  w a. Bits w
             => EnumSet w a -> EnumSet w a -> EnumSet w a
intersection (EnumSet x) (EnumSet y) = EnumSet $ x .&. y
{-# INLINE intersection #-}

{--------------------------------------------------------------------
  Filter
--------------------------------------------------------------------}

-- | /O(n)/. Filter all elements that satisfy some predicate.
filter ::  w a. (FiniteBits w, Num w, Enum a)
       => (a -> Bool) -> EnumSet w a -> EnumSet w a
filter p (EnumSet w) = EnumSet $ foldlBits' f 0 w
    where
      f z i
        | p $ toEnum i = setBit z i
        | otherwise    = z
      {-# INLINE f #-}

-- | /O(n)/. Partition the set according to some predicate.
-- The first set contains all elements that satisfy the predicate,
-- the second all elements that fail the predicate.
partition ::  w a. (FiniteBits w, Num w, Enum a)
          => (a -> Bool) -> EnumSet w a -> (EnumSet w a, EnumSet w a)
partition p (EnumSet w) = (EnumSet yay, EnumSet nay)
    where
      (yay, nay) = foldlBits' f (0, 0) w
      f (x, y) i
          | p $ toEnum i = (setBit x i, y)
          | otherwise    = (x, setBit y i)
      {-# INLINE f #-}

{--------------------------------------------------------------------
  Map
--------------------------------------------------------------------}

-- | /O(n)/.
-- @'map' f s@ is the set obtained by applying @f@ to each element of @s@.
--
-- It's worth noting that the size of the result may be smaller if,
-- for some @(x,y)@, @x \/= y && f x == f y@.
map ::  w a b. (FiniteBits w, Num w, Enum a, Enum b)
    => (a -> b) -> EnumSet w a -> EnumSet w b
map = map'
{-# INLINE map #-}

-- | /O(n)/. Apply 'map' while converting the underlying representation of the
-- set to some other representation.
map' ::  v w a b. (FiniteBits v, FiniteBits w, Num v, Num w, Enum a, Enum b)
     => (a -> b) -> EnumSet v a -> EnumSet w b
map' f0 (EnumSet w) = EnumSet $ foldlBits' f 0 w
    where
      f z i = setBit z $ fromEnum $ f0 (toEnum i)
      {-# INLINE f #-}

{--------------------------------------------------------------------
  Folds
--------------------------------------------------------------------}

-- | /O(n)/. Left fold.
foldl ::  w a b. (FiniteBits w, Num w, Enum a)
      => (b -> a -> b) -> b -> EnumSet w a -> b
foldl f z (EnumSet w) = foldlBits ((. toEnum) . f) z w
{-# INLINE foldl #-}

-- | /O(n)/. Left fold with strict accumulator.
foldl' ::  w a b. (FiniteBits w, Num w, Enum a)
       => (b -> a -> b) -> b -> EnumSet w a -> b
foldl' f z (EnumSet w) = foldlBits' ((. toEnum) . f) z w
{-# INLINE foldl' #-}

-- | /O(n)/. Right fold.
foldr ::  w a b. (FiniteBits w, Num w, Enum a)
      => (a -> b -> b) -> b -> EnumSet w a -> b
foldr f z (EnumSet w) = foldrBits (f . toEnum) z w
{-# INLINE foldr #-}

-- | /O(n)/. Right fold with strict accumulator.
foldr' ::  w a b. (FiniteBits w, Num w,  Enum a)
       => (a -> b -> b) -> b -> EnumSet w a -> b
foldr' f z (EnumSet w) = foldrBits' (f . toEnum) z w
{-# INLINE foldr' #-}

-- | /O(n)/. Left fold on non-empty sets.
foldl1 ::  w a. (FiniteBits w, Num w, Enum a)
       => (a -> a -> a) -> EnumSet w a -> a
foldl1 f = fold1Aux lsb $ foldlBits ((. toEnum) . f)
{-# INLINE foldl1 #-}

-- | /O(n)/. Left fold on non-empty sets with strict accumulator.
foldl1' ::  w a. (FiniteBits w, Num w, Enum a)
        => (a -> a -> a) -> EnumSet w a -> a
foldl1' f = fold1Aux lsb $ foldlBits' ((.toEnum) . f)
{-# INLINE foldl1' #-}

-- | /O(n)/. Right fold on non-empty sets.
foldr1 ::  w a. (FiniteBits w, Num w, Enum a)
       => (a -> a -> a) -> EnumSet w a -> a
foldr1 f = fold1Aux msb $ foldrBits (f . toEnum)
{-# INLINE foldr1 #-}

-- | /O(n)/. Right fold on non-empty sets with strict accumulator.
foldr1' ::  w a. (FiniteBits w, Num w, Enum a)
        => (a -> a -> a) -> EnumSet w a -> a
foldr1' f = fold1Aux msb $ foldrBits' (f . toEnum)
{-# INLINE foldr1' #-}

-- | /O(n)/. Map each element of the structure to a monoid, and combine the
-- results.
foldMap ::  m w a. (Monoid m, FiniteBits w, Num w, Enum a)
        => (a -> m) -> EnumSet w a -> m
foldMap f (EnumSet w) = foldrBits (mappend . f . toEnum) mempty w
{-# INLINE foldMap #-}

traverse ::  f w a. (Applicative f, FiniteBits w, Num w, Enum a)
         => (a -> f a) -> EnumSet w a -> f (EnumSet w a)
traverse f (EnumSet w) = EnumSet <$>
                         foldrBits
                         (liftA2 (flip setBit) . fmap fromEnum . f . toEnum)
                         (pure zeroBits)
                         w
{-# INLINE traverse #-}

-- | /O(n)/. Check if all elements satisfy some predicate.
all ::  w a. (FiniteBits w, Num w, Enum a)
    => (a -> Bool) -> EnumSet w a -> Bool
all p (EnumSet w) = let lb = lsb w in go lb (w `unsafeShiftR` lb)
  where
    go !_ 0 = True
    go bi n
      | n `testBit` 0 && not (p $ toEnum bi) = False
      | otherwise = go (bi + 1) (n `unsafeShiftR` 1)

-- | /O(n)/. Check if any element satisfies some predicate.
any ::  w a. (FiniteBits w, Num w, Enum a)
    => (a -> Bool) -> EnumSet w a -> Bool
any p (EnumSet w) = let lb = lsb w in go lb (w `unsafeShiftR` lb)
  where
    go !_ 0 = False
    go bi n
      | n `testBit` 0 && p (toEnum bi) = True
      | otherwise = go (bi + 1) (n `unsafeShiftR` 1)

{--------------------------------------------------------------------
  Min/Max
--------------------------------------------------------------------}

-- | /O(1)/. The minimal element of a non-empty set.
minimum ::  w a. (FiniteBits w, Num w, Enum a)
        => EnumSet w a -> a
minimum (EnumSet 0) = error "empty EnumSet"
minimum (EnumSet w) = toEnum $ lsb w

-- | /O(1)/. The maximal element of a non-empty set.
maximum ::  w a. (FiniteBits w, Num w, Enum a)
        => EnumSet w a -> a
maximum (EnumSet 0) = error "empty EnumSet"
maximum (EnumSet w) = toEnum $ msb w

-- | /O(1)/. Delete the minimal element.
deleteMin ::  w a. (FiniteBits w, Num w)
          => EnumSet w a -> EnumSet w a
deleteMin (EnumSet 0) = EnumSet 0
deleteMin (EnumSet w) = EnumSet $ clearBit w $ lsb w

-- | /O(1)/. Delete the maximal element.
deleteMax ::  w a. (FiniteBits w, Num w)
          => EnumSet w a -> EnumSet w a
deleteMax (EnumSet 0) = EnumSet 0
deleteMax (EnumSet w) = EnumSet $ clearBit w $ msb w

-- | /O(1)/. Retrieves the minimal element of the set,
-- and the set stripped of that element,
-- or Nothing if passed an empty set.
minView ::  w a. (FiniteBits w, Num w, Enum a)
        => EnumSet w a -> Maybe (a, EnumSet w a)
minView (EnumSet 0) = Nothing
minView (EnumSet w) = let i = lsb w in Just (toEnum i, EnumSet $ clearBit w i)

-- | /O(1)/. Retrieves the maximal element of the set,
-- and the set stripped of that element,
-- or Nothing if passed an empty set.
maxView ::  w a. (FiniteBits w, Num w, Enum a)
        => EnumSet w a -> Maybe (a, EnumSet w a)
maxView (EnumSet 0) = Nothing
maxView (EnumSet w) = let i = msb w in Just (toEnum i, EnumSet $ clearBit w i)

{--------------------------------------------------------------------
  Conversion
--------------------------------------------------------------------}

-- | /O(n)/. Convert the set to a list of values.
toList ::  w a. (FiniteBits w, Num w, Enum a)
       => EnumSet w a -> [a]
toList (EnumSet w) = build \c n -> foldrBits (c . toEnum) n w
{-# INLINE toList #-}

-- | /O(1)/. Convert a representation into an @EnumSet@.
-- Intended for use with foreign types.
fromRaw ::  w a. w -> EnumSet w a
fromRaw = EnumSet
{-# INLINE fromRaw #-}

{--------------------------------------------------------------------
  Utility functions
--------------------------------------------------------------------}

lsb ::  w. (FiniteBits w, Num w) => w -> Int
lsb n0 = go 0 n0 $ finiteBitSize n0 `quot` 2
  where
    go b n 1 = case n .&. 1 of
        0 -> 1 + b
        _ -> b
    go b n i = case n .&. (bit i - 1) of
        0 -> go (i + b) (n `unsafeShiftR` i) (i `quot` 2)
        _ -> go b       n                    (i `quot` 2)
{-# INLINE lsb #-}

msb ::  w. (FiniteBits w, Num w) => w -> Int
msb n0 = go 0 n0 $ finiteBitSize n0 `quot` 2
  where
    go b n 1 = case n .&. 2 of
        0 -> b
        _ -> 1 + b
    go b n i = case n .&. (bit (i * 2) - bit i) of
        0 -> go b       n                    (i `quot` 2)
        _ -> go (i + b) (n `unsafeShiftR` i) (i `quot` 2)
{-# INLINE msb #-}

foldlBits ::  w a. (FiniteBits w, Num w) => (a -> Int -> a) -> a -> w -> a
foldlBits  f z w = let lb = lsb w in go lb z (w `unsafeShiftR` lb)
  where
    go !_ acc 0 = acc
    go bi acc n
      | n `testBit` 0 = go (bi + 1) (f acc bi) (n `unsafeShiftR` 1)
      | otherwise     = go (bi + 1)    acc     (n `unsafeShiftR` 1)
{-# INLINE foldlBits #-}

foldlBits' ::  w a. (FiniteBits w, Num w) => (a -> Int -> a) -> a -> w -> a
foldlBits' f z w = let lb = lsb w in go lb z (w `unsafeShiftR` lb)
  where
    go !_ !acc 0 = acc
    go bi acc n
      | n `testBit` 0 = go (bi + 1) (f acc bi) (n `unsafeShiftR` 1)
      | otherwise     = go (bi + 1)    acc     (n `unsafeShiftR` 1)
{-# INLINE foldlBits' #-}

foldrBits ::  w a. (FiniteBits w, Num w) => (Int -> a -> a) -> a -> w -> a
foldrBits f z w = let lb = lsb w in go lb (w `unsafeShiftR` lb)
  where
    go !_ 0 = z
    go bi n
      | n `testBit` 0 = f bi (go (bi + 1) (n `unsafeShiftR` 1))
      | otherwise     =       go (bi + 1) (n `unsafeShiftR` 1)
{-# INLINE foldrBits #-}

foldrBits' ::  w a. (FiniteBits w, Num w) => (Int -> a -> a) -> a -> w -> a
foldrBits' f z w = let lb = lsb w in go lb (w `unsafeShiftR` lb)
  where
    go !_ 0 = z
    go bi n
      | n `testBit` 0 = f bi $! go (bi + 1) (n `unsafeShiftR` 1)
      | otherwise     =         go (bi + 1) (n `unsafeShiftR` 1)
{-# INLINE foldrBits' #-}

fold1Aux ::  w a. (Bits w, Num w, Enum a)
         => (w -> Int) -> (a -> w -> a) -> EnumSet w a -> a
fold1Aux _      _ (EnumSet 0) = error "empty EnumSet"
fold1Aux getBit f (EnumSet w) = f (toEnum gotBit) (clearBit w gotBit)
  where
    gotBit = getBit w
{-# INLINE fold1Aux #-}