{-# LANGUAGE LambdaCase #-}
----------------------------------------------------------------------------
-- |
-- Module     : Algebra.Graph.Bipartite.AdjacencyMap.Algorithm
-- Copyright  : (c) Andrey Mokhov 2016-2025
-- License    : MIT (see the file LICENSE)
-- Maintainer : andrey.mokhov@gmail.com
-- Stability  : experimental
--
-- __Alga__ is a library for algebraic construction and manipulation of graphs
-- in Haskell. See <https://github.com/snowleopard/alga-paper this paper> for
-- the motivation behind the library, the underlying theory, and
-- implementation details.
--
-- This module provides several basic algorithms on undirected bipartite graphs.
----------------------------------------------------------------------------
module Algebra.Graph.Bipartite.AdjacencyMap.Algorithm (
    -- * Bipartiteness test
    OddCycle, detectParts,

    -- * Matchings
    Matching, pairOfLeft, pairOfRight, matching, isMatchingOf, matchingSize,
    maxMatching,

    -- * Vertex covers
    VertexCover, isVertexCoverOf, vertexCoverSize, minVertexCover,

    -- * Independent sets
    IndependentSet, isIndependentSetOf, independentSetSize, maxIndependentSet,

    -- * Miscellaneous
    augmentingPath, consistentMatching
    ) where

import Algebra.Graph.Bipartite.AdjacencyMap

import Control.Monad             (guard, when)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Maybe (MaybeT(..))
import Control.Monad.Trans.State (State, runState, get, put, modify)
import Control.Monad.ST          (ST, runST)
import Data.Either               (fromLeft)
import Data.Foldable             (asum, foldl')
import Data.Functor              (($>))
import Data.List                 (sort)
import Data.Maybe                (fromJust)
import Data.STRef                (STRef, newSTRef, readSTRef, writeSTRef, modifySTRef)
import GHC.Generics

import qualified Algebra.Graph.AdjacencyMap as AM

import qualified Data.Map.Strict as Map
import qualified Data.Set        as Set
import qualified Data.Sequence   as Seq

import Data.Map.Strict (Map)
import Data.Set        (Set)
import Data.Sequence   (Seq, ViewL (..), (|>))

-- TODO: Make this representation type-safe
-- | A cycle of odd length. For example, @[1,2,3]@ represents the cycle
-- @1@ @->@ @2@ @->@ @3@ @->@ @1@.
type OddCycle a = [a]

data Part = LeftPart | RightPart deriving (Show, Eq)

otherPart :: Part -> Part
otherPart LeftPart  = RightPart
otherPart RightPart = LeftPart

-- | Test the bipartiteness of a given "Algebra.Graph.AdjacencyMap". In case of
-- success, return an 'AdjacencyMap' with the same set of edges and each vertex
-- marked with the part it belongs to. In case of failure, return any cycle of
-- odd length in the graph.
--
-- The returned partition is lexicographically smallest, assuming that vertices
-- of the left part precede all the vertices of the right part.
--
-- The returned cycle is optimal in the following sense: there exists a path
-- that is either empty or ends in a vertex adjacent to the first vertex in the
-- cycle, such that all vertices in @path@ @++@ @cycle@ are distinct and
-- @path@ @++@ @cycle@ is lexicographically smallest among all such pairs of
-- paths and cycles.
--
-- /Note/: since 'AdjacencyMap' represents /undirected/ bipartite graphs, all
-- edges in the input graph are treated as undirected. See the examples and the
-- correctness property for a clarification.
--
-- Complexity: /O((n + m) * log(n))/ time and /O(n + m)/ memory.
--
-- @
-- detectParts 'Algebra.Graph.AdjacencyMap.empty'                                       == Right 'empty'
-- detectParts ('Algebra.Graph.AdjacencyMap.vertex' x)                                  == Right ('leftVertex' x)
-- detectParts ('Algebra.Graph.AdjacencyMap.edge' x x)                                  == Left [x]
-- detectParts ('Algebra.Graph.AdjacencyMap.edge' 1 2)                                  == Right ('edge' 1 2)
-- detectParts (1 * (2 + 3))                               == Right ('edges' [(1,2), (1,3)])
-- detectParts (1 * 2 * 3)                                 == Left [1, 2, 3]
-- detectParts ((1 + 3) * (2 + 4) + 6 * 5)                 == Right ('swap' (1 + 3) * (2 + 4) + 'swap' 5 * 6)
-- detectParts ((1 * 3 * 4) + 2 * (1 + 2))                 == Left [2]
-- detectParts ('Algebra.Graph.AdjacencyMap.clique' [1..10])                            == Left [1, 2, 3]
-- detectParts ('Algebra.Graph.AdjacencyMap.circuit' [1..10])                           == Right ('circuit' [(x, x + 1) | x <- [1,3,5,7,9]])
-- detectParts ('Algebra.Graph.AdjacencyMap.circuit' [1..11])                           == Left [1..11]
-- detectParts ('Algebra.Graph.AdjacencyMap.biclique' [] xs)                            == Right ('vertices' xs [])
-- detectParts ('Algebra.Graph.AdjacencyMap.biclique' ('map' Left (x:xs)) ('map' Right ys)) == Right ('biclique' ('map' Left (x:xs)) ('map' Right ys))
-- 'isRight' (detectParts ('Algebra.Graph.AdjacencyMap.star' x ys))                       == 'notElem' x ys
-- 'isRight' (detectParts ('fromBipartite' ('toBipartite' x)))   == True
-- @
--
-- The correctness of 'detectParts' can be expressed by the following property:
--
-- @
-- let undirected = 'Algebra.Graph.AdjacencyMap.symmetricClosure' input in
-- case detectParts input of
--     Left cycle -> 'mod' (length cycle) 2 == 1 && 'Algebra.Graph.AdjacencyMap.isSubgraphOf' ('Algebra.Graph.AdjacencyMap.circuit' cycle) undirected
--     Right result -> 'Algebra.Graph.AdjacencyMap.gmap' 'Data.Either.Extra.fromEither' ('fromBipartite' result) == undirected
-- @
detectParts :: Ord a => AM.AdjacencyMap a -> Either (OddCycle a) (AdjacencyMap a a)
detectParts x = case runState (runMaybeT dfs) Map.empty of
    (Nothing, partMap) -> Right $ toBipartiteWith (toEither partMap) g
    (Just c , _      ) -> Left  $ oddCycle c
  where
    -- g :: AM.AdjacencyMap a
    g = AM.symmetricClosure x

    -- type PartMap a = Map a Part
    -- type PartMonad a = MaybeT (State (PartMap a)) [a]
    -- dfs :: PartMonad a
    dfs = asum [ processVertex v | v <- AM.vertexList g ]

    -- processVertex :: a -> PartMonad a
    processVertex v = do partMap <- lift get
                         guard (Map.notMember v partMap)
                         inVertex LeftPart v

    -- inVertex :: Part -> a -> PartMonad a
    inVertex vertexPart v = (v :) <$> do
        lift $ modify (Map.insert v vertexPart)
        let otherVertexPart = otherPart vertexPart
        asum [ onEdge otherVertexPart u | u <- Set.toAscList (AM.postSet v g) ]

    {-# INLINE onEdge #-}
    -- onEdge :: Part -> a -> PartMonad a
    onEdge vertexPart v = do partMap <- lift get
                             case Map.lookup v partMap of
                                 Nothing   -> inVertex vertexPart v
                                 Just part -> do guard (vertexPart /= part)
                                                 return [v] -- found a cycle!

    -- toEither :: PartMap a -> a -> Either a a
    toEither partMap v = case fromJust (Map.lookup v partMap) of
                             LeftPart  -> Left  v
                             RightPart -> Right v

    -- oddCycle :: [a] -> [a]
    oddCycle pathToCycle = init $ dropWhile (/= lastVertex) pathToCycle
      where
        lastVertex = last pathToCycle

-- | A /matching/ is a set of pairwise non-adjacent edges between the two parts
-- of a bipartite graph.
--
-- The 'Show' instance is defined using the 'matching' function, with the edges
-- listed in the ascending order of left vertices.
--
-- @
-- show ('matching' [])                 == "matching []"
-- show ('matching' [(2,\'a\'), (1,\'b\')]) == "matching [(1,\'b\'),(2,\'a\')]"
-- @
data Matching a b = Matching {
    -- | The map of vertices covered by the matching in the left part to their
    -- neighbours in the right part.
    -- Complexity: /O(1)/ time.
    --
    -- @
    -- pairOfLeft ('matching' [])                 == Map.'Data.Map.Strict.empty'
    -- pairOfLeft ('matching' [(2,\'a\'), (1,\'b\')]) == Map.'Data.Map.Strict.fromList' [(1,\'b\'), (2,\'a\')]
    -- Map.'Map.size' . pairOfLeft                    == Map.'Map.size' . pairOfRight
    -- @
    pairOfLeft  :: Map a b,

    -- | The map of vertices covered by the matching in the right part to their
    -- neighbours in the left part.
    -- Complexity: /O(1)/.
    --
    -- @
    -- pairOfRight ('matching' [])                 == Map.'Data.Map.Strict.empty'
    -- pairOfRight ('matching' [(2,\'a\'), (1,\'b\')]) == Map.'Data.Map.Strict.fromList' [(\'a\',2), (\'b\',1)]
    -- Map.'Map.size' . pairOfRight                    == Map.'Map.size' . pairOfLeft
    -- @
    pairOfRight :: Map b a
} deriving Generic

instance (Show a, Show b) => Show (Matching a b) where
    showsPrec _ m = showString "matching " . showList (Map.toAscList $ pairOfLeft m)

instance (Eq a, Eq b) => Eq (Matching a b) where
    x == y = pairOfLeft x == pairOfLeft y

instance (Ord a, Ord b) => Ord (Matching a b) where
    compare x y = compare (pairOfLeft x) (pairOfLeft y)

addEdgeUnsafe :: (Ord a, Ord b) => a -> b -> Matching a b -> Matching a b
addEdgeUnsafe a b (Matching ab ba) = Matching (Map.insert a b ab) (Map.insert b a ba)

addEdge :: (Ord a, Ord b) => a -> b -> Matching a b -> Matching a b
addEdge a b (Matching ab ba) = addEdgeUnsafe a b (Matching ab' ba')
    where
        ab' = case b `Map.lookup` ba of
                  Nothing -> Map.delete a ab
                  Just a' -> Map.delete a (Map.delete a' ab)
        ba' = case a `Map.lookup` ab of
                  Nothing -> Map.delete b ba
                  Just b' -> Map.delete b (Map.delete b' ba)

leftCovered :: Ord a => a -> Matching a b -> Bool
leftCovered a = Map.member a . pairOfLeft

-- | Construct a 'Matching' from a list of edges.
-- Complexity: /O(L * log(L))/ time, where /L/ is the length of the given list.
--
-- Edges that appear closer to the end of the list supersede all previous edges.
-- That is, if two edges from the list share a vertex, the one that appears
-- closer to the beginning is ignored.
--
-- @
-- 'pairOfLeft'  (matching [])                     == Map.'Data.Map.Strict.empty'
-- 'pairOfRight' (matching [])                     == Map.'Data.Map.Strict.empty'
-- 'pairOfLeft'  (matching [(2,\'a\'), (1,\'b\')])     == Map.'Data.Map.Strict.fromList' [(2,\'a\'), (1,\'b\')]
-- 'pairOfLeft'  (matching [(1,\'a\'), (1,\'b\')])     == Map.'Data.Map.Strict.singleton' 1 \'b\'
-- matching [(1,\'a\'), (1,\'b\'), (2,\'b\'), (2,\'a\')] == matching [(2,\'a\')]
-- @
matching :: (Ord a, Ord b) => [(a, b)] -> Matching a b
matching = foldl' (flip (uncurry addEdge)) (Matching Map.empty Map.empty)

-- | Check if a given 'Matching' is a valid /matching/ of a bipartite graph.
-- Complexity: /O(S * log(n))/, where /S/ is the size of the matching.
--
-- @
-- isMatchingOf ('matching' []) x               == True
-- isMatchingOf ('matching' xs) 'empty'           == 'null' xs
-- isMatchingOf ('matching' [(x,y)]) ('edge' x y) == True
-- isMatchingOf ('matching' [(1,2)]) ('edge' 2 1) == False
-- @
isMatchingOf :: (Ord a, Ord b) => Matching a b -> AdjacencyMap a b -> Bool
isMatchingOf m@(Matching ab _) g = consistentMatching m
    && and [ hasEdge a b g | (a, b) <- Map.toList ab ]

-- | The number of edges in a matching.
-- Complexity: /O(1)/ time.
--
-- @
-- matchingSize ('matching' [])                 == 0
-- matchingSize ('matching' [(2,\'a\'), (1,\'b\')]) == 2
-- matchingSize ('matching' [(1,\'a\'), (1,\'b\')]) == 1
-- matchingSize ('matching' xs)                 <= 'length' xs
-- matchingSize                               == Map.'Data.Map.Strict.size' . 'pairOfLeft'
-- @
matchingSize :: Matching a b -> Int
matchingSize = Map.size . pairOfLeft

-- | Find a /maximum matching/ in a bipartite graph. A matching is maximum if it
-- has the largest possible size.
-- Complexity: /O(m * sqrt(n) * log(n))/ time.
--
-- @
-- maxMatching 'empty'                                          == 'matching' []
-- maxMatching ('vertices' xs ys)                               == 'matching' []
-- maxMatching ('path' [1,2,3,4])                               == 'matching' [(1,2), (3,4)]
-- 'matchingSize' (maxMatching ('circuit' [(1,2), (3,4), (5,6)])) == 3
-- 'matchingSize' (maxMatching ('star' x (y:ys)))                 == 1
-- 'matchingSize' (maxMatching ('biclique' xs ys))                == 'min' ('length' ('Data.List.nub' xs)) ('length' ('Data.List.nub' ys))
-- 'isMatchingOf' (maxMatching x) x                             == True
-- @
maxMatching :: (Ord a, Ord b) => AdjacencyMap a b -> Matching a b
maxMatching graph = runST (maxMatchingHK graph)

-- TODO: Should we use a more efficient data structure for the queue?
-- TODO: We could try speeding this up by representing vertices with 'Int's.
-- The state maintained by the Hopcroft-Karp algorithm implemented below
data HKState s a b = HKState
    { distance    :: STRef s (Map a Int)
    , curMatching :: STRef s (Matching a b)
    , queue       :: STRef s (Seq a)
    , visited     :: STRef s (Set a) }

-- See https://en.wikipedia.org/wiki/Hopcroft-Karp_algorithm
maxMatchingHK :: forall a b s. (Ord a, Ord b) => AdjacencyMap a b -> ST s (Matching a b)
maxMatchingHK g = do
    distance    <- newSTRef Map.empty
    curMatching <- newSTRef (Matching Map.empty Map.empty)
    queue       <- newSTRef Seq.empty
    visited     <- newSTRef Set.empty
    runHK (HKState distance curMatching queue visited)
    readSTRef curMatching
  where
    runHK :: HKState s a b -> ST s ()
    runHK state = do writeSTRef (distance state) Map.empty
                     foundAugmentingPath <- bfs state
                     when foundAugmentingPath $ do
                         writeSTRef (visited state) Set.empty
                         dfs state
                         runHK state

    currentlyUncovered :: HKState s a b -> ST s [a]
    currentlyUncovered state = do
        m <- readSTRef (curMatching state)
        return [ v | v <- leftVertexList g, not (leftCovered v m) ]


    bfs :: HKState s a b -> ST s Bool
    bfs state = do
        uncovered <- currentlyUncovered state
        mapM_ (enqueue state 1) uncovered
        bfsLoop state

    enqueue :: HKState s a b -> Int -> a -> ST s ()
    enqueue state d v = do modifySTRef (distance state) (Map.insert v d)
                           modifySTRef (queue    state) (|> v)

    dequeue :: HKState s a b -> ST s (Maybe a)
    dequeue state = do q <- readSTRef (queue state)
                       case Seq.viewl q of
                           a :< q -> writeSTRef (queue state) q $> Just a
                           EmptyL -> return Nothing

    bfsLoop :: HKState s a b -> ST s Bool
    bfsLoop state = dequeue state >>= \case
                        Just v  -> do p <- bfsVertex state v
                                      q <- bfsLoop state
                                      return (p || q)
                        Nothing -> return False

    bfsVertex :: HKState s a b -> a -> ST s Bool
    bfsVertex state v = do dist <- readSTRef (distance state)
                           let d = fromJust (v `Map.lookup` dist) + 1
                           or <$> mapM (bfsEdge state d) (neighbours v)

    checkEnqueue :: HKState s a b -> Int -> a -> ST s ()
    checkEnqueue state d v = do dist <- readSTRef (distance state)
                                when (v `Map.notMember` dist) (enqueue state d v)

    bfsEdge :: HKState s a b -> Int -> b -> ST s Bool
    bfsEdge state d u = do m <- readSTRef (curMatching state)
                           case u `Map.lookup` pairOfRight m of
                               Just v  -> checkEnqueue state d v $> False
                               Nothing -> return True

    dfs :: HKState s a b -> ST s ()
    dfs state = currentlyUncovered state >>= mapM_ (dfsVertex state 0)

    dfsVertex :: HKState s a b -> Int -> a -> ST s Bool
    dfsVertex state d v = do dist <- readSTRef (distance state)
                             vis  <- readSTRef (visited state)
                             let dv = fromJust (v `Map.lookup` dist)
                             case (d + 1 == dv) && (v `Set.notMember` vis) of
                                 False -> return False
                                 True  -> do modifySTRef (visited state) (Set.insert v)
                                             dfsEdges state dv v (neighbours v)

    dfsEdges :: HKState s a b -> Int -> a -> [b] -> ST s Bool
    dfsEdges _     _ _ []     = return False
    dfsEdges state d a (b:bs) = do m <- readSTRef (curMatching state)
                                   case b `Map.lookup` pairOfRight m of
                                       Nothing -> addEdge state a b $> True
                                       Just w  -> dfsVertex state d w >>= \case
                                            True  -> addEdge state a b $> True
                                            False -> dfsEdges state d a bs

    addEdge :: HKState s a b -> a -> b -> ST s ()
    addEdge state a b = modifySTRef (curMatching state) (addEdgeUnsafe a b)

    neighbours :: a -> [b]
    neighbours a = Set.toAscList $ fromJust $ Map.lookup a $ leftAdjacencyMap g

-- | A /vertex cover/ of a bipartite graph.
--
-- A /vertex cover/ is a subset of vertices such that every edge is incident to
-- some vertex in the subset. We represent vertex covers by storing two sets of
-- vertices, one for each part. An equivalent representation, which is slightly
-- less memory efficient, is @Set@ @(Either@ @a@ @b)@.
type VertexCover a b = (Set a, Set b)

-- | Check if a given pair of sets is a /vertex cover/ of a bipartite graph.
-- Complexity: /O(m * log(n))/.
--
-- @
-- isVertexCoverOf (xs             , ys             ) 'empty'          == Set.'Set.null' xs && Set.'Set.null' ys
-- isVertexCoverOf (xs             , ys             ) ('leftVertex' x) == Set.'Set.isSubsetOf' xs (Set.'Set.singleton' x) && Set.'Set.null' ys
-- isVertexCoverOf (Set.'Set.empty'      , Set.'Set.empty'      ) ('edge' x y)     == False
-- isVertexCoverOf (Set.'Set.singleton' x, ys             ) ('edge' x y)     == Set.'Set.isSubsetOf' ys (Set.'Set.singleton' y)
-- isVertexCoverOf (xs             , Set.'Set.singleton' y) ('edge' x y)     == Set.'Set.isSubsetOf' xs (Set.'Set.singleton' x)
-- @
isVertexCoverOf :: (Ord a, Ord b) => (Set a, Set b) -> AdjacencyMap a b -> Bool
isVertexCoverOf (as, bs) g = as `Set.isSubsetOf` leftVertexSet g
    && bs `Set.isSubsetOf` rightVertexSet g
    && and [ a `Set.member` as || b `Set.member` bs | (a, b) <- edgeList g ]

-- | The number of vertices in a vertex cover.
-- Complexity: /O(1)/ time.
vertexCoverSize :: VertexCover a b -> Int
vertexCoverSize (as, bs) = Set.size as + Set.size bs

-- | Find a /minimum vertex cover/ in a bipartite graph. A vertex cover is
-- minimum if it has the smallest possible size.
-- Complexity: /O(m * sqrt(n) * log(n))/.
--
-- @
-- minVertexCover 'empty'                              == (Set.'Set.empty', Set.'Set.empty')
-- minVertexCover ('vertices' xs ys)                   == (Set.'Set.empty', Set.'Set.empty')
-- minVertexCover ('path' [1,2,3])                     == (Set.'Set.empty', Set.'Set.singleton' 2)
-- minVertexCover ('star' x (1:2:ys))                  == (Set.'Set.singleton' x, Set.'Set.empty')
-- 'vertexCoverSize' (minVertexCover ('biclique' xs ys)) == 'min' ('length' ('Data.List.nub' xs)) ('length' ('Data.List.nub' ys))
-- 'vertexCoverSize' . minVertexCover                  == 'matchingSize' . 'maxMatching'
-- 'isVertexCoverOf' (minVertexCover x) x              == True
-- @
minVertexCover :: (Ord a, Ord b) => AdjacencyMap a b -> VertexCover a b
minVertexCover g = fromLeft panic $ augmentingPath (maxMatching g) g
  where
    panic = error "minVertexCover: internal error (found augmenting path)"

-- | An /independent set/ of a bipartite graph.
--
-- An /independent set/ is a subset of vertices such that no two of them are
-- adjacent. We represent independent sets by storing two sets of vertices, one
-- for each part. An equivalent representation, which is slightly less memory
-- efficient, is @Set@ @(Either@ @a@ @b)@.
type IndependentSet a b = (Set a, Set b)

-- | Check if a given pair of sets is an /independent set/ of a bipartite graph.
-- Complexity: /O(m * log(n))/.
--
-- @
-- isIndependentSetOf (xs             , ys             ) 'empty'          == Set.'Set.null' xs && Set.'Set.null' ys
-- isIndependentSetOf (xs             , ys             ) ('leftVertex' x) == Set.'Set.isSubsetOf' xs (Set.'Set.singleton' x) && Set.'Set.null' ys
-- isIndependentSetOf (Set.'Set.empty'      , Set.'Set.empty'      ) ('edge' x y)     == True
-- isIndependentSetOf (Set.'Set.singleton' x, ys             ) ('edge' x y)     == Set.'Set.null' ys
-- isIndependentSetOf (xs             , Set.'Set.singleton' y) ('edge' x y)     == Set.'Set.null' xs
-- @
isIndependentSetOf :: (Ord a, Ord b) => (Set a, Set b) -> AdjacencyMap a b -> Bool
isIndependentSetOf (as, bs) g = as `Set.isSubsetOf` leftVertexSet g
    && bs `Set.isSubsetOf` rightVertexSet g
    && and [ not (a `Set.member` as && b `Set.member` bs) | (a, b) <- edgeList g ]

-- | The number of vertices in an independent set.
-- Complexity: /O(1)/ time.
independentSetSize :: IndependentSet a b -> Int
independentSetSize (as, bs) = Set.size as + Set.size bs

-- | Find a /maximum independent set/ in a bipartite graph. An independent set
-- is maximum if it has the largest possible size.
-- Complexity: /O(m * sqrt(n) * log(n))/.
--
-- @
-- maxIndependentSet 'empty'                                 == (Set.'Set.empty', Set.'Set.empty')
-- maxIndependentSet ('vertices' xs ys)                      == (Set.'Set.fromList' xs, Set.'Set.fromList' ys)
-- maxIndependentSet ('path' [1,2,3])                        == (Set.'Set.fromList' [1,3], Set.'Set.empty')
-- maxIndependentSet ('star' x (1:2:ys))                     == (Set.'Set.empty', Set.'Set.fromList' (1:2:ys))
-- 'independentSetSize' (maxIndependentSet ('biclique' xs ys)) == 'max' ('length' ('Data.List.nub' xs)) ('length' ('Data.List.nub' ys))
-- 'independentSetSize' (maxIndependentSet x)                == 'vertexCount' x - 'vertexCoverSize' ('minVertexCover' x)
-- 'isIndependentSetOf' (maxIndependentSet x) x              == True
-- @
maxIndependentSet :: (Ord a, Ord b) => AdjacencyMap a b -> IndependentSet a b
maxIndependentSet g =
    (leftVertexSet g `Set.difference` as, rightVertexSet g `Set.difference` bs)
  where
    (as, bs) = minVertexCover g

-- | Given a matching in a bipartite graph, find either a /vertex cover/ of the
-- same size or an /augmenting path/ with respect to the matching, thereby
-- demonstrating that the matching is not maximum.
-- Complexity: /O((m + n) * log(n))/.
--
-- An /alternating path/ is a path whose edges belong alternately to the
-- matching and not to the matching. An /augmenting path/ is an alternating path
-- that starts from and ends on the vertices that are not covered by the
-- matching. A matching is maximum if and only if there is no augmenting path
-- with respect to it.
--
-- @
-- augmentingPath ('matching' [])      'empty'            == Left (Set.'Set.empty', Set.'Set.empty')
-- augmentingPath ('matching' [])      ('edge' 1 2)       == Right [1,2]
-- augmentingPath ('matching' [(1,2)]) ('path' [1,2,3])   == Left (Set.'Set.empty', Set.'Set.singleton' 2)
-- augmentingPath ('matching' [(3,2)]) ('path' [1,2,3,4]) == Right [1,2,3,4]
-- isLeft (augmentingPath ('maxMatching' x) x)          == True
-- @
augmentingPath :: (Ord a, Ord b) => Matching a b -> AdjacencyMap a b -> Either (VertexCover a b) (List a b)
augmentingPath = augmentingPathImpl

type AugPathMonad a b = MaybeT (State (VertexCover a b)) (List a b)

-- The implementation is in a separate function to avoid the "forall" in docs.
augmentingPathImpl :: forall a b. (Ord a, Ord b) => Matching a b -> AdjacencyMap a b -> Either (VertexCover a b) (List a b)
augmentingPathImpl m g = case runState (runMaybeT dfs) (leftVertexSet g, Set.empty) of
    (Nothing  , cover) -> Left cover
    (Just path, _    ) -> Right path
  where
    dfs :: AugPathMonad a b
    dfs = asum [ inVertex v | v <- leftVertexList g, not (leftCovered v m) ]

    inVertex :: a -> AugPathMonad a b
    inVertex a = do (as, bs) <- lift get
                    guard (a `Set.member` as)
                    lift $ put (Set.delete a as, bs)
                    asum [ onEdge a b | b <- neighbours a ]

    onEdge :: a -> b -> AugPathMonad a b
    onEdge a b = addEdge a b <$> do (as, bs) <- lift get
                                    lift $ put (as, Set.insert b bs)
                                    case b `Map.lookup` pairOfRight m of
                                        Just a  -> inVertex a
                                        Nothing -> return Nil

    addEdge :: a -> b -> List a b -> List a b
    addEdge a b = Cons a . Cons b

    neighbours :: a -> [b]
    neighbours a = Set.toAscList $ fromJust $ Map.lookup a $ leftAdjacencyMap g

-- | Check if the internal representation of a matching is consistent, i.e. that
-- every edge that is present in 'pairOfLeft' is also present in 'pairOfRight'.
-- Complexity: /O(S * log(S))/, where /S/ is the size of the matching.
--
-- @
-- consistentMatching ('matching' xs)   == True
-- consistentMatching ('maxMatching' x) == True
-- @
consistentMatching :: (Ord a, Ord b) => Matching a b -> Bool
consistentMatching (Matching ab ba) =
    Map.toAscList ab == sort [ (a, b) | (b, a) <- Map.toAscList ba ]
