{-# LANGUAGE ExplicitForAll   #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies     #-}
{-# 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 as E
--
-- data Foo = A | B | C | D | E | F | G | H deriving (Bounded, Enum, Eq, Ord)
--
-- instance E.AsEnumSet Foo
--
-- addFoos :: E.EnumSet Foo -> E.EnumSet Foo
-- addFoos = E.delete A . E.insert B
--
-- bar :: E.EnumSet Foo
-- bar = addFoos $ E.fromFoldable [A, C, E]
--
-- barHasA :: Bool
-- barHasA = E.member A bar
-- @
--
-- With @-O@ or @-O2@, @bar@ will compile to @GHC.Types.W\# 22\#\#@ and
-- @barHasA@ will compile to @GHC.Types.False@.
--
-- By default, 'Word's are used as the representation. Other representations may
-- be chosen in the class instance:
--
-- @
-- {-\# LANGUAGE TypeFamilies \#-}
--
-- import Data.Enum.Set as E
-- import Data.Word (Word64)
--
-- data Foo = A | B | C | D | E | F | G | H deriving (Bounded, Enum, Eq, Ord, Show)
--
-- instance E.AsEnumSet Foo where
--     type EnumSetRep Foo = Word64
--
-- @
--
-- For type @EnumSet E@, @EnumSetRep E@ 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 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
-- @EnumSetRep E@. For this reason, it is preferable for @E@ to be a type that
-- derives @Eq@ and @Enum@, and for @EnumSetRep E@ to have more bits than the
-- number of constructors of @E@.
--
-- If the highest @fromEnum@ value of @E@ is 29, @EnumSetRep E@ should be
-- 'Word', because it always has at least 30 bits. This is the
-- default implementation.
-- 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.
--
-- Note: complexity calculations assume that @EnumSetRep E@ implements 'Bits'
-- with constant-time functions, as is the case with @Word@ etc. Otherwise, the
-- complexity of those operations should be added to the complexity of @EnumSet@
-- functions.
module Data.Enum.Set
  ( AsEnumSet(..)
  -- * 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

  -- * 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 Prelude hiding (all, any, filter, foldl, foldl1, foldMap, foldr, foldr1, map, maximum, minimum, null, traverse)

import Data.Bits
import Data.Monoid (Monoid(..))

import qualified Data.Enum.Set.Base as E

class (Enum a, FiniteBits (EnumSetRep a), Num (EnumSetRep a)) => AsEnumSet a where
    type EnumSetRep a
    type EnumSetRep a = Word

type EnumSet a = E.EnumSet (EnumSetRep a) a

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

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

-- | /O(1)/. A set of one element.
singleton ::  a. AsEnumSet a => a -> EnumSet a
singleton = E.singleton
{-# INLINE singleton #-}

-- | /O(n)/. Create a set from a finite foldable data structure.
fromFoldable ::  f a. (Foldable f, AsEnumSet a) => f a -> EnumSet a
fromFoldable = E.fromFoldable
{-# INLINE fromFoldable #-}

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

-- | /O(1)/. Add a value to the set.
insert ::  a. AsEnumSet a => a -> EnumSet a -> EnumSet a
insert = E.insert
{-# INLINE insert #-}

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

-- | /O(1)/. Delete a value in the set.
delete ::  a. AsEnumSet a => a -> EnumSet a -> EnumSet a
delete = E.delete
{-# INLINE delete #-}

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

-- | /O(1)/. Is the value a member of the set?
member ::  a. AsEnumSet a => a -> EnumSet a -> Bool
member = E.member
{-# INLINE member #-}

-- | /O(1)/. Is the value not in the set?
notMember ::  a. AsEnumSet a => a -> EnumSet a -> Bool
notMember = E.notMember
{-# INLINE notMember #-}

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

-- | /O(1)/. The number of elements in the set.
size ::  a. AsEnumSet a => EnumSet a -> Int
size = E.size
{-# INLINE size #-}

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

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

-- | /O(1)/. The union of two sets.
union ::  a. AsEnumSet a => EnumSet a -> EnumSet a -> EnumSet a
union = E.union
{-# INLINE union #-}

-- | /O(1)/. Difference between two sets.
difference ::  a. AsEnumSet a => EnumSet a -> EnumSet a -> EnumSet a
difference = E.difference
{-# INLINE difference #-}

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

-- | /O(1)/. Elements which are in either set, but not both.
symmetricDifference ::  a. AsEnumSet a => EnumSet a -> EnumSet a -> EnumSet a
symmetricDifference = E.symmetricDifference
{-# INLINE symmetricDifference #-}

-- | /O(1)/. The intersection of two sets.
intersection ::  a. AsEnumSet a => EnumSet a -> EnumSet a -> EnumSet a
intersection = E.intersection
{-# INLINE intersection #-}

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

-- | /O(n)/. Filter all elements that satisfy some predicate.
filter ::  a. AsEnumSet a => (a -> Bool) -> EnumSet a -> EnumSet a
filter = E.filter
{-# INLINE filter #-}

-- | /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 ::  a. AsEnumSet a
          => (a -> Bool) -> EnumSet a -> (EnumSet a, EnumSet a)
partition = E.partition
{-# INLINE partition #-}

{--------------------------------------------------------------------
  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 ::  a b. (AsEnumSet a, AsEnumSet b) => (a -> b) -> EnumSet a -> EnumSet b
map = E.map'

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

-- | /O(n)/. Left fold.
foldl ::  a b. AsEnumSet a => (b -> a -> b) -> b -> EnumSet a -> b
foldl = E.foldl
{-# INLINE foldl #-}

-- | /O(n)/. Left fold with strict accumulator.
foldl' ::  a b. AsEnumSet a => (b -> a -> b) -> b -> EnumSet a -> b
foldl' = E.foldl'
{-# INLINE foldl' #-}

-- | /O(n)/. Right fold.
foldr ::  a b. AsEnumSet a => (a -> b -> b) -> b -> EnumSet a -> b
foldr = E.foldr
{-# INLINE foldr #-}

-- | /O(n)/. Right fold with strict accumulator.
foldr' ::  a b. AsEnumSet a => (a -> b -> b) -> b -> EnumSet a -> b
foldr' = E.foldr'
{-# INLINE foldr' #-}

-- | /O(n)/. Left fold on non-empty sets.
foldl1 ::  a. AsEnumSet a => (a -> a -> a) -> EnumSet a -> a
foldl1 = E.foldl1
{-# INLINE foldl1 #-}

-- | /O(n)/. Left fold on non-empty sets with strict accumulator.
foldl1' ::  a. AsEnumSet a => (a -> a -> a) -> EnumSet a -> a
foldl1' = E.foldl1'
{-# INLINE foldl1' #-}

-- | /O(n)/. Right fold on non-empty sets.
foldr1 ::  a. AsEnumSet a => (a -> a -> a) -> EnumSet a -> a
foldr1 = E.foldr1
{-# INLINE foldr1 #-}

-- | /O(n)/. Right fold on non-empty sets with strict accumulator.
foldr1' ::  a. AsEnumSet a => (a -> a -> a) -> EnumSet a -> a
foldr1' = E.foldr1'
{-# INLINE foldr1'  #-}

-- | /O(n)/. Map each element of the structure to a monoid, and combine the
-- results.
foldMap ::  m a. (Monoid m, AsEnumSet a) => (a -> m) -> EnumSet a -> m
foldMap = E.foldMap
{-# INLINE foldMap #-}

traverse ::  f a. (Applicative f, AsEnumSet a)
         => (a -> f a) -> EnumSet a -> f (EnumSet a)
traverse = E.traverse
{-# INLINE traverse #-}

-- | /O(n)/. Check if all elements satisfy some predicate.
all ::  a. AsEnumSet a => (a -> Bool) -> EnumSet a -> Bool
all = E.all
{-# INLINE all #-}

-- | /O(n)/. Check if any element satisfies some predicate.
any ::  a. AsEnumSet a => (a -> Bool) -> EnumSet a -> Bool
any = E.any
{-# INLINE any #-}

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

-- | /O(1)/. The minimal element of a non-empty set.
minimum ::  a. AsEnumSet a => EnumSet a -> a
minimum = E.minimum
{-# INLINE minimum #-}

-- | /O(1)/. The maximal element of a non-empty set.
maximum ::  a. AsEnumSet a => EnumSet a -> a
maximum = E.maximum
{-# INLINE maximum #-}

-- | /O(1)/. Delete the minimal element.
deleteMin ::  a. AsEnumSet a => EnumSet a -> EnumSet a
deleteMin = E.deleteMin
{-# INLINE deleteMin #-}

-- | /O(1)/. Delete the maximal element.
deleteMax ::  a. AsEnumSet a => EnumSet a -> EnumSet a
deleteMax = E.deleteMax
{-# INLINE deleteMax #-}

-- | /O(1)/. Retrieves the minimal element of the set,
-- and the set stripped of that element,
-- or Nothing if passed an empty set.
minView ::  a. AsEnumSet a => EnumSet a -> Maybe (a, EnumSet a)
minView = E.minView
{-# INLINE minView #-}

-- | /O(1)/. Retrieves the maximal element of the set,
-- and the set stripped of that element,
-- or Nothing if passed an empty set.
maxView ::  a. AsEnumSet a => EnumSet a -> Maybe (a, EnumSet a)
maxView = E.maxView
{-# INLINE maxView #-}

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

-- | /O(n)/. Convert the set to a list of values.
toList ::  a. AsEnumSet a => EnumSet a -> [a]
toList = E.toList
{-# INLINE toList #-}

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