module Data.PrefixTree (
    PrefixTree
    
  , empty
  , singleton
  , insert
  , delete
  , toList
  , fromList
    
  , lookup
  , member
  , matches
  , match
  , elems
  , keys
  , key
  ) where
import Prelude hiding (lookup)
import Data.Maybe (isJust,listToMaybe)
#ifdef TESTS
import Data.List (nub)
import Test.QuickCheck
#endif
data PrefixTree a
  = Empty
  | Prefix Key (Maybe a) (PrefixTree a)
  | Branch (PrefixTree a) (PrefixTree a)
    deriving Show
type Key = [Bool]
matchPrefix :: Key -> Key -> (Key,Key,Key)
matchPrefix = loop id
  where
  loop k (a:as) (b:bs) | a == b = loop (k . (a:)) as bs
  loop k as     bs              = (k [], as, bs)
empty :: PrefixTree a
empty  = Empty
singleton :: Key -> a -> PrefixTree a
singleton ks a = Prefix ks (Just a) empty
fromList :: [(Key,a)] -> PrefixTree a
fromList  = foldr (uncurry insert) empty
toList :: PrefixTree a -> [([Bool], a)]
toList t =
  case t of
    Empty -> []
    Prefix ls mb t' ->
      case mb of
        Nothing -> map (prefix ls) (toList t')
        Just a  -> (ls,a) : map (prefix ls) (toList t')
    Branch l r -> toList l ++ toList r
  where
  prefix ls (ks,a) = (ls ++ ks, a)
elems :: PrefixTree a -> [a]
elems t =
  case t of
    Empty                -> []
    Prefix _ (Just a) t' -> a : elems t'
    Prefix _ _        t' -> elems t'
    Branch l r           -> elems l ++ elems r
insert :: Key -> a -> PrefixTree a -> PrefixTree a
insert ks a t =
  case t of
    Empty -> singleton ks a
    Prefix ls mb t' ->
      case matchPrefix ks ls of
        
        ([],[],[]) -> Prefix [] (Just a) t'
        
        ([],[],_) -> Prefix [] (Just a) t
        
        ([],_,[]) -> Prefix [] mb (insert ks a t')
        
        ([], k:_, _)
          | k         -> Branch (singleton ks a) t
          | otherwise -> Branch t (singleton ks a)
        
        (_, [], []) -> Prefix ks (Just a) t'
        
        (_ ,ks',[]) -> Prefix ls mb (insert ks' a t')
        
        (_,[],ls') -> Prefix ks (Just a) (Prefix ls' mb t')
        
        (ps,ks'@(k:_),ls') -> Prefix ps Nothing br
          where
          t1             = singleton ks' a
          t2             = Prefix ls' mb t'
          br | k         = Branch t1 t2
             | otherwise = Branch t2 t1
    Branch l r ->
      case ks of
        []              -> Prefix [] (Just a) t
        b:_ | b         -> Branch (insert ks a l) r
            | otherwise -> Branch l (insert ks a r)
delete :: Key -> PrefixTree a -> PrefixTree a
delete ks t =
  case t of
    Empty -> Empty
    Prefix ls mb t' ->
      case matchPrefix ks ls of
        (_,[],[])   -> compact (Prefix ls Nothing t')
        ([],ks',[]) -> compact (Prefix ls mb (delete ks' t'))
        _           -> t
    Branch l r ->
      case ks of
        []               -> t
        b:bs | b         -> compact (Branch (delete bs l) r)
             | otherwise -> compact (Branch l (delete bs r))
compact :: PrefixTree a -> PrefixTree a
compact t =
  case t of
    Prefix ls Nothing (Prefix ks mb t') -> Prefix (ls ++ ks) mb t'
    Branch l Empty -> l
    Branch Empty r -> r
    _ -> t
member :: Key -> PrefixTree a -> Bool
member ks t =
  case t of
    Empty -> False
    Prefix ls mb t' ->
      case matchPrefix ks ls of
        (_,[], []) -> isJust mb
        (_,ks',[]) -> member ks' t'
        _          -> False
    Branch l r ->
      case ks of
        []              -> False
        b:_ | b         -> member ks l
            | otherwise -> member ks r
matches :: Key -> PrefixTree a -> [a]
matches = loop []
  where
  loop ms ks t =
    case t of
      Empty -> ms
      Prefix ls mb t' ->
        case matchPrefix ks ls of
          (_,[], []) -> maybe ms (:ms) mb
          (_,ks',[]) -> loop (maybe ms (:ms) mb) ks' t'
          _          -> ms
      Branch l r ->
        case ks of
          []              -> ms
          b:_ | b         -> loop ms ks l
              | otherwise -> loop ms ks r
match :: Key -> PrefixTree a -> Maybe a
match k t = listToMaybe (matches k t)
lookup :: Key -> PrefixTree a -> Maybe a
lookup = match
keys :: Key -> PrefixTree a -> [Key]
keys  = keys' [] []
  where
  keys' as p ks t =
    case t of
      Empty -> as
      Prefix ls _ t' ->
        case matchPrefix ks ls of
          (ps,ks',[]) -> keys' (p':as) p' ks' t'
            where p' = p ++ ps
          _           -> as
      Branch l r -> keys' ls p ks r
        where ls = keys' as p ks l
key :: Key -> PrefixTree a -> Maybe Key
key ks t = listToMaybe (keys ks t)
#ifdef TESTS
forAllUniqueLists :: (Testable prop, Arbitrary a, Show a, Eq a)
                  => ([a] -> prop) -> Property
forAllUniqueLists = forAll (nub `fmap` arbitrary)
prop_toList_fromList = forAllUniqueLists p
  where
  p :: [([Bool],())] -> Bool
  p bs = length bs == length bs' && all (`elem` bs) bs'
    where bs' = toList (fromList bs)
prop_matchesOrder bs = and (map (f . fst) bs)
  where
  t1  = fromList bs
  t2  = fromList (reverse bs)
  f k = matches k t1 == matches k t2
#endif