-- | For Connectivity analisis purposes a 'DGraph' can be converted into a
-- | 'UGraph' using 'toUndirected'

{-# LANGUAGE ScopedTypeVariables #-}

module Data.Graph.Connectivity where

import Data.List (foldl')

import           Data.Hashable
import qualified Data.Set      as S

import Data.Graph.DGraph
import Data.Graph.Types
import Data.Graph.UGraph

-- | Tell if two vertices of a graph are connected
-- | Two vertices are @connected@ if it exists a path between them
-- | The order of the vertices is relevant when the graph is directed
areConnected :: forall g v e . (Graph g, Hashable v, Eq v, Ord v)
 => g v e
 -> v
 -> v
 -> Bool
areConnected g fromV toV
    | fromV == toV = True
    | otherwise = search (fromV : reachableAdjacentVertices g fromV) S.empty toV
    where
        search :: [v] -> S.Set v -> v -> Bool
        search [] _ _ = False
        search (v:vs) banned v'
            | v `S.member` banned = search vs banned v'
            | v == v' = True
            | otherwise =
                search (v : reachableAdjacentVertices g v) banned' v'
                || search vs banned' v'
            where banned' = v `S.insert` banned

-- | Tell if two vertices of a 'UGraph' are disconnected
-- | Two vertices are @disconnected@ if it doesn't exist a path between them
areDisconnected :: (Graph g, Hashable v, Eq v, Ord v) => g v e -> v -> v -> Bool
areDisconnected g fromV toV = not $ areConnected g fromV toV

-- | Tell if a vertex is isolated
-- | A vertex is @isolated@ if it has no incidet edges, that is, it has a degree
-- | of zero
isIsolated :: (Graph g, Hashable v, Eq v) => g v e -> v -> Bool
isIsolated g v = vertexDegree g v == 0

-- | Tell if a graph is connected
-- | An Undirected Graph is @connected@ when there is a path between every pair
-- | of vertices
isConnected :: (Graph g, Hashable v, Eq v, Ord v) => g v e -> Bool
-- FIXME: Use a O(n) algorithm
isConnected g = go vs True
    where
        vs = vertices g
        go _ False = False
        go [] bool = bool
        go (v':vs') bool =
            go vs' $ foldl' (\b v -> b && areConnected g v v') bool vs

-- | Tell if a graph is bridgeless
-- | A graph is @bridgeless@ if it has no edges that, when removed, split the
-- | graph in two isolated components
isBridgeless :: (Hashable v, Eq v, Ord v) => UGraph v e -> Bool
-- FIXME: Use a O(n) algorithm
isBridgeless g =
    foldl' (\b vs -> b && isConnected (removeEdgePair vs g)) True (edgePairs g)

-- | Tell if a 'UGraph' is orietable
-- | An undirected graph is @orietable@ if it can be converted into a directed
-- | graph that is @strongly connected@ (See 'isStronglyConnected')
isOrientable :: (Hashable v, Eq v, Ord v) => UGraph v e -> Bool
isOrientable g = isConnected g && isBridgeless g

-- | Tell if a 'DGraph' is weakly connected
-- | A Directed Graph is @weakly connected@ if the underlying undirected graph
-- | is @connected@
isWeaklyConnected :: (Hashable v, Eq v, Ord v) => DGraph v e -> Bool
isWeaklyConnected = isConnected . toUndirected

-- | Tell if a 'DGraph' is strongly connected
-- | A Directed Graph is @strongly connected@ if it contains a directed path
-- | on every pair of vertices
isStronglyConnected :: (Hashable v, Eq v, Ord v) => DGraph v e -> Bool
isStronglyConnected = isConnected

-- TODO
-- * connected component
-- * strong components
-- * vertex cut
-- * vertex connectivity
--     * biconnectivity
--     * triconnectivity
--     * separable
-- * bridge
-- * edge-connectivity
-- * maximally connected
-- * maximally edge-connected
-- * super-connectivity
-- * hyper-connectivity
-- * Menger's theorem

-- Robin's Theorem: a graph is orientable if it is connected and has no bridges