{-# OPTIONS_HADDOCK hide #-}

{- |
   Module      : Data.Graph.Analysis.Internal
   Description : Internal definitions
   Copyright   : (c) Ivan Lazar Miljenovic 2009
   License     : 2-Clause BSD
   Maintainer  : Ivan.Miljenovic@gmail.com

   This module defines various internal definitions utilised by other
   modules of the Graphalyze library.
 -}
module Data.Graph.Analysis.Internal where

import Data.Graph.Inductive.Graph

import Data.Either(partitionEithers)
import qualified Data.Map as M
import Data.Map(Map)
import qualified Data.Set as S
import Data.Set(Set)
import Data.Maybe(fromJust)
import Control.Arrow((***))
import Control.Monad(ap)

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

-- | Squaring a number.
sq   :: (Num a) => a -> a
sq x = x * x

-- | Shorthand for 'fromIntegral'
fI :: (Num a) => Int -> a
fI = fromIntegral

-- | Flip a pair.
swap       :: (a,b) -> (b,a)
swap (a,b) = (b,a)

-- | Apply the same function to both elements of a pair.
applyBoth :: (a -> b) -> (a,a) -> (b,b)
applyBoth f = f *** f

-- | Create a lookup 'Map' to determine which 'Node' has a specific label.
mkNodeMap :: (Ord a) => [LNode a] -> Map a Node
mkNodeMap = M.fromList . map swap

spreadOut :: [([a], b)] -> [(a,b)]
spreadOut = concatMap spread
  where
    spread (as, b) = map (flip (,) b) as

-- -----------------------------------------------------------------------------
-- Items re-exported in Utils (needed by Types, so defined here to
-- avoid cycles).

-- | The node number of an 'LNode'.
node :: LNode a -> Node
node = fst

-- | The label of an 'LNode'.
label :: LNode a -> a
label = snd

-- | Find all the labelled nodes in the graph that match the given predicate.
filterNodes     :: (Graph g) => (g a b -> LNode a -> Bool) -> g a b -> [LNode a]
filterNodes p g = filter (p g) (labNodes g)

-- | Find all the nodes in the graph that match the given predicate.
filterNodes'     :: (Graph g) => (g a b -> Node -> Bool) -> g a b -> [Node]
filterNodes' p g = filter (p g) (nodes g)

-- | Obtain the labels for a list of 'Node's.
--   It is assumed that each 'Node' is indeed present in the given graph.
addLabels    :: (Graph g) => g a b -> [Node] -> [LNode a]
addLabels gr = map (ap (,) (fromJust . lab gr))

-- | Obtain the labels for a 'Set' of 'Node's.
--   It is assumed that each 'Node' is indeed present in the given graph.
addLabels'    :: (Ord a, Graph g) => g a b -> Set Node -> Set (LNode a)
addLabels' gr = S.map (ap (,) (fromJust . lab gr))

-- | Obtain the labels for a list of 'Node's.
--   It is assumed that each 'Node' is indeed present in the given graph.
getLabels   :: (Graph g) => g a b -> [Node] -> [a]
getLabels gr = map label . addLabels gr

-- | Obtain the labels for a list of 'Node's.
--   It is assumed that each 'Node' is indeed present in the given graph.
getLabels'   :: (Ord a, Graph g) => g a b -> Set Node -> Set a
getLabels' gr = S.fromList -- List fusion might make this more
                           -- efficient than multiple S.map's with the
                           -- resulting internal re-organisation.
                . getLabels gr
                . S.toList

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

-- | A relationship between two nodes with a label.
type Rel n e = (n, n, e)

applyNodes               :: (a -> b) -> Rel a e -> Rel b e
applyNodes f (n1, n2, e) = (f n1, f n2, e)

fromNode            :: Rel n e -> n
fromNode (n1, _, _) = n1

toNode            :: Rel n e -> n
toNode (_, n2, _) = n2

relLabel           :: Rel n e -> e
relLabel (_, _, e) = e


relsToEs              :: (Ord a) => Bool -> [LNode a] -> [Rel a e]
                         -> ([Rel a e], [LEdge e])
relsToEs isDir lns rs = (unRs, graphEdges)
    where
      -- Creating a lookup map from the label to the @Node@ value.
      nodeMap = mkNodeMap lns
      findNode v = M.lookup v nodeMap
      -- Validate a edge after looking its values up.
      validEdge e = case applyNodes findNode e of
                      (Just x, Just y, l) -> Right (x,y,l)
                      _                   -> Left e
      -- The valid edges in the graph.
      (unRs, gEdges) = partitionEithers $ map validEdge rs
      dupSwap' = if isDir
                 then id
                 else concatMap dupSwap
      dupSwap e@(x,y,l) | x == y    = [e]
                        | otherwise = [e, (y,x,l)]
      graphEdges = dupSwap' gEdges