module TrieIO where
import IOExts

data TrieIO a = TrieIO String a (IORef [TrieIO a])
     
initTrie :: String -> a -> IO (TrieIO a)
initTrie s a = do
 cref <- newIORef []
 return $ TrieIO s a cref

registerInTrie :: TrieIO a -> String -> a -> IO a
registerInTrie t p v = do
  (mbres,_) <- register t
  return $ maybe v id mbres
 where
  register t@(TrieIO s a ref) = do
   if p == s then return (Just a,True)
    else if s `prefix` p then do
     ts <- readIORef ref
     (mba,regd) <- regList ts
     if regd then return (mba,regd)
      else do
        cref <- newIORef []
        writeIORef ref (TrieIO p v cref:ts)
        return (Nothing,True)
    else return (Nothing,False)
  
  regList [] = return (Nothing,False)
  regList (t:ts) = do
    (mba,regd) <- register t
    if regd then return (mba,True)
     else regList ts


lookupTrie :: TrieIO a -> String -> IO (Maybe a)
lookupTrie t p = findOne t 
 where 
 findOne (TrieIO s a cref) =
  if s == p then return $ Just a
  else if s `prefix` p then do
    cs <- readIORef cref
    findList cs
  else
    return Nothing

 findList [] = return Nothing
 findList (t:ts) = do
   mbv <- findOne t
   maybe (findList ts) (return . Just) mbv

foldTrie :: TrieIO a -> IO [a]
foldTrie (TrieIO s a cref) = do 
  cs <- readIORef cref
  vs <- fmap concat $ mapM foldTrie cs
  return $ a:vs


deleteFromTrie :: String -> TrieIO a -> IO (Bool,Bool,[a])
deleteFromTrie p t =  delete t
 where
  delete t@(TrieIO s a cref) = do
   if s == p then do
      vs <- foldTrie t 
      return (True,True,vs)
    else if s `prefix` p then do
      cs <- readIORef cref
      (isdead, siblings,children) <- delList cs
      writeIORef cref siblings
      return (isdead,False,children)
    else
      return (False,False,[])

  delList [] = return (False,[],[])
  delList (t:ts) = do
    (isdead, medead, children) <- delete t
    if isdead then
      return (True,(if medead then [] else [t]) ++ ts,children)
     else do
      (isdead,siblings,children) <- delList ts
      return (isdead,t:siblings,children)
      
      

prefix :: String -> String -> Bool
prefix "" _ = True
prefix (c:tag) c_tag' =
   case c_tag' of
	c':tag' | c==c' -> prefix tag tag'
	_               -> False
