{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}

-- |
-- Module : Data.EnumSet
-- Description : Data.IntSet, wrapped with Enum

module Data.EnumSet
  ( -- * Set type
    EnumSet,

    -- * Construction
    empty,
    singleton,
    fromList,
    fromAscList,
    fromDistinctAscList,

    -- * Insertion
    insert,

    -- * Deletion
    delete,

    -- * Query
    member,
    notMember,
    lookupLT,
    lookupGT,
    lookupLE,
    lookupGE,
    null,
    size,
    isSubsetOf,
    isProperSubsetOf,
    disjoint,

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

    -- * Filter
    filter,
    partition,
    split,
    splitMember,
    splitRoot,

    -- * Map
    map,

    -- * Folds
    foldr,
    foldl,

    -- ** Strict folds
    foldr',
    foldl',

    -- ** Legacy folds
    fold,

    -- * Min\/Max
    findMin,
    findMax,
    deleteMin,
    deleteMax,
    deleteFindMin,
    deleteFindMax,
    maxView,
    minView,

    -- * Conversion

    -- ** List
    elems,
    toList,
    toAscList,
    toDescList,
  )
where

import Data.Bifunctor (first)
import Data.Coerce (Coercible, coerce)
import Data.EnumSet.Wrapper (EnumSet (..))
import qualified Data.IntSet as IntSet
import Prelude
  ( ($),
    (.),
    Bool,
    Enum,
    Foldable,
    Int,
    Maybe,
    fmap,
    fromEnum,
    toEnum,
  )

(\\) :: EnumSet k -> EnumSet k -> EnumSet k
(\\) = coerce (IntSet.\\)
{-# INLINE (\\) #-}

null :: EnumSet k -> Bool
null = coerce IntSet.null
{-# INLINE null #-}

size :: EnumSet k -> Int
size = coerce IntSet.size
{-# INLINE size #-}

member :: Enum k => k -> EnumSet k -> Bool
member (fromEnum -> k) = coerce $ IntSet.member k
{-# INLINE member #-}

notMember :: Enum k => k -> EnumSet k -> Bool
notMember (fromEnum -> k) = coerce $ IntSet.notMember k
{-# INLINE notMember #-}

lookupLT :: Enum k => k -> EnumSet k -> Maybe k
lookupLT (fromEnum -> k) = fmap toEnum . coerce (IntSet.lookupLT k)
{-# INLINE lookupLT #-}

lookupGT :: Enum k => k -> EnumSet k -> Maybe k
lookupGT (fromEnum -> k) = fmap toEnum . coerce (IntSet.lookupGT k)
{-# INLINE lookupGT #-}

lookupLE :: Enum k => k -> EnumSet k -> Maybe k
lookupLE (fromEnum -> k) = fmap toEnum . coerce (IntSet.lookupLE k)
{-# INLINE lookupLE #-}

lookupGE :: Enum k => k -> EnumSet k -> Maybe k
lookupGE (fromEnum -> k) = fmap toEnum . coerce (IntSet.lookupGE k)
{-# INLINE lookupGE #-}

empty :: EnumSet k
empty = coerce IntSet.empty
{-# INLINE empty #-}

singleton :: Enum k => k -> EnumSet k
singleton (fromEnum -> k) = coerce $ IntSet.singleton k
{-# INLINE singleton #-}

insert :: Enum k => k -> EnumSet k -> EnumSet k
insert (fromEnum -> k) = coerce $ IntSet.insert k
{-# INLINE insert #-}

delete :: Enum k => k -> EnumSet k -> EnumSet k
delete (fromEnum -> k) = coerce $ IntSet.delete k
{-# INLINE delete #-}

unions :: forall f k. (Foldable f, Coercible (f IntSet.IntSet) (f (EnumSet k))) => f (EnumSet k) -> EnumSet k
unions = coerce $ IntSet.unions @f
{-# INLINE unions #-}

union :: EnumSet k -> EnumSet k -> EnumSet k
union = coerce $ IntSet.union
{-# INLINE union #-}

difference :: EnumSet k -> EnumSet k -> EnumSet k
difference = coerce $ IntSet.difference
{-# INLINE difference #-}

intersection :: EnumSet k -> EnumSet k -> EnumSet k
intersection = coerce $ IntSet.intersection
{-# INLINE intersection #-}

isProperSubsetOf :: EnumSet k -> EnumSet k -> Bool
isProperSubsetOf = coerce $ IntSet.isProperSubsetOf
{-# INLINE isProperSubsetOf #-}

isSubsetOf :: EnumSet k -> EnumSet k -> Bool
isSubsetOf = coerce $ IntSet.isSubsetOf
{-# INLINE isSubsetOf #-}

disjoint :: EnumSet k -> EnumSet k -> Bool
disjoint = coerce $ IntSet.disjoint
{-# INLINE disjoint #-}

filter :: Enum k => (k -> Bool) -> EnumSet k -> EnumSet k
filter ((. toEnum) -> p) = coerce $ IntSet.filter p
{-# INLINE filter #-}

partition :: Enum k => (k -> Bool) -> EnumSet k -> (EnumSet k, EnumSet k)
partition ((. toEnum) -> p) = coerce $ IntSet.partition p
{-# INLINE partition #-}

split :: Enum k => k -> EnumSet k -> (EnumSet k, EnumSet k)
split (fromEnum -> k) = coerce $ IntSet.split k
{-# INLINE split #-}

splitMember :: Enum k => k -> EnumSet k -> (EnumSet k, Bool, EnumSet k)
splitMember (fromEnum -> k) = coerce $ IntSet.splitMember k
{-# INLINE splitMember #-}

maxView :: Enum k => EnumSet k -> Maybe (k, EnumSet k)
maxView = fmap (first toEnum) . coerce IntSet.maxView
{-# INLINE maxView #-}

minView :: Enum k => EnumSet k -> Maybe (k, EnumSet k)
minView = fmap (first toEnum) . coerce IntSet.minView
{-# INLINE minView #-}

deleteFindMin :: Enum k => EnumSet k -> (k, EnumSet k)
deleteFindMin = first toEnum . coerce IntSet.deleteFindMin
{-# INLINE deleteFindMin #-}

deleteFindMax :: Enum k => EnumSet k -> (k, EnumSet k)
deleteFindMax = first toEnum . coerce IntSet.deleteFindMax
{-# INLINE deleteFindMax #-}

findMin :: Enum k => EnumSet k -> k
findMin = toEnum . coerce IntSet.findMin
{-# INLINE findMin #-}

findMax :: Enum k => EnumSet k -> k
findMax = toEnum . coerce IntSet.findMax
{-# INLINE findMax #-}

deleteMin :: EnumSet k -> EnumSet k
deleteMin = coerce IntSet.deleteMin
{-# INLINE deleteMin #-}

deleteMax :: Enum k => EnumSet k -> EnumSet k
deleteMax = coerce IntSet.deleteMax
{-# INLINE deleteMax #-}

map :: Enum k => (k -> k) -> EnumSet k -> EnumSet k
map ((. toEnum) . (fromEnum .) -> f)= coerce $ IntSet.map f
{-# INLINE map #-}

fold :: Enum k => (k -> b -> b) -> b -> EnumSet k -> b
fold ((. toEnum) -> f) = coerce $ IntSet.fold f
{-# INLINE fold #-}

foldr :: Enum k => (k -> b -> b) -> b -> EnumSet k -> b
foldr ((. toEnum) -> f) = coerce $ IntSet.foldr f
{-# INLINE foldr #-}

foldr' :: Enum k => (k -> b -> b) -> b -> EnumSet k -> b
foldr' ((. toEnum) -> f) = coerce $ IntSet.foldr' f
{-# INLINE foldr' #-}

foldl :: Enum k => (a -> k -> a) -> a -> EnumSet k -> a
foldl (fmap (. toEnum) -> f) = coerce $ IntSet.foldl f
{-# INLINE foldl #-}

foldl' :: Enum k => (a -> k -> a) -> a -> EnumSet k -> a
foldl' (fmap (. toEnum) -> f) = coerce $ IntSet.foldl' f
{-# INLINE foldl' #-}

elems :: Enum k => EnumSet k -> [k]
elems = fmap toEnum . coerce (IntSet.elems)
{-# INLINE elems #-}

toList :: Enum k => EnumSet k -> [k]
toList = fmap toEnum . coerce (IntSet.toList)
{-# INLINE toList #-}

toAscList :: Enum k => EnumSet k -> [k]
toAscList = fmap toEnum . coerce (IntSet.toAscList)
{-# INLINE toAscList #-}

toDescList :: Enum k => EnumSet k -> [k]
toDescList = fmap toEnum . coerce (IntSet.toDescList)
{-# INLINE toDescList #-}

fromList :: Enum k => [k] -> EnumSet k
fromList (fmap fromEnum -> xs) = coerce $ IntSet.fromList xs
{-# INLINE fromList #-}

fromAscList :: Enum k => [k] -> EnumSet k
fromAscList (fmap fromEnum -> xs) = coerce $ IntSet.fromAscList xs
{-# INLINE fromAscList #-}

fromDistinctAscList :: Enum k => [k] -> EnumSet k
fromDistinctAscList (fmap fromEnum -> xs) = coerce $ IntSet.fromDistinctAscList xs
{-# INLINE fromDistinctAscList #-}

splitRoot :: EnumSet k -> [EnumSet k]
splitRoot = coerce $ IntSet.splitRoot
{-# INLINE splitRoot #-}
