module Data.DiGraph( Graph ()
                   , empty
                   , create
                   , addNode
                   , addAllNodes
                   , addNeighbors
                   , addAllNeighbors
                   , contains
                   , deleteNode
                   , getNeighbors
                   , getNodes
                   , isEmpty
                   , hasCycle
                   , generateInverseDependencyList) where

import qualified Data.Map as M
import qualified Data.Set as S

data (Ord a) => Graph a = Graph { nodes :: S.Set a
                                , next  :: M.Map a (S.Set a)
                                } deriving (Eq, Show)

empty :: Ord a => Graph a
empty = Graph { nodes = S.empty
              , next = M.empty
              }

create :: Ord a => [(a, [a])] -> Graph a
create pairs = addAllNeighbors pairs $ addAllNodes (map fst pairs) empty

addNode :: Ord a => a -> Graph a -> Graph a
addNode v graph = graph { nodes = v `S.insert` nodes graph }

deleteNode :: Ord a => a -> Graph a -> Graph a
deleteNode v graph = graph { nodes = v `S.delete` nodes graph
                           , next = deleteNodeFromNext v $ next graph}
  where
    deleteNodeFromNext v m = rmFromAllSets v $ M.delete v m
    rmFromAllSets v = M.map (S.delete v)

addAllNodes :: Ord a => [a] -> Graph a -> Graph a
addAllNodes nodes graph = foldl (flip addNode) graph nodes

addNeighbors :: Ord a => a -> [a] -> Graph a -> Graph a
addNeighbors v neighboors graph | graph `contains` v = graph { nodes = nodes $ addAllNodes neighboors graph, next = M.insert v (S.fromList neighboors) $ next graph }
                                | otherwise = addNeighbors v neighboors $ addNode v graph

addAllNeighbors :: Ord a => [(a, [a])] -> Graph a -> Graph a
addAllNeighbors pairs graph = foldl (\g (v, next) -> addNeighbors v next g) graph pairs

contains :: Ord a => Graph a -> a -> Bool
contains graph v = v `S.member` nodes graph

getNeighbors :: Ord a => Graph a -> a -> Maybe [a]
getNeighbors graph v = do
  ns <- M.lookup v $ next graph
  let nList = S.toList ns
  if null nList
  then Nothing
  else return nList

getNodes :: Ord a => Graph a -> [a]
getNodes = S.toList . nodes

isEmpty :: Ord a => Graph a -> Bool
isEmpty graph = S.null $ nodes graph

hasCycle :: Ord a => Graph a -> Bool
hasCycle graph | isEmpty graph = False
               | otherwise = any (`go` S.empty) $ getNodes graph
  where
    go node visited |node `S.member` visited = True
                    | otherwise = case getNeighbors graph node of
                                    Nothing -> False
                                    Just next -> any (flip go $ node `S.insert` visited) next

dfs :: Ord a => Graph a -> Maybe a
dfs graph | isEmpty graph = Nothing
          | otherwise = go (head $ getNodes graph) graph
  where
    go :: Ord a => a -> Graph a -> Maybe a
    go node graph =
      case getNeighbors graph node of
        Nothing -> return node
        Just [] -> error "getNeighbors returning \"Just []\" should never happen"
        Just (node':_) -> go node' graph

generateInverseDependencyList :: Ord a => Graph a -> [a]
generateInverseDependencyList graph = go graph []
  where
    go graph accum =
      case dfs graph of
        Nothing -> reverse accum
        Just n -> go (deleteNode n graph) (n:accum)