{- |
   Module      : Data.Graph.Analysis.Algorithms.Directed
   Description : Algorithms for directed graphs.
   Copyright   : (c) Ivan Lazar Miljenovic 2008
   License     : 2-Clause BSD
   Maintainer  : Ivan.Miljenovic@gmail.com

   Defines algorithms that work on both directed graphs.
 -}
module Data.Graph.Analysis.Algorithms.Directed
    ( -- * Ending nodes
      -- $ends
      endNode, endNode',
      endBy, endBy',
      -- ** Root nodes
      rootsOf, rootsOf',
      isRoot, isRoot',
      -- ** Leaf nodes
      leavesOf, leavesOf',
      isLeaf, isLeaf',
      -- ** Singleton nodes
      singletonsOf, singletonsOf',
      isSingleton, isSingleton',
      -- * Subgraphs
      coreOf,
    ) where

import Data.Graph.Analysis.Types
import Data.Graph.Analysis.Utils

import Data.Graph.Inductive.Graph

-- -----------------------------------------------------------------------------
{- $ends
   Find starting/ending nodes.

   We define an ending node as one where, given a function:

   @
     f :: (Graph g) => g a b -> Node -> [Node]
   @

   the only allowed result is that node itself (to allow for loops).
 -}

-- | Determine if this 'LNode' is an ending node.
endNode :: (Graph g) => (g a b -> Node -> NGroup) -> g a b -> LNode a -> Bool
endNode f g ln = endNode' f g (node ln)

-- | Determine if this 'Node' is an ending node.
endNode'       :: (Graph g) => (g a b -> Node -> NGroup) -> g a b -> Node
               -> Bool
endNode' f g n = case (f g n) of
                  []   -> True
                  -- Allow loops
                  [n'] -> n' == n
                  _    -> False

-- | Find all 'LNode's that meet the ending criteria.
endBy   :: (Graph g) => (g a b -> Node -> NGroup) -> g a b -> LNGroup a
endBy f = filterNodes (endNode f)

-- | Find all 'Node's that match the ending criteria.
endBy'   :: (Graph g) => (g a b -> Node -> NGroup) -> g a b -> NGroup
endBy' f = filterNodes' (endNode' f)

-- -----------------------------------------------------------------------------

{-
   Root detection.
 -}

-- | Find all roots of the graph.
rootsOf :: (Graph g) => g a b -> LNGroup a
rootsOf = endBy pre

-- | Find all roots of the graph.
rootsOf' :: (Graph g) => g a b -> NGroup
rootsOf' = endBy' pre

-- | Returns @True@ if this 'LNode' is a root.
isRoot :: (Graph g) => g a b -> LNode a -> Bool
isRoot = endNode pre

-- | Returns @True@ if this 'Node' is a root.
isRoot' :: (Graph g) => g a b -> Node -> Bool
isRoot' = endNode' pre

-- -----------------------------------------------------------------------------

{-
   Leaf detection.
 -}

-- | Find all leaves of the graph.
leavesOf :: (Graph g) => g a b -> LNGroup a
leavesOf = endBy pre

-- | Find all leaves of the graph.
leavesOf' :: (Graph g) => g a b -> NGroup
leavesOf' = endBy' pre

-- | Returns @True@ if this 'LNode' is a leaf.
isLeaf :: (Graph g) => g a b -> LNode a -> Bool
isLeaf = endNode pre

-- | Returns @True@ if this 'Node' is a leaf.
isLeaf' :: (Graph g) => g a b -> Node -> Bool
isLeaf' = endNode' pre

-- -----------------------------------------------------------------------------

{-
   Singleton detection.
 -}

-- | Find all singletons of the graph.
singletonsOf :: (Graph g) => g a b -> LNGroup a
singletonsOf = endBy pre

-- | Find all singletons of the graph.
singletonsOf' :: (Graph g) => g a b -> NGroup
singletonsOf' = endBy' pre

-- | Returns @True@ if this 'LNode' is a singleton.
isSingleton :: (Graph g) => g a b -> LNode a -> Bool
isSingleton = endNode pre

-- | Returns @True@ if this 'Node' is a singleton.
isSingleton' :: (Graph g) => g a b -> Node -> Bool
isSingleton' = endNode' pre

-- -----------------------------------------------------------------------------

{- |
   The /core/ of the graph is the part of the graph containing all the
   cycles, etc.  Depending on the context, it could be interpreted as
   the part of the graph where all the "work" is done.
 -}
coreOf :: (DynGraph g, Eq a, Eq b) => g a b -> g a b
coreOf = fixPointGraphs stripEnds
    where
      stripEnds gr' = delNodes roots . delNodes leaves $ gr'
          where
            roots = rootsOf' gr'
            leaves = leavesOf' gr'