module Data.DAWG.Dynamic
(
  DAWG
, numStates
, numEdges
, lookup
, empty
, fromList
, fromListWith
, fromLang
, insert
, insertWith
, delete
, assocs
, keys
, elems
) where
import Prelude hiding (lookup)
import Control.Applicative ((<$>), (<*>))
import Control.Arrow (first)
import Data.List (foldl')
import qualified Control.Monad.State.Strict as S
import Data.DAWG.Types
import Data.DAWG.Graph (Graph)
import Data.DAWG.Dynamic.Internal
import qualified Data.DAWG.Trans as T
import qualified Data.DAWG.Graph as G
import qualified Data.DAWG.Dynamic.Node as N
type GraphM a b = S.State (Graph (N.Node a)) b
mkState :: (Graph a -> Graph a) -> Graph a -> ((), Graph a)
mkState f g = ((), f g)
nodeBy :: ID -> GraphM a (N.Node a)
nodeBy i = G.nodeBy i <$> S.get
insertNode :: Ord a => N.Node a -> GraphM a ID
insertNode = S.state . G.insert
insertLeaf :: Ord a => GraphM a ID
insertLeaf = do
    i <- insertNode (N.Leaf Nothing)
    insertNode (N.Branch i T.empty)
deleteNode :: Ord a => N.Node a -> GraphM a ()
deleteNode = S.state . mkState . G.delete
insertM :: Ord a => [Sym] -> a -> ID -> GraphM a ID
insertM (x:xs) y i = do
    n <- nodeBy i
    j <- case N.onSym x n of
        Just j  -> return j
        Nothing -> insertLeaf
    k <- insertM xs y j
    deleteNode n
    insertNode (N.insert x k n)
insertM [] y i = do
    n <- nodeBy i
    w <- nodeBy (N.eps n)
    deleteNode w
    deleteNode n
    j <- insertNode (N.Leaf $ Just y)
    insertNode (n { N.eps = j })
insertWithM
    :: Ord a => (a -> a -> a)
    -> [Sym] -> a -> ID -> GraphM a ID
insertWithM f (x:xs) y i = do
    n <- nodeBy i
    j <- case N.onSym x n of
        Just j  -> return j
        Nothing -> insertLeaf
    k <- insertWithM f xs y j
    deleteNode n
    insertNode (N.insert x k n)
insertWithM f [] y i = do
    n <- nodeBy i
    w <- nodeBy (N.eps n)
    deleteNode w
    deleteNode n
    let y'new = case N.value w of
            Just y' -> f y y'
            Nothing -> y
    j <- insertNode (N.Leaf $ Just y'new)
    insertNode (n { N.eps = j })
deleteM :: Ord a => [Sym] -> ID -> GraphM a ID
deleteM (x:xs) i = do
    n <- nodeBy i
    case N.onSym x n of
        Nothing -> return i
        Just j  -> do
            k <- deleteM xs j
            deleteNode n
            insertNode (N.insert x k n)
deleteM [] i = do
    n <- nodeBy i
    w <- nodeBy (N.eps n)
    deleteNode w
    deleteNode n
    j <- insertLeaf
    insertNode (n { N.eps = j })
    
lookupM :: [Sym] -> ID -> GraphM a (Maybe a)
lookupM [] i = do
    j <- N.eps <$> nodeBy i
    N.value <$> nodeBy j
lookupM (x:xs) i = do
    n <- nodeBy i
    case N.onSym x n of
        Just j  -> lookupM xs j
        Nothing -> return Nothing
assocsAcc :: Graph (N.Node a) -> ID -> [([Sym], a)]
assocsAcc g i =
    here w ++ concatMap there (N.edges n)
  where
    n = G.nodeBy i g
    w = G.nodeBy (N.eps n) g
    here v = case N.value v of
        Just x  -> [([], x)]
        Nothing -> []
    there (sym, j) = map (first (sym:)) (assocsAcc g j)
empty :: Ord b => DAWG a b
empty = 
    let (i, g) = S.runState insertLeaf G.empty
    in  DAWG g i
numStates :: DAWG a b -> Int
numStates = G.size . graph
numEdges :: DAWG a b -> Int
numEdges = sum . map (length . N.edges) . G.nodes . graph
insert :: (Enum a, Ord b) => [a] -> b -> DAWG a b -> DAWG a b
insert xs' y d =
    let xs = map fromEnum xs'
        (i, g) = S.runState (insertM xs y $ root d) (graph d)
    in  DAWG g i
insertWith
    :: (Enum a, Ord b) => (b -> b -> b)
    -> [a] -> b -> DAWG a b -> DAWG a b
insertWith f xs' y d =
    let xs = map fromEnum xs'
        (i, g) = S.runState (insertWithM f xs y $ root d) (graph d)
    in  DAWG g i
delete :: (Enum a, Ord b) => [a] -> DAWG a b -> DAWG a b
delete xs' d =
    let xs = map fromEnum xs'
        (i, g) = S.runState (deleteM xs $ root d) (graph d)
    in  DAWG g i
lookup :: (Enum a, Ord b) => [a] -> DAWG a b -> Maybe b
lookup xs' d =
    let xs = map fromEnum xs'
    in  S.evalState (lookupM xs $ root d) (graph d)
assocs :: (Enum a, Ord b) => DAWG a b -> [([a], b)]
assocs
    = map (first (map toEnum))
    . (assocsAcc <$> graph <*> root)
keys :: (Enum a, Ord b) => DAWG a b -> [[a]]
keys = map fst . assocs
elems :: Ord b => DAWG a b -> [b]
elems = map snd . (assocsAcc <$> graph <*> root)
fromList :: (Enum a, Ord b) => [([a], b)] -> DAWG a b
fromList xs =
    let update t (x, v) = insert x v t
    in  foldl' update empty xs
fromListWith
    :: (Enum a, Ord b) => (b -> b -> b)
    -> [([a], b)] -> DAWG a b
fromListWith f xs =
    let update t (x, v) = insertWith f x v t
    in  foldl' update empty xs
fromLang :: Enum a => [[a]] -> DAWG a ()
fromLang xs = fromList [(x, ()) | x <- xs]