-- | STM mutable tree
module Haskus.Utils.STM.TTree
   ( TTree (..)
   , TTreePath (..)
   , singleton
   , addChild
   , detachChild
   , attachChild
   , treeFollowPath
   )
where

import qualified Haskus.Utils.STM.TList as TList
import Haskus.Utils.STM.TList (TList)
import Haskus.Utils.STM.TEq
import Haskus.Utils.STM

-- | A STM mutable tree
data TTree k v = TTree
   { treeKey      :: k                        -- ^ Node identifier
   , treeValue    :: v                        -- ^ Node value
   , treeChildren :: TList (TTree k v)        -- ^ Children
   , treeParent   :: TVar (Maybe (TTree k v)) -- ^ Parent
   }

-- | Path in the tree
newtype TTreePath k = TTreePath [k]


-- | Create a singleton node
singleton :: k -> v -> STM (TTree k v)
singleton k v =
   TTree k v
      <$> TList.empty
      <*> newTVar Nothing

-- | Add a child
addChild :: k -> v -> TTree k v -> STM (TTree k v)
addChild k v parent = do
   n <- TTree k v
      <$> TList.empty
      <*> newTVar (Just parent)

   TList.append_ n (treeChildren parent)
   return n

-- | Detach a child
detachChild :: TEq k => TTree k v -> STM ()
detachChild n = do

   -- remove child from parent
   let f c = not <$> (treeKey c `teq` treeKey n)
   p <- readTVar (treeParent n)
   mapM_ (TList.filter f . treeChildren) p

   -- remove parent from child
   writeTVar (treeParent n) Nothing

-- | Attach a child a node (detaching it from a previous one if necessary)
attachChild :: TEq k => TTree k v -> TTree k v -> STM ()
attachChild newparent child = do
   -- detach from the previous parent (if any)
   detachChild child

   -- add to newparent's children list
   TList.append_ child (treeChildren newparent)

   -- set newparent
   writeTVar (treeParent child) (Just newparent)

-- | Follow a path from a parent node
treeFollowPath :: TEq k => TTree k v -> TTreePath k -> STM (Maybe (TTree k v))
treeFollowPath p (TTreePath [])     = return (Just p)
treeFollowPath p (TTreePath (x:xs)) = do
   child <- TList.find (\y -> x `teq` treeKey y) (treeChildren p)
   case TList.value <$> child of
      Just c  -> treeFollowPath c (TTreePath xs)
      Nothing -> return Nothing