{-# LANGUAGE ScopedTypeVariables #-}

-- | The function in this module finds a cycle in a given directed graph, if
-- one exists.
module Graphs.FindCycle (
   findCycle,
      -- :: Ord a => [a] -> (a -> [a]) -> Maybe [a]
      -- List of all nodes, and a successor function.
   ) where

import qualified Data.Set as Set

data DFSOut a =
      NoCycle (Set.Set a) -- set of nodes *not* visited
   |  Cycle [a]
   |  PartialCycle [a] a

-- | Find a cycle in a graph.  We are given a list of nodes to start
-- from, and a successor function.
findCycle :: Ord a => [a] -> (a -> [a]) -> Maybe [a]
findCycle (nodes :: [a]) (sFn :: a -> [a]) =
   let
      findCycle1 :: [a] -> Set.Set a -> Maybe [a]
      findCycle1 nodes0 visited0 =
         case nodes0 of
            a : nodes1 ->
               case findCycle2 Set.empty visited0 a of
                  NoCycle visited1 -> findCycle1 nodes1 visited1
                  Cycle cycle -> Just cycle
                  _ -> error "findCycle - unexpected PartialCycle"
            [] -> Nothing


      findCycle2 :: Set.Set a -> Set.Set a -> a -> DFSOut a
      findCycle2 aboveThis0 visited0 this =
         if Set.member this visited0
            then
               NoCycle visited0
            else
               if Set.member this aboveThis0
                  then
                     PartialCycle [] this
                  else
                     let
                        succs = sFn this
                        aboveThis1 = Set.insert this aboveThis0

                        doSuccs :: [a] -> Set.Set a -> DFSOut a
                        doSuccs [] visited
                           = NoCycle (Set.insert this visited)
                        doSuccs (succ:succs) visited0 =
                           case findCycle2 aboveThis1 visited0 succ of
                              NoCycle visited1 -> doSuccs succs visited1
                              PartialCycle arc node ->
                                 if node == this
                                    then
                                       Cycle (this : arc)
                                    else
                                       PartialCycle (this : arc) node
                              cycle -> cycle
                     in
                        doSuccs succs visited0
   in
      findCycle1 nodes Set.empty