{-# LANGUAGE CPP #-}

----------------------------------------------------------------------
-- |
-- Module      :  Unbound.Util
-- License     :  BSD-like (see LICENSE)
-- Maintainer  :  Brent Yorgey <byorgey@cis.upenn.edu>, Stephanie Weirich <sweirich@cis.upenn.edu>
-- Portability :  GHC only (-XKitchenSink)
--
-- Various utilities for the Unbound library.
----------------------------------------------------------------------
module Unbound.Util where

import Data.Maybe (catMaybes)
import Data.Monoid
import qualified Data.Foldable as F

import qualified Data.Set as S
import qualified Data.Map as M

------------------------------------------------------------
-- Convenient Monoid syntax
------------------------------------------------------------

-- As of base-4.5, (<>) is exported by Data.Monoid.

#if MIN_VERSION_base(4,5,0)
#else
(<>) :: Monoid m => m -> m -> m
(<>) = mappend
#endif

------------------------------------------------------------
-- Collections
------------------------------------------------------------

-- | Collections are foldable types that support empty, singleton,
--   union, and map operations.  The result of a free variable
--   calculation may be any collection.  Instances are provided for
--   lists, sets, and multisets.
class F.Foldable f => Collection f where

  -- | An empty collection. Must be the identity for @union@.
  emptyC    :: f a

  -- | Create a singleton collection.
  singleton :: a -> f a

  -- | An associative combining operation.  The @Ord@ constraint is in
  --   order to accommodate sets.
  union     :: Ord a => f a -> f a -> f a

  -- | Collections must be functorial.  The normal @Functor@ class
  --   won't do because of the @Ord@ constraint on sets.
  cmap      :: (Ord a, Ord b) => (a -> b) -> f a -> f b


-- | Combine a list of containers into one.
unions :: (Ord a, Collection f) => [f a] -> f a
unions = foldr union emptyC

-- | Create a collection from a list of elements.
fromList :: (Ord a, Collection f) => [a] -> f a
fromList = unions . map singleton

-- | Remove the @Nothing@s from a collection.
filterC :: (Collection f, Ord a) => f (Maybe a) -> f a
filterC = fromList . catMaybes . F.toList

-- | Lists are containers under concatenation.  Lists preserve
--   ordering and multiplicity of elements.
instance Collection [] where
  emptyC    = []
  singleton = (:[])
  union     = (++)
  cmap      = map

-- | A simple representation of multisets.
newtype Multiset a = Multiset (M.Map a Int)

instance F.Foldable Multiset where
  fold      (Multiset m) = M.foldrWithKey (\a n x -> mconcat (x : replicate n a)) mempty m
  foldMap f (Multiset m) = M.foldrWithKey (\a n x -> mconcat (x : replicate n (f a))) mempty m

-- | Multisets are containers which preserve multiplicity but not
--   ordering.
instance Collection Multiset where
  emptyC                              = Multiset M.empty
  singleton                           = Multiset . flip M.singleton 1
  (Multiset m1) `union` (Multiset m2) = Multiset $ M.unionWith (+) m1 m2
  cmap f (Multiset m)                 = Multiset $ M.mapKeys f m

-- | Sets are containers under union, which preserve only occurrence,
--   not multiplicity or ordering.
instance Collection S.Set where
  emptyC    = S.empty
  singleton = S.singleton
  union     = S.union
  cmap      = S.map

-- | Determine whether two sets have an empty intersection
disjoint :: Ord a => S.Set a -> S.Set a -> Bool
disjoint s1 s2 = S.null( S.intersection s1 s2 )