module Zeno.Traversing (
  WithinTraversable (..), Substitution,
  mapWithin, substitute,
  replaceWithin, replaceManyWithin, 
  withinList, strictlyWithinList,
  contains, containsStrictly,
  removeSupersets, removeSubsets,
  showSubstitution
) where

import Prelude ()
import Zeno.Prelude
import Zeno.Utils

import qualified Data.Map as Map
import qualified Data.Set as Set

type Substitution = Map

showSubstitution :: (Show k, Show v) => Substitution k v -> String
showSubstitution (Map.toList -> subs) =
  "[ " ++ (intercalate "; " . map showSub) subs ++ " ]"
  where showSub (k, v) = show k ++ " -> " ++ show v

class WithinTraversable t f where
  mapWithinM :: Monad m => (t -> m t) -> f -> m f
  
instance Traversable f => WithinTraversable a (f a) where
  mapWithinM = mapM
  
substitute :: (WithinTraversable a f, Eq a) => 
  Substitution a a -> f -> f
substitute map = replaceManyWithin (Map.toList map)

isOneToOne :: Ord b => Substitution a b -> Bool
isOneToOne = not . containsDuplicates . Map.elems
  
mapWithin :: WithinTraversable t f => (t -> t) -> f -> f
mapWithin = mapM_to_fmap mapWithinM

anyWithin :: forall t f .  WithinTraversable t f => (t -> Bool) -> f -> Bool
anyWithin p = getAny . execWriter . mapWithinM tellAny
  where
  tellAny :: t -> Writer Any t
  tellAny t | p t = tell (Any True) >> return t
            | otherwise = return t

replaceWithin :: (WithinTraversable t f, Eq t) => t -> t -> f -> f
replaceWithin = genericReplace mapWithin

replaceManyWithin :: (Foldable f, WithinTraversable a t, Eq a) => 
  f (a, a) -> t -> t
replaceManyWithin = genericReplaceMany mapWithin

withinList :: WithinTraversable t f => f -> [t]
withinList = execWriter . mapWithinM (\t -> tell [t] >> return t)

strictlyWithinList :: (WithinTraversable t t, Eq t) => t -> [t]
strictlyWithinList t = filter (/= t) (withinList t)

contains :: (WithinTraversable t f, Eq t) => f -> t -> Bool
contains f = flip elem (withinList f)

containsStrictly :: (WithinTraversable t t, Eq t) => t -> t -> Bool
containsStrictly t = flip elem (strictlyWithinList t)

removeSubsets :: (WithinTraversable a a, Eq a) => [a] -> [a]
removeSubsets sets = filter (not . isSubset) sets
  where isSubset set = any (flip containsStrictly set) sets

removeSupersets :: (WithinTraversable a a, Eq a) => [a] -> [a]
removeSupersets sets = filter (not . isSuperset) sets
  where isSuperset set = any (containsStrictly set) sets
  
