module Data.LinkedHashSet
    (
     LinkedHashSet

    -- * Construction
    , empty
    , singleton

    -- * Combine
    -- , union
    -- , unions

    -- * Basic interface
    , null
    , size
    , member
    , insert
    , delete

    -- * Transformations
    -- , map

      -- * Difference and intersection
    -- , difference
    -- , intersection

    -- * Folds
    -- , foldl'
    -- , foldr

    -- * Filter
    -- , filter

    -- ** Lists
    , toList
    , fromList
    ) where

import Prelude hiding (null, lookup)
import Control.DeepSeq (NFData(rnf))
import Data.Hashable (Hashable)
import qualified Data.List as L
import qualified Data.LinkedHashMap.Seq as M

-- | A set of values.  A set cannot contain duplicate values.
newtype LinkedHashSet a = LinkedHashSet {
      asMap :: M.LinkedHashMap a ()
    }

instance (Show a) => Show (LinkedHashSet a) where
    showsPrec d m = showParen (d > 10) $
      showString "fromList " . shows (toList m)

-- | /O(1)/ Construct an empty set.
empty :: LinkedHashSet a
empty = LinkedHashSet M.empty

-- | /O(1)/ Construct a set with a single element.
singleton :: (Eq a, Hashable a) => a -> LinkedHashSet a
singleton a = LinkedHashSet (M.singleton a ())
{-# INLINABLE singleton #-}

-- -- | /O(n+m)/ Construct a set containing all elements from both sets.
-- --
-- -- To obtain good performance, the smaller set must be presented as
-- -- the first argument.
-- union :: (Eq a, Hashable a) => LinkedHashSet a -> LinkedHashSet a -> LinkedHashSet a
-- union s1 s2 = LinkedHashSet $ M.union (asMap s1) (asMap s2)
-- {-# INLINE union #-}

-- -- | Construct a set containing all elements from a list of sets.
-- unions :: (Eq a, Hashable a) => [LinkedHashSet a] -> LinkedHashSet a
-- unions = List.foldl' union empty
-- {-# INLINE unions #-}

-- | /O(1)/ Return 'True' if this set is empty, 'False' otherwise.
null :: LinkedHashSet a -> Bool
null = M.null . asMap
{-# INLINE null #-}

-- | /O(1)/ Return the number of elements in this set.
size :: LinkedHashSet a -> Int
size = M.size . asMap
{-# INLINE size #-}

-- | /O(min(n,W))/ Return 'True' if the given value is present in this
-- set, 'False' otherwise.
member :: (Eq a, Hashable a) => a -> LinkedHashSet a -> Bool
member a s = case M.lookup a (asMap s) of
               Just _ -> True
               _      -> False
{-# INLINABLE member #-}

-- | /O(min(n,W))/ Add the specified value to this set.
insert :: (Eq a, Hashable a) => a -> LinkedHashSet a -> LinkedHashSet a
insert a = LinkedHashSet . M.insert a () . asMap
{-# INLINABLE insert #-}

-- | /O(min(n,W))/ Remove the specified value from this set if
-- present.
delete :: (Eq a, Hashable a) => a -> LinkedHashSet a -> LinkedHashSet a
delete a = LinkedHashSet . M.delete a . asMap
{-# INLINABLE delete #-}

-- -- | /O(n)/ Transform this set by applying a function to every value.
-- -- The resulting set may be smaller than the source.
-- map :: (Hashable b, Eq b) => (a -> b) -> LinkedHashSet a -> LinkedHashSet b
-- map f = fromList . List.map f . toList
-- {-# INLINE map #-}

-- -- | /O(n)/ Difference of two sets. Return elements of the first set
-- -- not existing in the second.
-- difference :: (Eq a, Hashable a) => LinkedHashSet a -> LinkedHashSet a -> LinkedHashSet a
-- difference (LinkedHashSet a) (LinkedHashSet b) = LinkedHashSet (M.difference a b)
-- {-# INLINABLE difference #-}

-- -- | /O(n)/ Intersection of two sets. Return elements present in both
-- -- the first set and the second.
-- intersection :: (Eq a, Hashable a) => LinkedHashSet a -> LinkedHashSet a -> LinkedHashSet a
-- intersection (LinkedHashSet a) (LinkedHashSet b) = LinkedHashSet (M.intersection a b)
-- {-# INLINABLE intersection #-}

-- -- | /O(n)/ Reduce this set by applying a binary operator to all
-- -- elements, using the given starting value (typically the
-- -- left-identity of the operator).  Each application of the operator
-- -- is evaluated before before using the result in the next
-- -- application.  This function is strict in the starting value.
-- foldl' :: (a -> b -> a) -> a -> LinkedHashSet b -> a
-- foldl' f z0 = M.foldlWithKey' g z0 . asMap
--   where g z k _ = f z k
-- {-# INLINE foldl' #-}

-- -- | /O(n)/ Reduce this set by applying a binary operator to all
-- -- elements, using the given starting value (typically the
-- -- right-identity of the operator).
-- foldr :: (b -> a -> a) -> a -> LinkedHashSet b -> a
-- foldr f z0 = foldrWithKey g z0 . asMap
--   where g k _ z = f k z
-- {-# INLINE foldr #-}

-- -- | /O(n)/ Filter this set by retaining only elements satisfying a
-- -- predicate.
-- filter :: (a -> Bool) -> LinkedHashSet a -> LinkedHashSet a
-- filter p = LinkedHashSet . H.filterWithKey q . asMap
--   where q k _ = p k
-- {-# INLINE filter #-}

-- | /O(n)/ Return a list of this set's elements.  The list is produced lazily.
toList :: LinkedHashSet a -> [a]
toList t = map (\(k, _) -> k) $ M.toList (asMap t)
{-# INLINE toList #-}

-- | /O(n*min(W, n))/ Construct a set from a list of elements.
fromList :: (Eq a, Hashable a) => [a] -> LinkedHashSet a
fromList as = LinkedHashSet $ M.fromList $ zip as $ cycle [()]
{-# INLINE fromList #-}

instance (NFData a) => NFData (LinkedHashSet a) where
  rnf = rnf . asMap