-- |
-- Module      : Algebra.Closure.Set.DepthFirst
-- Copyright   : (c) Joseph Abrahamson 2013
-- License     : MIT
-- 
-- Maintainer  : me@jspha.com
-- Stability   : experimental
-- Portability : non-portable
-- 
-- Depth-first closed sets. For a particular endomorphism @(p :: a ->
-- a)@ a 'Closed' set is a set where if some element @x@ is in the set
-- then so is @p x@.

module Algebra.Closure.Set.DepthFirst (

  -- * Closed sets
  Closed, seen,

  -- ** Operations
  member,

  -- ** Creation
  empty, insert, close,
  
  ) where

import Prelude hiding (foldr)
import Data.HashSet (HashSet)
import Data.Hashable
import Data.Foldable (Foldable, foldr)
import qualified Data.HashSet as Set

-- | A closed set @Closed a@, given an endomorphism @(p :: a -> a)@,
-- is a set where if some element @x@ is in the set then so is @p x@.
data Closed a = Closed (HashSet a) (a -> a)

-- | Access the underlying set.
seen :: Closed a -> HashSet a
seen (Closed set _) = set

-- | Inserts a new element into a 'Closed' set, maintaining closure.
insert :: (Hashable a, Eq a) => a -> Closed a -> Closed a
insert a c@(Closed set iter)
  | Set.member a set = c
  | otherwise        = insert (iter a) $ Closed (Set.insert a set) iter

-- | An empty closed set under a fixed endomorphism.
empty :: (a -> a) -> Closed a
empty = Closed Set.empty

-- | Is a particular element in the closure of this set?
member :: (Hashable a, Eq a) => a -> Closed a -> Bool
member a = Set.member a . seen

-- | Converts any 'Foldable' container into the 'Closed' set of its
-- contents.
close :: (Hashable a, Eq a, Foldable t) => (a -> a) -> t a -> Closed a
close iter = foldr insert (empty iter)